# Enabling JSX in Node.js

By default, the native Node.js module system:

Using immaculata, you can:

import { compileJsx } from "immaculata/hooks.js"
import { registerHooks } from "module"
import ts from 'typescript'
import { fileURLToPath } from "url"

// transpiles tsx into javascript when Node.js loads it
registerHooks(compileJsx((str, url) =>
  ts.transpileModule(str, {
    fileName: fileURLToPath(url),
    compilerOptions: {
      target: ts.ScriptTarget.ESNext,
      module: ts.ModuleKind.ESNext,
      jsx: ts.JsxEmit.ReactJSX,
      sourceMap: true,
      inlineSourceMap: true,
      inlineSources: true,
    }
  }).outputText
))

# Remapping JSX implementation

By default, using JSX will auto-import react/jsx-runtime like usual.

You'll almost definitely want to remap that import to anything else:

import { hooks } from "immaculata"
import { registerHooks } from "module"

registerHooks(hooks.mapImport('react/jsx-runtime', 'another-jsx-impl'))

# Simple JSX string-builder

The module 'immaculata/jsx-strings.js' provides react/jsx-runtime-compatible exports that are implemented as a highly efficient HTML string builder.

import { hooks } from "immaculata"
import { registerHooks } from "module"

registerHooks(hooks.mapImport('react/jsx-runtime', 'immaculata/jsx-strings.js'))

# Using your own JSX implementation

To use a JSX implementatoin within a FileTree, prepend its root:

import { FileTree, hooks } from "immaculata"
import { registerHooks } from "module"

const tree = new FileTree('site', import.meta.url)

registerHooks(hooks.mapImport('react/jsx-runtime', tree.root + '/my-jsx.ts'))

# Importing with .js

To allow importing .jsx/.tsx files but using the .js extension:

import { hooks } from "immaculata"
import { registerHooks } from "module"

registerHooks(hooks.tryAltExts)

import('./foo.js') // now works even though only foo.tsx exists

# JSX Types

If you're not using a library that provides JSX types, you'll need to add your own.

Here's a basic starter:

declare namespace JSX {

  type IntrinsicElements = Record<string, any>

  interface ElementChildrenAttribute {
    children: any
  }

}

If you're using immaculata/jsx-strings.js then you should use this:

declare namespace JSX {

  type jsxChildren =
    | string
    | false
    | null
    | undefined
    | jsxChildren[]

  type ElementChildrenAttribute = { children: jsxChildren }

  type Element = string

  type ElementType =
    | string
    | ((data: any) => jsxChildren)

}

For HTML tag autocompletion and good-enough attr validation, add this:

declare namespace JSX {

  // ...

  type jsxify<T extends HTMLElement> = {
    [A in keyof T as A extends string ? Lowercase<Exclude<A, 'children'>> : never]?: (
      | string
      | boolean
      | (T[A] extends (string | boolean | null | number)
        ? T[A]
        : never))
  } & { children?: any, class?: string }

  type IntrinsicElements =
    & { [K in keyof HTMLElementTagNameMap]: jsxify<HTMLElementTagNameMap[K]> }
    & { meta: jsxify<HTMLMetaElement> & { charset?: 'utf-8' } }

}