diff --git a/README.md b/README.md
index 60ae90b..01a2c5e 100644
--- a/README.md
+++ b/README.md
@@ -70,7 +70,7 @@ src % tree
│ ├── client.js # nested pages are just pages, so they also can have a page scoped client and style.
│ └── style.css
├── html-page
-│ ├── client.js
+│ ├── client.jsx # client bundles can also be written in .jsx/.tsx
│ ├── page.html # Raw html pages are also supported. They support handlebars template blocks.
│ ├── page.vars.js # pages can define page variables in a page.vars.js.
│ └── style.css
@@ -316,6 +316,10 @@ await someHelper()
await funnyLibrary()
```
+#### .tsx/.jsx
+
+Client bundles support .jsx and .tsx. They default to preact, so if you want mainlain recat, customize your esbuild settings to load that instead.
+
### Page variable files
Each page can also have a `page.vars.js` file that exports a `default` function or object that contains page specific variables.
@@ -1184,7 +1188,7 @@ Some notable features are included below, see the [roadmap](https://github.com/u
- [x] Esbuild settings escape hatch
- [x] Copy folders
- [x] Full Typescript support via native type stripping
-- [ ] JSX support in client bundles
+- [x] JSX+TSX support in client bundles
- ...[See roadmap](https://github.com/users/bcomnes/projects/3/)
## History
diff --git a/examples/preact/src/README.md b/examples/preact/src/README.md
index d9c4051..93af148 100644
--- a/examples/preact/src/README.md
+++ b/examples/preact/src/README.md
@@ -2,4 +2,5 @@
This is a preact example.
-[Isomorphic Component Rendering](./isomorphic/)
+- [Isomorphic Component Rendering](./isomorphic/)
+- [JSX-page](./jsx-page/)
diff --git a/examples/preact/src/jsx-page/client.jsx b/examples/preact/src/jsx-page/client.jsx
new file mode 100644
index 0000000..fa47236
--- /dev/null
+++ b/examples/preact/src/jsx-page/client.jsx
@@ -0,0 +1,12 @@
+import { render } from 'preact'
+
+export const page = () => {
+ return (
+
+ look ma, client side jsx!
+
+ )
+}
+
+const renderTarget = document.querySelector('.jsx-app')
+render(page(), renderTarget)
diff --git a/examples/preact/src/jsx-page/page.html b/examples/preact/src/jsx-page/page.html
new file mode 100644
index 0000000..bf0074d
--- /dev/null
+++ b/examples/preact/src/jsx-page/page.html
@@ -0,0 +1,4 @@
+
+
This is an html page, with a client.jsx that mounts onto it
+
+
diff --git a/examples/type-stripping/src/README.md b/examples/type-stripping/src/README.md
index d9c4051..f922c8c 100644
--- a/examples/type-stripping/src/README.md
+++ b/examples/type-stripping/src/README.md
@@ -2,4 +2,5 @@
This is a preact example.
-[Isomorphic Component Rendering](./isomorphic/)
+- [Isomorphic Component Rendering](./isomorphic/)
+- [tsx-client]('./isomorphic/')
diff --git a/examples/type-stripping/src/tsx-page/client.tsx b/examples/type-stripping/src/tsx-page/client.tsx
new file mode 100644
index 0000000..497ef69
--- /dev/null
+++ b/examples/type-stripping/src/tsx-page/client.tsx
@@ -0,0 +1,14 @@
+import { render } from 'preact'
+
+export const page = () => {
+ return (
+
+ look ma, client side jsx!
+
+ )
+}
+
+const renderTarget = document.querySelector('.jsx-app')
+if (renderTarget) {
+ render(page(), renderTarget)
+}
diff --git a/examples/type-stripping/src/tsx-page/page.html b/examples/type-stripping/src/tsx-page/page.html
new file mode 100644
index 0000000..bf0074d
--- /dev/null
+++ b/examples/type-stripping/src/tsx-page/page.html
@@ -0,0 +1,4 @@
+
+
This is an html page, with a client.jsx that mounts onto it
+
+
diff --git a/examples/type-stripping/tsconfig.json b/examples/type-stripping/tsconfig.json
index 9aca575..c0a6886 100644
--- a/examples/type-stripping/tsconfig.json
+++ b/examples/type-stripping/tsconfig.json
@@ -5,7 +5,9 @@
"erasableSyntaxOnly": true,
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true,
- "verbatimModuleSyntax": true
+ "verbatimModuleSyntax": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "preact"
},
"include": [
"**/*",
diff --git a/lib/build-esbuild/index.js b/lib/build-esbuild/index.js
index 08e4df8..3f356cb 100644
--- a/lib/build-esbuild/index.js
+++ b/lib/build-esbuild/index.js
@@ -91,6 +91,8 @@ export async function buildEsbuild (src, dest, siteData, opts) {
metafile: true,
entryNames: '[dir]/[name]-[hash]',
chunkNames: 'chunks/[ext]/[name]-[hash]',
+ jsx: 'automatic',
+ jsxImportSource: 'preact'
}
const esbuildSettingsExtends = siteData.esbuildSettings
diff --git a/lib/identify-pages.js b/lib/identify-pages.js
index ecb214b..d2acb29 100644
--- a/lib/identify-pages.js
+++ b/lib/identify-pages.js
@@ -31,8 +31,8 @@ const jsPageDraftNames = hasTS
: ['page.draft.js', 'page.draft.mjs', 'page.draft.cjs']
const pageClientNames = hasTS
- ? ['client.ts', 'client.mts', 'client.cts', 'client.js', 'client.mjs', 'client.cjs']
- : ['client.js', 'client.mjs', 'client.cjs']
+ ? ['client.tsx', 'client.ts', 'client.mts', 'client.cts', 'client.jsx', 'client.js', 'client.mjs', 'client.cjs']
+ : ['client.jsx', 'client.js', 'client.mjs', 'client.cjs']
const pageVarsNames = hasTS
? ['page.vars.ts', 'page.vars.mts', 'page.vars.cts',
diff --git a/package.json b/package.json
index 270a66d..a75f03e 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,7 @@
"p-map": "^7.0.2",
"package-json": "^10.0.0",
"pkg-dir": "^8.0.0",
+ "preact": "^10.26.6",
"pretty": "^2.0.0",
"pretty-tree": "^1.0.0",
"read-pkg": "^9.0.1",
diff --git a/test-cases/general-features/src/blog/2025/a-blog-post-from-2025/client.tsx b/test-cases/general-features/src/blog/2025/a-blog-post-from-2025/client.tsx
new file mode 100644
index 0000000..8304912
--- /dev/null
+++ b/test-cases/general-features/src/blog/2025/a-blog-post-from-2025/client.tsx
@@ -0,0 +1,14 @@
+import { render } from 'preact'
+
+export const page = () => {
+ return (
+
+ look ma, client side jsx!
+
+ )
+}
+
+const renderTarget = document.querySelector('.tsx-app')
+if (renderTarget) {
+ render(page(), renderTarget)
+}
diff --git a/test-cases/general-features/src/blog/2025/a-blog-post-from-2025/page.ts b/test-cases/general-features/src/blog/2025/a-blog-post-from-2025/page.ts
index 305856f..d882874 100644
--- a/test-cases/general-features/src/blog/2025/a-blog-post-from-2025/page.ts
+++ b/test-cases/general-features/src/blog/2025/a-blog-post-from-2025/page.ts
@@ -1,5 +1,8 @@
export default function () {
- return `Hello world, from 2025. Also typescript`
+ return `
+ Hello world, from 2025. Also typescript
+
+ `
}
export const vars = {
diff --git a/tsconfig.json b/tsconfig.json
index 4eff7ad..f78f4c8 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,7 +1,9 @@
{
"extends": "@voxpelli/tsconfig/node20.json",
"compilerOptions": {
- "skipLibCheck": true
+ "skipLibCheck": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "preact"
},
"include": [
"lib/**/*",