Reference
JSX types and tsconfig
The JSX and environment types, and the tsconfig settings a Lumiose project needs.
#tsconfig.json
A Lumiose project's tsconfig.json extends the tsconfig that Lumiose generates (decision-25). This is
what create-lumiose writes, what lumiose agents init writes when the file is missing, and what
lumiose check prints when there is none:
{
// Lumiose's compiler options, shadow TSX and route types. lumiose dev, build, check and typegen
// write the generated file; add your own options below.
"extends": "./.lumiose/tsconfig.generated.json",
}Add your own compilerOptions next to extends; they override the generated ones. lumiose check,
lumiose typegen, lumiose dev/build, @lumiose/vite and the language server write
.lumiose/tsconfig.generated.json, so it follows the installed Lumiose version and the workspace
layout. .lumiose/ is not committed: in a fresh clone, run lumiose typegen (or lumiose check) before
a bare tsc.
The generated file, with paths relative to .lumiose/:
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "lumiose",
"module": "preserve",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"rootDirs": ["..", "./shadow"],
"types": ["lumiose/env"]
},
"include": ["../src", "./shadow", "./types"]
}| Option | Why |
|---|---|
jsx: "preserve", jsxImportSource: "lumiose" |
The Lumiose compiler transforms JSX; TypeScript only checks it against Lumiose's types. |
rootDirs with .lumiose/shadow |
import "./X.stsx" resolves to the shadow file X.stsx.tsx. |
include .lumiose/shadow, .lumiose/types |
Shadow files and generated route types are part of the program. |
types: ["lumiose/env"] |
The global JSX namespace, import.meta.env, Locals and .svg/.md/.html modules. |
allowImportingTsExtensions |
Imports name their extension: ./Card.stsx, ./tokens.ts. |
skipLibCheck |
Dependencies' .d.ts files are not checked: faster, and their type errors are not yours. |
If you set include or types yourself, they replace the generated lists: keep src,
.lumiose/shadow and .lumiose/types in include, and lumiose/env in types. An array extends
keeps a shared base: ["../../tsconfig.base.json", "./.lumiose/tsconfig.generated.json"] (later
entries win). lumiose check reports missing options (LUM6001).
#Imports outside src/ and .stsx from other packages
The generated rootDirs pair a mirror base with .lumiose/shadow. The mirror base is the nearest
directory that contains the project and the Lumiose packages it uses. These are the linked
dependencies that list lumiose as a dependency, plus the enclosing package of a project without its
own package.json. For a standalone project, it is the project root (.. above).
Shadows mirror the paths below that base. For example, apps/web/.lumiose/shadow/apps/web/src/ holds
the app's own shadows and apps/web/.lumiose/shadow/packages/ui/src/ holds a package's. So a .stsx
import may leave src/ (../../lib/x.ts), and a component that a workspace package re-exports
from its own .stsx files (@acme/ui → ./Button.stsx) is type-checked with your project, with
errors pointing at its sources.
#Without extends
A tsconfig can also set the options itself:
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "lumiose",
"module": "preserve",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"rootDirs": ["src", ".lumiose/shadow/src"],
"types": ["lumiose/env"]
},
"include": ["src", ".lumiose/shadow", ".lumiose/types"]
}Its rootDirs pair only src with .lumiose/shadow/src, so .stsx files must stay inside src/:
a .stsx import that leaves src/ does not resolve (TS2307), and the .stsx modules of workspace
packages have no shadow. lumiose check warns (LUM6001) when a used package has .stsx files that
the tsconfig's rootDirs leave out.
#The JSX namespace
| Type | Rule |
|---|---|
JSX.Element |
What JSX evaluates to: nodes, strings, numbers, booleans, null/undefined, arrays and functions of those. |
JSX.ElementType |
Intrinsic tags, and sync or async function components. |
JSX.IntrinsicElements |
Every HTML (except style), SVG and MathML tag of lib.dom, plus custom elements (my-widget). |
JSX.IntrinsicAttributes |
key, and on components client:load, client:idle, client:visible (boolean | string), client:media (string), client:only. |
JSX.ClassValue |
string | false | null | undefined | Record<string, boolean | null | undefined> | ClassValue[]. |
JSX.CSSProperties |
Kebab-case CSS properties ("font-weight", "-webkit-line-clamp") and --custom properties; values string | number | false | null | undefined. |
JSX.EventHandler<E, T> |
(event: E & { currentTarget: T }) => void. |
JSX.Ref<T> |
What ref<T>() returns: callable with the element, readable as () => T | undefined. |
JSX.HTMLProps<T>, JSX.SVGProps<T>, JSX.MathMLProps<T> |
Props of an element type, for wrappers. |
Element props. Every element accepts children, class, style, ref, attach, innerHTML,
key, its attributes, onX handlers and on:x listeners. input also accepts bind:value
(string or number signal) and bind:checked (boolean signal); select and textarea accept
bind:value.
Rejected on purpose (type errors with a hint): className (use class), htmlFor (use
for), camelCase style keys, exact-case or lowercase handlers (onKeydown, onclick), and
<style> in .tsx files.
Event names. onX handlers use React-style camelCase after on (onClick, onKeyDown,
onDblClick, onPointerDown, onMouseEnter); the DOM event is the suffix lowercased. Bubbling
events are delegated; non-bubbling ones (focus, blur, mouseenter, mouseleave,
pointerenter, pointerleave, scroll, load, error, media events) get a direct listener.
on:name adds a native listener with the exact name; any on:${string} is accepted for custom
events.
#Custom elements
Undeclared custom elements accept global attributes and any other attribute. Type one by
augmenting IntrinsicElements:
declare module "lumiose/jsx-runtime" {
namespace JSX {
interface IntrinsicElements {
"rating-stars": JSX.HTMLProps<HTMLElement> & { value: number; max?: number };
}
}
}
export function Review() {
return (
<rating-stars
value={4}
on:rating-change={(event: CustomEvent<number>) => console.log(event.detail)}
/>
);
}#lumiose/env
interface ImportMetaEnv {
readonly [publicVariable: `PUBLIC_${string}`]: string;
BASE_URL: string;
MODE: string;
DEV: boolean;
PROD: boolean;
SSR: boolean;
}
declare module "lumiose" {
interface Locals {}
}
declare module "*.css" {} // import "./app.css": bundled into the page's CSS
declare module "*.css?url" {} // export default url: string
declare module "*.css?inline" {} // export default css: string
declare module "*.css?raw" {} // export default source: string
declare module "*.svg?component" {
const Svg: (props: JSX.SVGProps<SVGSVGElement> & { title?: string }) => JSX.Element;
export default Svg;
}
declare module "*.svg?url" {} // export default url: string
declare module "*.svg?raw" {} // export default raw: string
declare module "*.md" {
const Content: () => JSX.Element;
export default Content;
export interface Frontmatter extends Record<string, unknown> {} // merge your fields into it
export const frontmatter: Frontmatter;
export interface Heading {
depth: 1 | 2 | 3 | 4 | 5 | 6;
text: string;
id: string;
} // read-only
export const headings: ReadonlyArray<Heading>; // document order, ids of the rendered anchors
}
declare module "*.html" {
const Content: () => JSX.Element;
export default Content;
}Declare known public variables by merging interface ImportMetaEnv { readonly PUBLIC_API: string }.
The built-in members match vite/client, so both can be loaded. vite/client types the other
asset imports, bare .svg URLs included (see SVG imports). TypeScript checks
side-effect imports (noUncheckedSideEffectImports), hence *.css. With lumiose/env listed first it also matches
*.module.css, so a CSS module import has no class map type; scoped <style> blocks replace CSS
modules in Lumiose components.