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(check): erasable-typescript-only rule + ship erasableSyntaxOnly in tsconfigs
Adds compilerOptions.erasableSyntaxOnly to: the scaffold tsconfig
generator (packages/cli/lib/create.js), the blog example, the docs
site, the marketing site. TypeScript 5.8+ rejects enum, namespace
with values, constructor parameter properties, legacy decorators
with emitDecoratorMetadata, and import = require at compile time
when this flag is set. Violations surface as red squiggles in the
editor, well before they would hit Node's built-in strip-types and
trigger the (slower, sourcemap-bearing) esbuild fallback path.
Adds a new webjs check rule, erasable-typescript-only, that loads
the project's tsconfig.json and warns when erasableSyntaxOnly is
missing or set to false. Lighter than a custom TS parser because
the actual checking is delegated to tsc; this rule just verifies
the tsconfig opted in. Verified against the blog example: passes
with the flag on, flags a single violation when flipped to false.
Copy file name to clipboardExpand all lines: packages/server/src/check.js
+50Lines changed: 50 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -87,6 +87,11 @@ export const RULES = [
87
87
description:
88
88
'Only the root layout (app/layout.{js,ts}) may write a <!doctype>/<html>/<head>/<body> shell to override default <html lang>, <body class>, etc. Non-root layouts (app/<segment>/layout.{js,ts}) and pages (app/**/page.{js,ts}) must not: the framework auto-emits the wrapper around the whole composition, so a nested shell ends up nested inside <body> where browsers drop it. Triggers on any of <!doctype>, <html, <head, <body in a non-root layout or page.',
89
89
},
90
+
{
91
+
name: 'erasable-typescript-only',
92
+
description:
93
+
'Apps must opt into TypeScript\'s `erasableSyntaxOnly: true` so the compiler rejects non-erasable syntax (enum, namespace with values, constructor parameter properties, legacy decorators with emitDecoratorMetadata, import = require) at edit time. webjs strips types via Node\'s built-in `module.stripTypeScriptTypes`, which only supports erasable TypeScript and produces byte-exact position preservation (no sourcemap overhead). Files using non-erasable syntax fall back to esbuild + inline sourcemap, which is supported as a safety net for third-party deps but should not be the path your own code takes. The rule checks the project\'s tsconfig.json and warns when `erasableSyntaxOnly` is missing or set to false. Set `compilerOptions.erasableSyntaxOnly: true` in tsconfig.json to comply.',
94
+
},
90
95
];
91
96
92
97
/** Set of all known rule names for fast lookup. */
@@ -754,6 +759,51 @@ export async function checkConventions(appDir, opts) {
754
759
}
755
760
}
756
761
762
+
// --- Rule: erasable-typescript-only ---
763
+
// The dev server's primary type-stripper is Node's built-in
764
+
// module.stripTypeScriptTypes, which rejects non-erasable TS (enum,
765
+
// namespace with values, constructor parameter properties, legacy
766
+
// decorators, `import = require`). The fallback path is esbuild +
767
+
// inline sourcemap, which is a real ~3x wire-byte hit on every .ts
768
+
// request that takes it. Enforce TS-side rejection of those patterns
769
+
// via `compilerOptions.erasableSyntaxOnly: true` in tsconfig.json so
770
+
// violations surface as red squiggles in the editor before they ever
? '`compilerOptions.erasableSyntaxOnly` is `false`. The framework strips TypeScript via Node\'s built-in stripper, which only supports erasable TS. Non-erasable syntax (enum, namespace with values, constructor parameter properties, legacy decorators) falls back to esbuild + inline sourcemap on every request, costing ~3x wire bytes and losing byte-exact stack-trace positions.'
799
+
: '`compilerOptions.erasableSyntaxOnly` is not set. The framework strips TypeScript via Node\'s built-in stripper, which only supports erasable TS. Setting this flag makes the TypeScript compiler flag non-erasable syntax as a red squiggle in the editor instead of letting it silently slip through to a slower runtime fallback.',
800
+
fix:
801
+
'Set `"erasableSyntaxOnly": true` under `compilerOptions` in tsconfig.json. Replace any existing `enum` declarations with `const X = { ... } as const` plus a `type X = typeof X[keyof typeof X]` union. Replace constructor parameter properties with explicit field declarations + assignments.',
0 commit comments