You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: use esbuild for TS on both SSR + hydration; default to no build
The dev server now registers an esbuild ESM loader hook
(`module.register()`) at startup. Every server-side `.ts` import flows
through esbuild's transform — same transformer the dev server already
used for browser-bound `.ts` requests. SSR and hydration produce
equivalent JS, full TS feature support (enums, decorators, parameter
properties).
Other changes:
- esbuild moves from a hopeful scaffold devDep to a real `dependency`
of `@webjskit/server`. `loadEsbuild`'s two-step user-app lookup
is no longer needed and gone.
- Scaffold's `devDependencies` no longer lists esbuild (transitive
via @webjskit/server).
- Scaffold's `package.json` no longer ships a `build` script. `webjs
build` remains in the CLI as an opt-in production-bundle command for
users who explicitly want it; default story is no build.
- README, AGENTS.md, website hero copy, docs (typescript, ssr, getting-
started, deployment, editor-setup, routing, ai-first) updated to drop
the "Node strips types natively" framing and describe the unified
esbuild path.
Tests:
- New `test/esbuild-loader.test.js` (7 tests) covering erasable types,
enums, parameter properties, namespaces, generics, non-TS delegation,
mtime caching.
- Extended `test/dev-handler.test.js` with a non-erasable TS request
test proving the browser-bound transform handles enum + parameter
property.
- All 640 unit tests pass.
Versions:
- `@webjskit/server`: 0.1.1 → 0.2.0 (esbuild now a dep, behavior change)
- `@webjskit/cli`: 0.1.4 → 0.2.0 (server pin updated to 0.2.0)
Copy file name to clipboardExpand all lines: README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,7 +12,7 @@ TypeScript with zero build step, real SSR with Declarative Shadow DOM.
12
12
## Why webjs
13
13
14
14
-**AI-first.** Predictable file conventions, one function per file, explicit `.server.ts` boundary, `AGENTS.md` contract — designed so LLMs modify code without loading the entire codebase into context.
15
-
-**No build.**`.ts` files served directly. Node 23.6+ strips types at runtime; the dev server strips types via esbuild for the browser (~1ms/file, cached). Edit, refresh, done.
15
+
-**No build step you run.**`.ts` files served directly. The dev server transforms TypeScript via esbuild for both server-side imports (SSR) and browser-bound modules (hydration) — same transformer for both, ~1ms/file, cached by mtime. Full TS feature support (enums, decorators, parameter properties — anything esbuild handles). Edit, refresh, done.
16
16
-**Web components, light DOM by default.** Pages and components render as light DOM so global CSS and Tailwind utilities apply directly — no `::part`, no `:host`, no CSS-var plumbing. Shadow DOM is opt-in (`static shadow = true`) when you need scoped styles or real `<slot>` projection. Both modes SSR fully, no hydration runtime.
17
17
-**Tailwind CSS by default.** The scaffold ships with the Tailwind browser runtime + `@theme` design tokens. Prefer hand-written CSS? Opt out entirely — the framework works just as well with vanilla CSS when you follow the wrapper-scoping convention (`.page-<route>`, `.layout-<name>`, component-tag scoped). Full recipe in the [Styling docs](./docs/app/docs/styling/page.ts).
18
18
-**Full-stack type safety.** Import a `.server.ts` function from a component — TypeScript sees the real signature. superjson on the wire preserves `Date`, `Map`, `Set`, `BigInt`.
<h3>4. No Build Step = What You See Is What Runs</h3>
60
60
<p>Frameworks with build pipelines transform source code before it executes. The JSX you write becomes <code>React.createElement</code> calls. Your imports become webpack chunks. Your CSS modules get hashed classnames. An AI agent reading the source sees one thing; the runtime does another.</p>
61
-
<p>webjs has <strong>no build step</strong>. The <code>.ts</code> file you see is the file that runs (Node strips types; the dev server strips types for the browser). There's no intermediate representation, no generated code, no output directory. An AI agent can reason about what the code does by reading the file — because the file IS what runs.</p>
61
+
<p>webjs has <strong>no build step you run</strong>. The <code>.ts</code> file you see is the file that runs — the dev server transforms TypeScript via esbuild on import (server-side) and on request (browser-side), with the same transformer for both. There's no intermediate representation, no generated code, no output directory. An AI agent can reason about what the code does by reading the file — because the file IS what runs.</p>
62
62
63
63
<h3>5. Explicit Server Boundary</h3>
64
64
<p>The <code>.server.ts</code> extension is a visible, greppable marker that says "this code runs only on the server." An AI agent never accidentally puts a database call in a component — the naming convention prevents it. And the framework enforces it: <code>.server.ts</code> files are rewritten to RPC stubs for the browser.</p>
<p>webjs runs as a standard Node.js server. There is no static export, no serverless adapter, no edge runtime. Deploy it anywhere you can run Node 23.6+: a VPS, a container, a PaaS like Fly.io or Railway, or behind a reverse proxy on bare metal.</p>
8
+
<p>webjs runs as a standard Node.js server. There is no static export, no serverless adapter, no edge runtime. Deploy it anywhere you can run Node 20.6+: a VPS, a container, a PaaS like Fly.io or Railway, or behind a reverse proxy on bare metal.</p>
9
9
10
10
<h2>Dev vs Prod</h2>
11
11
<p>webjs has two modes, controlled by the CLI command:</p>
<li>Server-only files (<code>.server.ts</code>, <code>route.ts</code>, <code>middleware.ts</code>) are never included.</li>
32
32
<li>In production, SSR pages load <code>/__webjs/bundle.js</code> instead of individual modules, collapsing dozens of requests into one.</li>
33
33
</ul>
34
-
<p>esbuild is required for the build step. Install it as a dev dependency:</p>
35
-
<pre>npm install -D esbuild</pre>
34
+
<p>esbuild is bundled with <code>@webjskit/server</code> — no separate install needed. <code>webjs build</code> runs immediately after a fresh scaffold.</p>
36
35
<p>For small apps (under ~20 components), unbundled serving in production is perfectly viable. The build step is an optimisation, not a requirement.</p>
<li>Use <code>node:23-slim</code>(not <code>node:23-alpine</code>) for native TypeScript type stripping support.</li>
210
-
<li>Run <code>npm ci --production</code> to skip dev dependencies. If you run <code>webjs build</code> in the container, install esbuild first (move it to regular <code>dependencies</code>or use a multi-stage build).</li>
208
+
<li><code>node:slim</code>works fine — esbuild ships its own native binary, so no extra system packages are required.</li>
209
+
<li>Run <code>npm ci --production</code> to skip dev dependencies. esbuild is a dependency of <code>@webjskit/server</code>, so it's always available for <code>webjs build</code>without extra setup.</li>
211
210
<li>Set <code>HEALTHCHECK</code> to the built-in health endpoint for container orchestrators.</li>
212
211
<li>For apps with Prisma, add <code>RUN npx prisma generate</code> before the CMD.</li>
Copy file name to clipboardExpand all lines: docs/app/docs/editor-setup/page.ts
+2-2Lines changed: 2 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -16,7 +16,7 @@ export default function EditorSetup() {
16
16
17
17
<h2>Prerequisites</h2>
18
18
<ul>
19
-
<li><strong>Node 23.6+</strong> for native TypeScript type-stripping at runtime.</li>
19
+
<li><strong>Node 20.6+</strong> for the esbuild loader hook the dev server registers at startup.</li>
20
20
<li><strong>TypeScript 5.6+</strong> as a dev dependency (<code>npm i -D typescript</code>). The framework itself has no TS dependency; you only need it for editor intellisense.</li>
21
21
<li>A <code>tsconfig.json</code> in your app. The scaffold generates one.</li>
22
22
</ul>
@@ -40,7 +40,7 @@ export default function EditorSetup() {
40
40
<ul>
41
41
<li><code>moduleResolution: "NodeNext"</code> — required for the framework's <code>exports</code> map to resolve correctly.</li>
42
42
<li><code>allowImportingTsExtensions: true</code> — lets you write <code>import { x } from './foo.ts'</code>, matching how webjs serves them.</li>
Copy file name to clipboardExpand all lines: docs/app/docs/getting-started/page.ts
+2-3Lines changed: 2 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -9,7 +9,7 @@ export default function GettingStarted() {
9
9
10
10
<h2>Prerequisites</h2>
11
11
<ul>
12
-
<li><strong>Node.js 23.6+</strong> — required for native TypeScript type-stripping. Node 20+ works if you stick to plain JavaScript.</li>
12
+
<li><strong>Node.js 20.6+</strong> — needed for <code>module.register()</code>, the API webjs uses to install its esbuild TypeScript loader at startup.</li>
13
13
<li><strong>npm</strong> (or any package manager).</li>
<li><strong>Server-side:</strong> Node 23.6+ strips TypeScript types at runtime. Your <code>.ts</code> pages and server actions run directly.</li>
122
-
<li><strong>Client-side:</strong> The dev server transforms <code>.ts</code> files via esbuild (~1ms/file, cached by mtime) before serving to the browser.</li>
121
+
<li><strong>TypeScript:</strong> The dev server registers an esbuild loader hook at startup. Every <code>.ts</code> import — server-side or browser-fetched — flows through esbuild's <code>transform()</code>. Same transformer for SSR and hydration, ~1ms/file, cached by mtime.</li>
123
122
<li><strong>SSR:</strong> Pages are rendered to HTML strings on the server. Light-DOM components serialize as plain children with a <code><!--webjs-hydrate--></code> marker; shadow-DOM components (opt-in) emit Declarative Shadow DOM so scoped styles paint before JS loads.</li>
124
123
<li><strong>Hydration:</strong> When JS loads, custom elements upgrade and become interactive. The fine-grained renderer preserves focus, cursor position, and form state across state updates.</li>
Copy file name to clipboardExpand all lines: docs/app/docs/ssr/page.ts
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -25,7 +25,7 @@ export default async function Home() {
25
25
\`;
26
26
}</pre>
27
27
28
-
<p>Because Node 23.6+ strips TypeScript types natively, no compilation step is needed. The file above runs directly on the server.</p>
28
+
<p>The dev server's esbuild loader hook transforms TypeScript on import, so the file above runs directly on the server with no manual compilation step.</p>
29
29
30
30
<h2>The SSR Pipeline</h2>
31
31
<p>When the server receives a GET request for a page URL, the pipeline runs in this order:</p>
0 commit comments