Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
d5d5998
feat(server): tighten bare-import scanner to exclude server-only file…
vivek7405 May 23, 2026
4d4df66
feat(check): add no-non-erasable-typescript rule
vivek7405 May 23, 2026
bb33d70
feat(server)!: replace esbuild-on-demand vendor with jspm.io direct (…
vivek7405 May 23, 2026
c1b0788
feat(server,cli): file-based pin command for Rails-style vendor persi…
vivek7405 May 24, 2026
baec4dd
test(server): coverage for file-based pin commands (pinAll, unpin, li…
vivek7405 May 24, 2026
c1416a2
docs(top-level): describe jspm.io vendor pipeline + webjs vendor pin …
vivek7405 May 25, 2026
7b43df6
docs(readme): vendor pipeline via jspm.io + webjs vendor pin commands
vivek7405 May 25, 2026
705f686
docs(server): describe jspm.io vendor resolution + check.js rules
vivek7405 May 25, 2026
22d7e0c
docs(docs-site): describe jspm.io vendor architecture in no-build + d…
vivek7405 May 25, 2026
af2f192
docs(deployment): update vendor URL pattern + cache headers for jspm.…
vivek7405 May 25, 2026
c1e64d9
test(e2e): vendor pin/unpin/list CLI integration coverage
vivek7405 May 25, 2026
245ebe7
docs(server): update importmap.js header comment to reflect jspm.io flow
vivek7405 May 25, 2026
8a82197
feat(server): support subpath imports (dayjs/plugin/utc) in vendor pi…
vivek7405 May 25, 2026
896bcd4
fix(scaffold,server): unignore .webjs/vendor + correct listPinned sub…
vivek7405 May 25, 2026
2be4d09
fix(gitignore): unignore .webjs/vendor in repo root + examples/blog
vivek7405 May 25, 2026
f2ebaaf
fix(test): dev-handler vendor tests accurately reflect --download beh…
vivek7405 May 25, 2026
447c413
fix(dockerignore): unignore .webjs/vendor so committed pin files reac…
vivek7405 May 25, 2026
4bf2071
fix(server): jspmGenerate caches the in-flight Promise, not just the …
vivek7405 May 25, 2026
d67a1bd
docs(deployment): add CSP guidance for jspm.io vendor URLs
vivek7405 May 25, 2026
6f66ae2
docs(scaffold): add 'NPM packages (vendor pipeline)' section to AGENT…
vivek7405 May 25, 2026
ea1406d
fix(gitignore,dockerignore): correct exception pattern for .webjs/vendor
vivek7405 May 25, 2026
5e8e42e
Add gitignore-vendor-not-ignored lint rule
vivek7405 May 25, 2026
16b8db2
Strengthen .webjs/vendor gitignore warnings
vivek7405 May 25, 2026
ecaf507
Exclude dot-dirs and *.config.* files from vendor scanner
vivek7405 May 25, 2026
ee13f5a
Per-package jspm.io resolution to isolate bad deps
vivek7405 May 25, 2026
c3ce2ae
Thread CSP nonce into script type=importmap tag
vivek7405 May 25, 2026
f9fd715
Add crossorigin=anonymous to cross-origin modulepreload links
vivek7405 May 25, 2026
844583e
Add SRI integrity hashes for vendor packages
vivek7405 May 25, 2026
9c69fff
Test SRI integrity flow + guard against clobbering real node_modules
vivek7405 May 25, 2026
5f2b1d9
Drop esbuild fallback from runtime TS stripper
vivek7405 May 25, 2026
2436866
Replace esbuildPlugin with a node:module strip-types WTR plugin
vivek7405 May 25, 2026
49c5f9d
Replace esbuildPlugin in blog-e2e WTR config with strip-types plugin
vivek7405 May 25, 2026
cf7413f
Update docs prose: remove esbuild fallback references
vivek7405 May 25, 2026
c4668c4
Update lint rule + scaffold prose: esbuild fallback is gone
vivek7405 May 25, 2026
90fb326
Update remaining esbuild prose: scaffold CONVENTIONS + blog example +…
vivek7405 May 25, 2026
856aefa
Update top-level docs: esbuild fallback gone + vendor pipeline rewrite
vivek7405 May 25, 2026
7ffdc09
Catch missed esbuild prose in README + no-build docs
vivek7405 May 25, 2026
d453e3e
flattenScope:true + nonce on modulepreload (Rails+Turbo parity)
vivek7405 May 25, 2026
6bae4f5
Propagate CSP nonce to client-router dynamic scripts (Turbo pattern)
vivek7405 May 25, 2026
f375af8
Hard-reload on importmap mismatch + nonce on all head clones
vivek7405 May 26, 2026
2146fdd
Refuse to write empty pin file when every jspm.io install fails
vivek7405 May 26, 2026
f8068b2
Edge-case tests for CSP nonce + SRI behaviors
vivek7405 May 26, 2026
8033165
Escape `</script>` and U+2028/U+2029 in JSON interpolated into script…
vivek7405 May 26, 2026
877b8ec
Defense-in-depth: serveDownloadedBundle + strip-ts error sanitization
vivek7405 May 26, 2026
7fd1e57
Validate pin file contents + delete empty pin on unpin
vivek7405 May 26, 2026
8068873
Stable key order in importmap output (prevents spurious reloads)
vivek7405 May 26, 2026
87d0242
Serialize dev-server rebuilds to prevent stale-overwrite race
vivek7405 May 26, 2026
14bc2d9
Extend gitignore-vendor-not-ignored to also probe bundle files
vivek7405 May 26, 2026
ad8fabc
Thread CSP nonce through error + 404 response paths
vivek7405 May 26, 2026
5d724b0
Scoped-package version regex + scanner stress tests + jspm failure-mo…
vivek7405 May 26, 2026
5b4a037
Expose cspNonce() helper so user code can sign inline scripts
vivek7405 May 26, 2026
6d53f70
Move cspNonce to @webjsdev/core for isomorphic import in layouts
vivek7405 May 26, 2026
816b9f8
Apply cspNonce() to scaffold layout's theme-detection script
vivek7405 May 26, 2026
f425288
Apply cspNonce() to all 4 in-repo app layouts
vivek7405 May 26, 2026
6512d9d
Suspense resolution <script> tags carry CSP nonce
vivek7405 May 26, 2026
dc119d2
Reject javascript:/data: URL schemes + control-char keys in pin file
vivek7405 May 26, 2026
4cc12c8
Vendor URL handler rejects non-GET/HEAD methods with 405
vivek7405 May 26, 2026
05308c9
Containment check on /public/ branch (defense vs encoded path traversal)
vivek7405 May 26, 2026
60cda7f
fix(ws): auto-register upgraded clients with broadcast registry
vivek7405 May 26, 2026
2aa6726
fix(check): point no-json-data-files at lib/prisma.server.ts
vivek7405 May 26, 2026
4c4eed1
fix(cache): tighten memoryStore TTL parsing + bump LRU on increment
vivek7405 May 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@
.github
node_modules
**/node_modules
**/.webjs
# `.webjs/` is ignored EXCEPT for `.webjs/vendor/`. The vendor
# subdirectory holds the committed importmap manifest (.webjs/vendor/
# importmap.json) and optionally downloaded bundle bytes (from
# `webjs vendor pin --download`). Both must reach the production
# image so the server doesn't need api.jspm.io reachable at boot.
# DO NOT collapse to `**/.webjs`: parent exclusion blocks child
# negations and the vendor files would silently never reach the
# image. Mirrors the .gitignore pattern enforced by the
# `gitignore-vendor-not-ignored` lint rule.
**/.webjs/*
!**/.webjs/vendor/
!**/.webjs/vendor/**
**/dist
**/build
**/out
Expand Down
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ dist/
build/
out/
.cache/
.webjs/
# `.webjs/vendor/` is the EXCEPTION: holds the committed importmap
# manifest + optional downloaded bundles for `webjs vendor pin`. See
# packages/cli/templates/.gitignore for the full rationale.
# DO NOT collapse to `.webjs/`: parent exclusion blocks child
# negations and silently breaks `webjs vendor pin`. The
# `gitignore-vendor-not-ignored` lint rule guards this.
.webjs/*
!.webjs/vendor/
!.webjs/vendor/**

# generated Tailwind CSS - built from public/input.css via `npm run dev` / `npm run start`
**/public/tailwind.css
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ For partial-swap NOT tied to a folder layout, wrap in `<webjs-frame id="...">`.
7. **Light-DOM components with custom CSS MUST prefix every class selector with their tag name.** Tailwind utilities are unique by construction, so prefer them.
8. **Non-root layouts and pages MUST NOT** write `<!doctype>` / `<html>` / `<head>` / `<body>`. Only the root layout may.
9. **No backtick characters inside `html\`...\`` template bodies**, even inside CSS / HTML comments. A nested backtick closes the literal at JS-parse time and 500s in prod.
10. **TypeScript must be erasable.** Set `compilerOptions.erasableSyntaxOnly: true`. No `enum`, no `namespace` with values, no constructor parameter properties, no legacy decorators with `emitDecoratorMetadata`, no `import = require`. The framework strips types via Node 24+'s built-in `module.stripTypeScriptTypes` (position-preserving, no sourcemap). If you disable the flag and use non-erasable syntax, the dev server falls back to esbuild on those files (~3x wire bytes, inline sourcemap). The `erasable-typescript-only` check enforces the flag. See `agent-docs/typescript.md` for erasable equivalents.
10. **TypeScript must be erasable.** Set `compilerOptions.erasableSyntaxOnly: true`. No `enum`, no `namespace` with values, no constructor parameter properties, no legacy decorators with `emitDecoratorMetadata`, no `import = require`. The framework strips types via Node 24+'s built-in `module.stripTypeScriptTypes` (position-preserving, no sourcemap). If you disable the flag and use non-erasable syntax, the dev server fails at strip time and returns a 500 pointing at the `no-non-erasable-typescript` lint rule. webjs is buildless end-to-end and has no bundler fallback. Two lint rules enforce this: `erasable-typescript-only` (checks the tsconfig flag) and `no-non-erasable-typescript` (scans source for the four offending patterns even if the flag is off). See `agent-docs/typescript.md` for erasable equivalents.

11. **No em-dashes (U+2014), no hyphen or semicolon used as pause-punctuation, and no colon attached to a code-shaped LHS.** Banned glyphs as pause punctuation: U+2014; a plain hyphen surrounded by spaces between word characters; a semicolon surrounded by spaces between word characters. Banned colon attachments (prefer verb-led rephrasings): `xyz()` followed by colon-then-prose; a custom-element tag like `<my-tag>` followed by colon-then-prose; `[expr]` subscript followed by colon-then-prose; markdown definition lists with `<code>foo()</code>` followed by colon-then-prose. Prefer a period, comma, colon on a plain-noun LHS only, parentheses, or a restructured sentence. Plain hyphens stay fine in natural roles (compound words, CLI flags, filenames, ranges). Semicolons stay fine inside code. Colons stay fine in TS / JSON / CSS syntax. Enforced for Claude Code via `.claude/hooks/block-prose-punctuation.sh` (PreToolUse on Write / Edit / MultiEdit / NotebookEdit / Bash). The hook scans only NEW content; you can still edit a line that already contains a banned glyph to remove it.

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ TypeScript with zero build step, real SSR with Declarative Shadow DOM.
## Why webjs

- **AI-first.** Predictable file conventions, one function per file, an explicit `.server.ts` boundary, and an `AGENTS.md` contract. The whole design lets LLMs modify code without loading the entire codebase into context.
- **No build step you run.** `.ts` files served directly. Node 24+ is the minimum runtime, and the dev server strips types via Node's built-in `module.stripTypeScriptTypes` (position-preserving, no sourcemap, near-zero overhead). TypeScript must be erasable. Non-erasable constructs (enums, value-carrying namespaces, constructor parameter properties, legacy decorators with `emitDecoratorMetadata`) trigger an esbuild fallback for those files (~3x wire bytes, inline sourcemap). Edit, refresh, done.
- **No build step you run.** `.ts` files served directly. Node 24+ is the minimum runtime, and the dev server strips types via Node's built-in `module.stripTypeScriptTypes` (position-preserving, no sourcemap, near-zero overhead). TypeScript must be erasable. Non-erasable constructs (enums, value-carrying namespaces, constructor parameter properties, legacy decorators with `emitDecoratorMetadata`) fail at strip time with a 500 pointing at the `no-non-erasable-typescript` lint rule, since webjs is buildless end-to-end with no bundler fallback. Edit, refresh, done.
- **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 third-party-embed isolation. `<slot>` projection (named slots, fallback content, `assignedNodes` / `slotchange`) works identically in both modes. Both modes SSR fully, no hydration runtime.
- **Progressive enhancement, built in.** Pages *and* components are SSR'd to real HTML. Every web component's `render()` runs on the server, so its initial markup is in the response before any script loads. Content reads, links navigate, forms submit (server actions are plain HTML POSTs), and display-only custom elements look right, all without JavaScript. JS is opt-in *per interactive behavior*, not per component: a counter renders as "0" without JS, and only the +/- click handling needs scripts. The HTML is the floor, and the client router and `@click` / signal interactivity are layered on top.
- **Tailwind CSS by default.** The scaffold ships with the Tailwind browser runtime + `@theme` design tokens. Prefer hand-written CSS? Opt out entirely, and 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).
Expand Down Expand Up @@ -262,7 +262,7 @@ Pre-1.0. Current packages: `@webjsdev/core` 0.7.1, `@webjsdev/server` 0.7.2, `@w
- **Core:** Signals (`signal`, `computed`, `effect`, `batch`, TC39 Stage 1 shape) as the default state primitive, with WebComponent's built-in SignalWatcher auto-tracking `.get()` reads inside `render()`. Reactive properties via `static properties` reserved for HTML attribute round-trip (`declare`-pattern enforced via the `reactive-props-use-declare` rule). Full lit-API parity: ReactiveController hooks (`hostConnected`, `hostDisconnected`, `hostUpdate`, `hostUpdated`) and lifecycle (`shouldUpdate`, `willUpdate`, `update`, `updated`, `firstUpdated`, `updateComplete`), 12 directives (`repeat`, `unsafeHTML`, `live`, `keyed`, `guard`, `templateContent`, `ref` + `createRef`, `cache`, `until`, `asyncAppend`, `asyncReplace`, `watch`). SSR with DSD (opt-in) + light-DOM hydration (default), light-DOM `<slot>` projection (framework-driven, same API as shadow DOM), fine-grained client renderer, `Suspense()`, client router with `composedPath()` for shadow DOM, mixed-attribute interpolation, MutationObserver upgrade safety net.
- **Data:** Server actions with webjs's built-in serializer (`Date`, `Map`, `Set`, `BigInt`, `TypedArray`, `Blob`, `File`, `FormData`, reference cycles all survive the wire). Two-marker server-file convention: `.server.{js,ts}` for path-level source-protection (browser imports get a throw-at-load stub), `'use server'` for RPC registration (file is also browser-callable). `expose()` for REST with optional `validate` hook. `json()` + `richFetch()` for content-negotiated APIs. `cache()` for server-side query caching with TTL + `invalidate()`. `WEBJS_PUBLIC_*` env vars injected into `window.process.env` at SSR (no build step, no transform).
- **Server:** File router with `page.ts`, `layout.ts`, `route.ts`, `error.ts`, `loading.ts`, `not-found.ts`, `middleware.ts`, metadata routes (`sitemap`, `robots`, `manifest`, `icon`, `opengraph-image`), per-segment middleware, `rateLimit()`, WebSockets (`WS` export + `connectWS()` + `broadcast()`), CSRF, gzip / brotli compression, HTTP/2, 103 Early Hints, modulepreload hints, health probes, graceful shutdown on `SIGTERM`, `Session` class with `SessionStorage` (cookie or store-backed), NextAuth-style `createAuth()` (Credentials, Google, GitHub), single pluggable cache store (in-memory by default, swap to Redis with one `setStore()` call shared by auth, sessions, caching, and rate limiting).
- **DX:** Node 24+ minimum runtime, with the dev server stripping TypeScript via Node's built-in `module.stripTypeScriptTypes` (zero build, position-preserving, no sourcemap). esbuild stays as a per-file fallback for non-erasable TS (enums, value-carrying namespaces, constructor parameter properties, legacy decorators) and for transitive `node_modules` vendor bundling. `webjs check` lint covers `use-server-needs-extension`, `no-server-env-in-components`, `reactive-props-use-declare`, `erasable-typescript-only`, `shell-in-non-root-layout`, `no-json-data-files`, and more (run `webjs check --rules` to enumerate). `AGENTS.md` contract + `CLAUDE.md` + per-tool agent configs (`.cursorrules`, `.windsurfrules`, `.github/copilot-instructions.md`, `.claude/settings.json` PreToolUse hook guarding edits on `main`). Live reload in dev (chokidar + SSE). `@webjsdev/ts-plugin` editor-only piece bundles `ts-lit-plugin` and layers webjs-aware intelligence on top: type-checked `` html`…` `` templates, custom-element go-to-definition, attribute auto-complete from `static properties`, silenced "Unknown tag" diagnostics for `Class.register('tag-name')` elements, all gated by the file's import graph. Not required for the framework to run.
- **DX:** Node 24+ minimum runtime, with the dev server stripping TypeScript via Node's built-in `module.stripTypeScriptTypes` (zero build, position-preserving, no sourcemap). Non-erasable TS (enums, value-carrying namespaces, constructor parameter properties, legacy decorators) fails with a 500 pointing at the `no-non-erasable-typescript` lint rule. webjs is buildless end-to-end and has no bundler fallback. Vendor (`node_modules`) packages resolve through importmap to jspm.io URLs at runtime; the webjs server doesn't bundle them. `webjs vendor pin` writes resolved URLs to `.webjs/vendor/importmap.json` for deterministic deploys; `webjs vendor pin --download` additionally vendors bundle bytes for offline-capable production. `webjs check` lint covers `use-server-needs-extension`, `no-server-env-in-components`, `reactive-props-use-declare`, `erasable-typescript-only`, `no-non-erasable-typescript`, `shell-in-non-root-layout`, `no-json-data-files`, and more (run `webjs check --rules` to enumerate). `AGENTS.md` contract + `CLAUDE.md` + per-tool agent configs (`.cursorrules`, `.windsurfrules`, `.github/copilot-instructions.md`, `.claude/settings.json` PreToolUse hook guarding edits on `main`). Live reload in dev (chokidar + SSE). `@webjsdev/ts-plugin` editor-only piece bundles `ts-lit-plugin` and layers webjs-aware intelligence on top: type-checked `` html`…` `` templates, custom-element go-to-definition, attribute auto-complete from `static properties`, silenced "Unknown tag" diagnostics for `Class.register('tag-name')` elements, all gated by the file's import graph. Not required for the framework to run.
- **Release:** Per-package per-version changelog under `changelog/<pkg>/<version>.md`, auto-generated on the same commit that bumps a `package.json` `version` field (universal pre-commit hook). The `.github/workflows/release.yml` workflow watches for new changelog files on `main` and dual-publishes to npm (`npm publish --workspace=@webjsdev/<pkg>`) and GitHub Releases (`gh release create <pkg>@<version>`), both idempotent so re-runs pick up where they left off. Free for public repos via `NPM_TOKEN` + the auto-provisioned `GITHUB_TOKEN`.

## License
Expand Down
14 changes: 10 additions & 4 deletions agent-docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,16 @@ Five stacked zero-build optimizations:
`IntersectionObserver` (200px root margin). The SSR-rendered DSD content
is visible immediately. `static hydrate = 'visible'` further defers
`connectedCallback` activation. Ideal for below-the-fold widgets.
5. **Auto-vendor bundling (Vite-style optimizeDeps).** At startup the server
scans client-reachable source for bare npm import specifiers. Each
package is bundled into a single ESM file via esbuild and served at
`/__webjs/vendor/<pkg>.js`. The import map is populated automatically.
5. **Auto-vendor via jspm.io (Rails 7 + importmap-rails posture).** At
startup the server scans client-reachable source for bare npm import
specifiers. Each `pkg@version` is resolved through `api.jspm.io/generate`
to a CDN URL (`https://ga.jspm.io/npm:<pkg>@<version>/...`) and added
to the import map; the browser fetches each package directly from
the CDN. `webjs vendor pin` commits the resolved URLs + SHA-384
integrity hashes to `.webjs/vendor/importmap.json` for reproducible
deploys; `webjs vendor pin --download` also caches the bundle bytes
locally under `.webjs/vendor/<pkg>@<version>.js` for air-gapped /
strict-CSP deployments. No bundler runs at any point.

## No-build production model

Expand Down
16 changes: 8 additions & 8 deletions agent-docs/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ set. Run `webjs check` to confirm.

If a third-party package ships `.ts` source using non-erasable
syntax (rare; most npm packages publish compiled `.js`), the dev
server transparently falls back to `esbuild.transform` for those
specific files. The fallback emits an inline sourcemap so DevTools
can still resolve source positions. Your own code never takes this
path as long as `erasableSyntaxOnly` is set.
server fails at strip time and returns a 500 naming the file and
pointing at the `no-non-erasable-typescript` lint rule. webjs is
buildless end-to-end and has no bundler fallback. Your own code
never hits this as long as `erasableSyntaxOnly` is set.

If you manually turn `erasableSyntaxOnly` off and write non-erasable
syntax in your own code, the same fallback fires: those files cost
~3x wire bytes (sourcemap overhead) and lose strict position
preservation. The convention check warns about this.
syntax in your own code, the dev server fails the same way. The
`erasable-typescript-only` convention check warns when the flag is
off so you catch the configuration drift before runtime.

## Import convention

Expand Down Expand Up @@ -97,7 +97,7 @@ import { formatPost } from '../utils/slugify.ts'; // TS file
The `erasableSyntaxOnly: true` line is the non-negotiable one. It
aligns the TypeScript compiler's accepted syntax with what Node's
strip-types accepts, so violations surface as editor diagnostics
instead of runtime fallback to esbuild.
instead of a runtime 500.

## Full-stack type safety

Expand Down
15 changes: 12 additions & 3 deletions docs/app/docs/deployment/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,16 @@ npm run start -- --port 8080</pre>
<p>In production mode, webjs automatically negotiates <code>Accept-Encoding</code> and compresses responses with Brotli (quality 4) or Gzip (level 6). Compression applies to text-based content types: HTML, JavaScript, JSON, CSS, SVG, XML. Binary assets (images, fonts) are served uncompressed.</p>

<h3>ETags and Cache Headers</h3>
<p>Static files are served with a SHA-1 ETag and a 1-hour <code>max-age</code>. Auto-vendored npm packages at <code>/__webjs/vendor/&lt;pkg&gt;.js</code> are served with <code>max-age=31536000, immutable</code> since their content is addressed by hash. In dev, all files use <code>Cache-Control: no-cache</code>.</p>
<p>Static files are served with a SHA-1 ETag and a 1-hour <code>max-age</code>. Vendor npm packages resolve through importmap to jspm.io URLs (default) or to local <code>/__webjs/vendor/&lt;pkg&gt;@&lt;version&gt;.js</code> paths (after <code>webjs vendor pin --download</code>). Direct jspm.io URLs use jspm.io's own immutable headers; locally-served <code>--download</code> bundles use <code>max-age=31536000, immutable</code>. In dev, all files use <code>Cache-Control: no-cache</code>.</p>

<h3>Content Security Policy (CSP) and vendor packages</h3>
<p>The default vendor mode serves bundles from <code>https://ga.jspm.io</code> (the jspm.io CDN). If your app sets a strict <code>Content-Security-Policy</code> header with <code>script-src 'self'</code>, the browser blocks the jspm.io script and vendor imports fail to load.</p>
<p>Two ways to handle this:</p>
<ol>
<li><strong>Allow jspm.io in CSP</strong>: add <code>https://ga.jspm.io</code> to your <code>script-src</code> directive. Example: <code>script-src 'self' https://ga.jspm.io</code>. Browsers fetch bundles from jspm.io's CDN. Same-origin-only consumers (compliance-locked, air-gapped) cannot use this mode.</li>
<li><strong>Switch to <code>--download</code> mode</strong>: run <code>webjs vendor pin --download</code> at deploy-prep time and commit the resulting <code>.webjs/vendor/&lt;pkg&gt;@&lt;version&gt;.js</code> bundle files. The importmap then points at local <code>/__webjs/vendor/</code> paths served by your own origin. <code>script-src 'self'</code> alone is sufficient; no third-party allowlist needed. Suitable for compliance-locked, air-gapped, or strictest-CSP environments.</li>
</ol>
<p>Pick the mode that matches your security posture. The choice is per-deploy, not per-package: either everything goes through jspm.io or everything is locally vendored. Mixing modes per-package is not supported.</p>

<h3>Graceful Shutdown</h3>
<p>On <code>SIGINT</code> or <code>SIGTERM</code>, webjs:</p>
Expand Down Expand Up @@ -190,8 +199,8 @@ HEALTHCHECK CMD curl -f http://localhost:8080/__webjs/health || exit 1
CMD ["npx", "webjs", "start"]</pre>
<p>Tips:</p>
<ul>
<li><code>node:slim</code> works fine. The primary TypeScript stripper is Node 24+'s built-in <code>module.stripTypeScriptTypes</code>, so no extra system packages are needed for the common case.</li>
<li><code>npm ci --omit=dev</code> skips dev dependencies. <code>@webjsdev/server</code> is a runtime dependency, which keeps the esbuild fallback available for the rare third-party file that uses non-erasable TypeScript syntax. See <a href="/docs/no-build">No-Build Model</a> for when the fallback kicks in.</li>
<li><code>node:slim</code> works fine. webjs strips TypeScript via Node 24+'s built-in <code>module.stripTypeScriptTypes</code>, so no extra system packages are needed.</li>
<li><code>npm ci --omit=dev</code> skips dev dependencies. <code>@webjsdev/server</code> is a runtime dependency. webjs is buildless end-to-end: there is no bundler or transpiler at deploy time.</li>
<li>Set <code>HEALTHCHECK</code> to the built-in health endpoint for container orchestrators.</li>
<li>For apps with Prisma, add <code>RUN npx prisma generate</code> before the CMD.</li>
<li>Layer-cache deps separately: copy <code>package.json</code> + <code>package-lock.json</code> and <code>npm ci</code> before copying the rest of the source, so application edits don't bust the deps layer.</li>
Expand Down
Loading