TypeScript
The compiler transforms JSX, so TypeScript only type-checks.
The compiler transforms JSX, so TypeScript only type-checks. Three things in tsconfig.json matter for ShadowJS;
the rest is a strict, Vite-friendly baseline (this is what create-shadow-app writes).
json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "preserve",
"jsxImportSource": "@shadow-js/web",
"strict": true,
"noUncheckedIndexedAccess": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"noEmit": true,
"skipLibCheck": true,
"types": ["vite/client"]
},
"include": ["src", "vite.config.ts"]
}
| Setting | Value | Why |
|---|---|---|
jsxImportSource |
"@shadow-js/web" |
Required. TypeScript reads the JSX namespace from @shadow-js/web/jsx-runtime: element and attribute types for HTML, SVG and MathML, typed event handlers, JSX.Element. |
jsx |
"preserve" |
Leaves JSX alone. "react-jsx" also type-checks, because both resolve the same module; nothing is emitted either way (noEmit). |
moduleResolution |
"Bundler" |
Resolves the packages' exports maps, including the types condition. |
verbatimModuleSyntax, isolatedModules |
true |
Vite transpiles each file on its own. Import types with import type. |
types |
["vite/client"] |
Declares import.meta.env and asset imports. |
strict, noUncheckedIndexedAccess |
true |
Recommended. useStore arrays and Record lookups give T | undefined with the second one. |
The JSX types are extensible. Declaration-merge the CustomEvents interface to type on: listeners and the
Directives interface to type use: directives:
src/jsx-augmentation.tsts
declare module "@shadow-js/web/jsx-runtime" {
interface CustomEvents {
toast: CustomEvent<{ message: string }>;
}
interface Directives {
tooltip: string;
}
}
export {};
src/Notice.tsxtsx
import "./jsx-augmentation";
export function Notice() {
return (
<div on:toast={(event) => event.detail.message} use:tooltip="Dismiss">
notice
</div>
);
}