@webjsdev/server 0.8.91 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/index.d.ts +461 -102
- package/index.js +9 -4
- package/package.json +3 -3
- package/src/action-flash.js +185 -0
- package/src/action-input.js +208 -0
- package/src/action-params.js +156 -0
- package/src/action-route.js +8 -9
- package/src/action-seed.js +44 -5
- package/src/actions.js +112 -15
- package/src/api.js +67 -10
- package/src/app-layout.js +133 -0
- package/src/asset-hash.js +82 -49
- package/src/auth.js +612 -108
- package/src/browser-entries.js +2 -17
- package/src/build-epoch.js +1 -1
- package/src/cache-tags.js +5 -1
- package/src/check/helpers.js +85 -0
- package/src/check/rules-actions.js +37 -1
- package/src/check/rules-components.js +133 -28
- package/src/check/rules-imports.js +175 -4
- package/src/check/rules-route-modules.js +200 -0
- package/src/check/rules-routes.js +55 -0
- package/src/check/rules-routing.js +143 -95
- package/src/check/rules-typescript.js +24 -2
- package/src/check/rules.js +109 -9
- package/src/check/runner-support.js +53 -171
- package/src/check/runner.js +24 -6
- package/src/client-signals.js +171 -0
- package/src/component-scanner.js +22 -16
- package/src/context.js +152 -16
- package/src/cookie-store.js +262 -0
- package/src/core-exports.js +190 -0
- package/src/dev/bun-app-source.js +95 -5
- package/src/dev/config.js +7 -51
- package/src/dev/dev-service-worker.js +48 -0
- package/src/dev/dev-status.js +122 -0
- package/src/dev/esm-hot-hooks.js +373 -0
- package/src/dev/handler.js +632 -155
- package/src/dev/helpers.js +127 -96
- package/src/dev/hot-host.js +33 -6
- package/src/dev/poke-state.js +110 -0
- package/src/dev/serve.js +191 -133
- package/src/dev/server.js +109 -37
- package/src/dev/specifier-versions.js +125 -0
- package/src/dev/watch-ignore.js +49 -4
- package/src/dev/watch-poll.js +78 -0
- package/src/dev/watch-tree.js +59 -22
- package/src/dev-classify.js +59 -26
- package/src/dev-error.js +104 -3
- package/src/dev-hot-classify.js +291 -0
- package/src/dev-hot-client.js +245 -0
- package/src/dev-import.js +22 -5
- package/src/dev-overlay.js +26 -0
- package/src/dev-ready-probe.js +69 -0
- package/src/dev-regenerate.js +322 -78
- package/src/dev-reload-hold.js +49 -20
- package/src/dev-reload-reason.js +61 -0
- package/src/dev-reload-worker.js +62 -13
- package/src/dev-secret.js +73 -0
- package/src/dev-source-locations-hook.js +4 -1
- package/src/dev-source-locations.js +33 -2
- package/src/dev-styles.js +64 -6
- package/src/dev.js +0 -1
- package/src/env-files.js +124 -0
- package/src/file-storage.js +2 -2
- package/src/foreign-imports.js +228 -0
- package/src/form-action-identity.js +52 -25
- package/src/form-dispatch.js +150 -69
- package/src/html-cache.js +35 -4
- package/src/importmap.js +2 -2
- package/src/instrumentation.js +59 -3
- package/src/js-scan.js +5 -4
- package/src/jwe.js +155 -0
- package/src/lazy-components.js +166 -0
- package/src/listener-core.js +4 -2
- package/src/lit-hints.js +172 -0
- package/src/middleware-load.js +248 -0
- package/src/module-graph.js +73 -47
- package/src/module-side-effect.js +284 -0
- package/src/public-files.js +73 -0
- package/src/redirect-location.js +19 -0
- package/src/redirects.js +9 -1
- package/src/route-frontier.js +151 -0
- package/src/route-report.js +130 -0
- package/src/router.js +120 -13
- package/src/session.js +32 -10
- package/src/ssr/client-router-flag.js +1 -1
- package/src/ssr/document.js +49 -17
- package/src/ssr/head.js +228 -25
- package/src/ssr/preloads.js +31 -45
- package/src/ssr/render.js +243 -55
- package/src/ssr/responses.js +51 -4
- package/src/ssr/shipped-modules.js +52 -16
- package/src/testing.d.ts +31 -2
- package/src/testing.js +85 -17
- package/src/thenable-params.js +26 -0
- package/src/vendor/jspm.js +12 -1
- package/src/vendor/pins.js +9 -8
- package/src/vendor/scanner.js +14 -9
- package/src/vendor.js +5 -3
- package/webjs-config.schema.json +3 -8
- package/src/component-elision.js +0 -1852
- package/src/elision-differential.js +0 -101
- package/src/elision-report.js +0 -193
package/src/check/rules.js
CHANGED
|
@@ -29,7 +29,22 @@ export const RULES = [
|
|
|
29
29
|
{
|
|
30
30
|
name: 'no-server-env-in-components',
|
|
31
31
|
description:
|
|
32
|
-
'
|
|
32
|
+
'Code the browser runs must not read non-public environment variables: every component file (under components/ or modules/*/components/) and every module the build ships to the browser (a util a component imports). process.env.X is allowed when X starts with WEBJS_PUBLIC_ (exposed to the browser by the SSR shim) or equals NODE_ENV. Any other read leaks the server-side value into the SSR\'d HTML, then reads as undefined after hydration. Next.js\'s NEXT_PUBLIC_ prefix gets a targeted fix (rename it to WEBJS_PUBLIC_). Read server-only env vars in a page function, server action, or middleware (which never reach the browser as source) and pass derived values to the component as attributes. Page, layout and boundary modules are not scanned: their functions run on the server only.',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'no-next-runtime-guard',
|
|
36
|
+
description:
|
|
37
|
+
'Flags `process.env.NEXT_RUNTIME`, which only Next.js sets (to `nodejs` or `edge`). Under WebJs it is undefined, so the usual `if (process.env.NEXT_RUNTIME === \'nodejs\')` guard around an instrumentation `register()` silently never runs. Drop the guard (WebJs runs `register()` once, server-side only), and use `process.versions.bun` / `process.versions.node` to tell the runtimes apart.',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'no-next-url',
|
|
41
|
+
description:
|
|
42
|
+
'Flags `request.nextUrl` (as `req.nextUrl` / `request.nextUrl`, or any `.nextUrl` in a middleware or proxy file). It is Next.js\'s extended URL; WebJs passes middleware and route handlers the platform Request, so the property throws in dev. Use `new URL(request.url)`.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'no-inline-use-server',
|
|
46
|
+
description:
|
|
47
|
+
'Flags a `\'use server\'` directive inside a function body (Next.js\'s inline server action, often written in a page). WebJs\'s server boundary is a FILE: the directive is honoured only at the top of a `*.server.{js,ts}` module, so an inline one is an ordinary string and the function runs wherever it is called, and binding it to a form throws (it has no action identity). Move the function into its own module (`modules/<feature>/actions/<name>.server.ts` starting with `\'use server\'`), export it, and import it.',
|
|
33
48
|
},
|
|
34
49
|
{
|
|
35
50
|
name: 'tag-name-has-hyphen',
|
|
@@ -42,14 +57,19 @@ export const RULES = [
|
|
|
42
57
|
'A custom-element tag name must be registered exactly once across the app. Two `Class.register(\'tag\')` / `customElements.define(\'tag\', …)` calls for the SAME tag resolve INCONSISTENTLY at runtime: SSR overwrites the registry (last registration wins) while the browser keeps the first native upgrade, so the rendered element and the webjs registry disagree. Rename one tag.',
|
|
43
58
|
},
|
|
44
59
|
{
|
|
45
|
-
name: 'no-
|
|
60
|
+
name: 'no-conflicting-routes',
|
|
61
|
+
description:
|
|
62
|
+
'Two files must not resolve to one URL: two pages (often in different route groups, `app/(marketing)/page.ts` beside `app/(app)/page.ts`, or `[id]` beside `[slug]` at one position), or a `page` and a `route` handler at the same path. Only one can ever be served, and Next.js refuses both at build time.',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'no-signal-field-assignment',
|
|
46
66
|
description:
|
|
47
|
-
'
|
|
67
|
+
'A field a WebComponent class creates with `signal(` / `computed(` (a class-field initializer like `readonly #open = signal(false)`, or `this.open = signal(false)` in the constructor) is written through `.set()`, never assigned. `this.open = true` replaces the signal object: the component stays subscribed to the old signal, nothing re-renders, and the next `this.open.get()` throws. Fix: `this.open.set(true)`. Declare signal fields `readonly` in TypeScript so the compiler catches it too; this rule covers plain `.js` and a forgotten `readonly`, for writes inside the declaring class.',
|
|
48
68
|
},
|
|
49
69
|
{
|
|
50
70
|
name: 'reactive-props-no-class-field',
|
|
51
71
|
description:
|
|
52
|
-
'A reactive property declared via the `extends WebComponent({ … })` factory must NOT also have a plain class-field declaration (`count = 0`, `count: number = 0`, `count!: number`, or `count?: number`) in the class body. Under modern class-field semantics (including `erasableSyntaxOnly: true`) every class-field declaration compiles to Object.defineProperty *after* super(), clobbering the framework\'s reactive accessor and silently breaking re-renders. Set the default by assigning in the constructor after super().',
|
|
72
|
+
'A reactive property declared via the `extends WebComponent({ … })` factory or Lit\'s `static properties` must NOT also have a plain class-field declaration (`count = 0`, `count: number = 0`, `count!: number`, or `count?: number`) in the class body. Under modern class-field semantics (including `erasableSyntaxOnly: true`) every class-field declaration compiles to Object.defineProperty *after* super(), clobbering the framework\'s reactive accessor and silently breaking re-renders. Set the default by assigning in the constructor after super(). A type-only `declare count: number` is erased and is fine.',
|
|
53
73
|
},
|
|
54
74
|
{
|
|
55
75
|
name: 'array-prop-uses-array-type',
|
|
@@ -79,7 +99,7 @@ export const RULES = [
|
|
|
79
99
|
{
|
|
80
100
|
name: 'no-non-erasable-typescript',
|
|
81
101
|
description:
|
|
82
|
-
'Scans .ts / .mts source for the four non-erasable TypeScript constructs (enum declarations, namespace blocks with value statements, constructor parameter properties, and `import = require`) that the framework\'s type-stripper rejects at request time. Companion to `erasable-typescript-only`: that rule checks the tsconfig flag, this rule checks the actual source. Both run by default so the flag check catches violations early in the editor while the source scan catches violations even if the tsconfig flag is missing or the rule is bypassed. Skips node_modules, dist, build, .git, .next,
|
|
102
|
+
'Scans .ts / .mts source for the four non-erasable TypeScript constructs (enum declarations, namespace blocks with value statements, constructor parameter properties, and `import = require`) that the framework\'s type-stripper rejects at request time, and .ts / .mts / .js / .mjs source for DECORATORS (`@customElement`, `@property`, `@state`, `@query`, any `@name` in decorator position). The stripper passes a decorator through untouched, so the module then fails to load with a bare `SyntaxError: Invalid or unexpected token` on Node and in the browser; the rule names the decorator and its WebJs spelling (`static properties` plus `declare`, or the `WebComponent({ ... })` factory, for `@property`; `state: true` for `@state`; `MyEl.register(tag)` for `@customElement`; a getter over `this.renderRoot` for `@query`). Comments, strings and template bodies are masked first, so `@click` in an html template, `@media` in a css template, and a JSDoc `@param` never match. Companion to `erasable-typescript-only`: that rule checks the tsconfig flag, this rule checks the actual source. Both run by default so the flag check catches violations early in the editor while the source scan catches violations even if the tsconfig flag is missing or the rule is bypassed. Skips node_modules, dist, build, .git, .next, _private folders, and .js files under public/.',
|
|
83
103
|
},
|
|
84
104
|
{
|
|
85
105
|
name: 'no-browser-globals-in-render',
|
|
@@ -94,7 +114,7 @@ export const RULES = [
|
|
|
94
114
|
{
|
|
95
115
|
name: 'no-server-import-in-browser-module',
|
|
96
116
|
description:
|
|
97
|
-
'A
|
|
117
|
+
'A module that loads in the browser (a component outside a `.server` file, `instrumentation-client`, or anything either imports) must not transitively import a server-only `.server.{ts,js}` utility: in the browser that import is a stub that throws when the module loads, so the page crashes while typecheck passes. A `\'use server\'` action is fine (it becomes a working RPC stub). Pages, layouts and boundaries are never checked, because they run only on the server and never load in a browser (#1686), so a server import there is always fine. A component in a `.server` file never loads in the browser either, so it is not flagged.',
|
|
98
118
|
},
|
|
99
119
|
{
|
|
100
120
|
name: 'one-action-per-configured-file',
|
|
@@ -107,9 +127,74 @@ export const RULES = [
|
|
|
107
127
|
'Flags `<form action=${someAction}>` bound to a `\'use server\'` action whose file declares `export const method = \'GET\'` (#488). A GET action is a READ: it is CSRF-exempt and rides its arguments in the url, while a form submission is a CSRF-checked POST carrying a body, so the two contracts contradict each other and the submission is answered with a 405 at runtime. The rule reads the imported action\'s own file, so it only fires when the binding really does resolve to a GET-declared action. Fix by dropping the `method` export (an action with no `method` is a POST, which is what a form wants) or by binding a different action; if the form really is a read, use a plain `<form method="get" action="/search">` with no bound action.',
|
|
108
128
|
},
|
|
109
129
|
{
|
|
110
|
-
name: 'no-
|
|
130
|
+
name: 'no-component-in-use-server-file',
|
|
131
|
+
description:
|
|
132
|
+
"Flags a component class in a `*.server.{ts,js}` file that starts with `'use server'`. Every function export of a `'use server'` file is wrapped as a server action, and a class is a function, so the component is neither a working component nor a usable action. Drop the directive to make it a server-only component (rendered on the server, never shipped), or move it to an ordinary file to make it a component that runs in the browser.",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'no-interactivity-in-server-component',
|
|
136
|
+
description:
|
|
137
|
+
'Flags a component in a `*.server.{ts,js}` file that uses an event binding, a reactive primitive (`signal`, `Task`, ...) or a client-only lifecycle hook (`connectedCallback`, `firstUpdated`, ...). A component in a `.server` file renders on the server only and is never defined in the browser, so that code never runs. Move it to an ordinary file, where a component always ships.',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'no-server-component-in-client-template',
|
|
141
|
+
description:
|
|
142
|
+
'Flags a module that loads in the browser (a component or anything it imports) rendering the tag of a component defined in a `*.server.{ts,js}` file. That tag is never defined in the browser, so it renders on the server and comes back empty after a client re-render, which is why Next.js does not let a client component render a server component. Render it from the page and pass it in as a child, where it lands in the component\'s `<slot>`.',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'no-use-client-in-route-module',
|
|
146
|
+
description:
|
|
147
|
+
"Flags a `'use client'` directive at the top of a page, layout or boundary, the Next.js way to make a page interactive. In WebJs a route module is always a server component and the client part is a component, so the directive does nothing there. Move the interactive part into a component (`components/my-widget.ts`, Lit's APIs) and render its tag from the page. A `'use client'` in a component file is left alone, since a component already runs on the client.",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'no-component-in-route-module',
|
|
151
|
+
description:
|
|
152
|
+
'Flags a component class defined in a page, layout or boundary file. A route module runs only on the server and never loads in the browser (#1686), so the element renders its server markup and never upgrades. Move the class into its own file (`components/my-widget.ts`) and import it. Route-ness comes from the route table, so a helper that happens to be called `page.ts` outside `app/` is not a route module.',
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'no-event-binding-in-route-module',
|
|
156
|
+
description:
|
|
157
|
+
'Flags an event binding (`@click=${...}`) on a tag in a page, layout or boundary template. A route module renders only on the server and never hydrates, so the handler never runs, the same reason Next.js refuses an event handler in a server component. Put the interactive part in a component, or use a `<form action=${action}>` or `<a href>` that works without JavaScript. A binding written as escaped text in a code sample (`<button @click=...`) is not flagged.',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'no-route-module-import-in-browser-module',
|
|
111
161
|
description:
|
|
112
|
-
'
|
|
162
|
+
'Flags a module that loads in the browser (a component or anything it imports) importing a page, layout or boundary file. Route modules run only on the server and are never served to the browser (#1686), so the import 404s and the importing module fails to load. Move the shared code into its own file and import it from both.',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'no-node-builtin-in-browser-module',
|
|
166
|
+
description:
|
|
167
|
+
'Flags a `node:` import, or a bare Node builtin such as `fs`, in a module that loads in the browser (a component or anything it imports). It can never resolve there, so the module fails to load. Do the work in the page (pages run only on the server) and pass the result in as a property, or behind a `\'use server\'` action. An `import type` is erased and never flagged.',
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'no-foreign-framework-import',
|
|
171
|
+
description:
|
|
172
|
+
'Flags an import of `next` / `next/*`, `react` / `react-dom`, `lit` / `lit-html` / `lit-element` / `lit/*`, `@lit/*` or `@lit-labs/*`, in any form (named, default, namespace, `import type`, side-effect, `export ... from`, or a string-literal dynamic `import()`). WebJs never aliases or loads those packages: a Next import fails to resolve, and a Lit import quietly loads a second component runtime from the CDN. The violation names the exact `@webjsdev/*` replacement (for example `next/navigation` -> `redirect` / `notFound` from `@webjsdev/core`, `lit` -> `WebComponent` / `html` / `css` from `@webjsdev/core`, `@lit/context` -> `@webjsdev/core/context`). `react` / `react-dom` stay allowed inside a `*.server.*` module (an email renderer, say), which never reaches the browser. The same refusal runs at load time: the dev overlay and production readiness name the import, and a server import is rewritten to the same fix.',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'no-jsx-attributes',
|
|
176
|
+
description:
|
|
177
|
+
'Flags JSX written into an `html` template: a `className=` or `htmlFor=` attribute (HTML reads them as unknown `classname` / `htmlfor` attributes, so the class never applies and the label never targets its control) and a capitalised component tag such as `<Link>` or `<Button>` (HTML lowercases it into an unknown element that renders nothing). Both renderers throw on these when the template first compiles; the rule reports them ahead of time. Write `class=` / `for=`, and render a custom element by its registered hyphenated tag (or a plain `<a href>` for a link). Text in the template (`<Link>`, a `className` mentioned in prose), comments, and script / style bodies are never flagged.',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: 'no-jsx-route-file',
|
|
181
|
+
description:
|
|
182
|
+
'Flags a routing file (`page`, `layout`, `route`, a boundary such as `error` / `not-found` / `loading`, `middleware`, or a metadata route) written as `.tsx` or `.jsx`, the Next.js default. WebJs has no build step, so JSX never loads and the router skips the file: the route silently does not exist. Production refuses to boot on it, dev answers every request with the problem, and `webjs doctor` fails (`JSX_ROUTE_FILES`). Rename it to `.ts` / `.js` and return an `html` template.',
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'no-css-import',
|
|
186
|
+
description:
|
|
187
|
+
'Flags `import \'./styles.css\'` (or any `.css` import, including `import styles from \'./x.module.css\'`). Importing CSS is a bundler feature WebJs does not have: Node refuses the `.css` extension, Bun silently turns the import into a no-op on the server, and the browser refuses a stylesheet as a module script, so the importing module never loads. The same refusal runs at load time. Load a stylesheet with `<link rel="stylesheet" href="...">` in the root layout (the scaffold links its Tailwind output this way), or scope CSS to a component with `static styles = css` and `static shadow = true`.',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: 'no-public-route-conflict',
|
|
191
|
+
description:
|
|
192
|
+
'Files in `public/` are served at the URL ROOT, as in Next.js (`public/logo.png` is `/logo.png`; there is no `/public/` prefix), and before every route, so a public file wins over a dynamic route. A public file at the same URL as a static page, a `route.{js,ts}` handler, or a metadata route (`public/robots.txt` beside `app/robots.ts`) would make one of them unreachable, so it is flagged here and refused at production boot. Rename or move one side.',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: 'no-page-route-conflict',
|
|
196
|
+
description:
|
|
197
|
+
'A `page.{js,ts}` and a `route.{js,ts}` must not answer the same URL (including through a route group, `app/(a)/x/page.ts` beside `app/x/route.ts`). Next.js refuses this at build time; WebJs refuses to boot in production and answers every dev request with the problem, because the route handler would shadow the page and it could never render. Move the route handler to its own folder (`app/api/...`) or delete the page.',
|
|
113
198
|
},
|
|
114
199
|
{
|
|
115
200
|
name: 'no-interpolation-in-raw-text-element',
|
|
@@ -119,7 +204,22 @@ export const RULES = [
|
|
|
119
204
|
{
|
|
120
205
|
name: 'no-missing-local-import',
|
|
121
206
|
description:
|
|
122
|
-
'Flags a NAMED value import of a symbol that a resolvable app-internal module does not export (`import { todos } from \'#db/schema.server.ts\'` when the schema no longer exports `todos`). The binding is `undefined` at runtime and crashes on first use, yet the rest of `webjs check` (
|
|
207
|
+
'Flags a NAMED value import of a symbol that a resolvable app-internal module does not export (`import { todos } from \'#db/schema.server.ts\'` when the schema no longer exports `todos`). The binding is `undefined` at runtime and crashes on first use, yet the rest of `webjs check` (graph-based, it does not compile types) misses it, so a schema swap that orphans a gallery module can pass `check` while `typecheck` is red. This is the runtime-crash class `check` exists to catch, filling that gap so an agent running only `check` cannot ship a broken import. Deliberately conservative to never false-positive on a valid app: it only inspects app-internal specifiers (a relative `./` path or a `#` alias) that resolve to a file in the app, only NAMED value imports (a `type` import, a default import, a namespace import, and any bare / `node:` / npm-package specifier are skipped), and it treats a module as UNKNOWABLE (never flags imports from it) when its exports are not fully enumerable: a `export * from ...` star re-export, a destructuring `export const { a } = ...`, or a multi-declarator `export const a = .., b = ..`. It reads names from `export function/class/const/let/var/type/interface/enum` and from `export { a, b as c }` / `export { x } from ...` clauses (the alias after `as` is the exported name). A re-export barrel therefore resolves correctly, and a `\'use server\'` action file exports its function names normally. Found dogfooding a tic-tac-toe app: dropping the example `todos` table left a gallery module importing it, green under `check` but red under `typecheck`.',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: 'static-styles-need-shadow',
|
|
211
|
+
description:
|
|
212
|
+
'Flags a class extending `WebComponent` that declares `static styles` without `static shadow = true`. WebJs adopts `static styles` into a SHADOW root, and a component renders into light DOM unless it opts into shadow DOM (the reverse of Lit\'s default), so the styles are never applied. Fix: add `static shadow = true` to scope them, or drop `static styles` and style the light DOM with Tailwind utilities or a global stylesheet whose class selectors are prefixed with the tag name. A `static shadow` the rule cannot evaluate (a getter or an expression) is not flagged.',
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'shadow-options-need-shadow',
|
|
216
|
+
description:
|
|
217
|
+
'Flags a class extending `WebComponent` that declares `static shadowRootOptions` (Lit\'s `{ mode, delegatesFocus, ... }` for `attachShadow`) without `static shadow = true`. The options configure a shadow root, and the component has none: it renders into light DOM by default, so they are ignored. Fix: add `static shadow = true`, or remove the options.',
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'core-imports-exist',
|
|
221
|
+
description:
|
|
222
|
+
'Flags a named value import from `@webjsdev/core` (or one of its subpaths) of a name that module does not export, a default import from a module with no default export, and an import from a subpath core does not export. Each is a load-time crash: ESM refuses to link the module with `does not provide an export named X`, which names neither the cause nor the fix. Lit habits produce it: `LitElement` (use `WebComponent`), a decorator function such as `customElement` or `property` (use `MyEl.register(tag)` and `static properties`), or a type such as `PropertyValues` or `TemplateResult` imported as a value (use `import type`, since the type stripper keeps a plain import). The rule reads the INSTALLED core package\'s real export surface (each `exports` subpath\'s source module for values, its declaration file for types), so a name core gains is accepted as soon as it ships, and a name exported by a different subpath is pointed there. `import type` imports and `type`-qualified names are skipped, since they are erased before load.',
|
|
123
223
|
},
|
|
124
224
|
];
|
|
125
225
|
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* and runner.js calls the rules. Parked in runner.js it made that a cycle.
|
|
7
7
|
*/
|
|
8
8
|
import { readFile, stat } from 'node:fs/promises';
|
|
9
|
-
import { join, relative
|
|
9
|
+
import { join, relative } from 'node:path';
|
|
10
10
|
import { walk } from '../fs-walk.js';
|
|
11
11
|
import { buildModuleGraph, transitiveDeps } from '../module-graph.js';
|
|
12
12
|
import { scanComponents } from '../component-scanner.js';
|
|
13
13
|
import { buildRouteTable } from '../router.js';
|
|
14
|
-
import {
|
|
14
|
+
import { routeTableFiles } from '../route-frontier.js';
|
|
15
15
|
import {
|
|
16
16
|
hasUseServerDirective,
|
|
17
17
|
} from './helpers.js';
|
|
@@ -60,70 +60,53 @@ export function findImportChain(graph, from, to) {
|
|
|
60
60
|
* Implements `no-server-import-in-browser-module`. Factored into its own
|
|
61
61
|
* function (rather than an inline block) because it does the heavier
|
|
62
62
|
* whole-app analysis the other rules avoid: it builds the module graph,
|
|
63
|
-
* scans components
|
|
64
|
-
*
|
|
65
|
-
* byte-for-byte the build's.
|
|
63
|
+
* scans components and builds the route table, and returns them for the
|
|
64
|
+
* rules that share the analysis.
|
|
66
65
|
*
|
|
67
66
|
* A module is flagged when BOTH hold:
|
|
68
|
-
* 1. It
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* 2. Its transitive import closure reaches a `.server.{ts,js}`
|
|
73
|
-
* `transitiveDeps` stops AT a server file (it is
|
|
74
|
-
* into), so a server file pulled in only through
|
|
75
|
-
* not attributed to a browser module that never
|
|
67
|
+
* 1. It loads in the browser: a component (every component in an ordinary
|
|
68
|
+
* file ships, as in Lit) or `instrumentation-client.*`. A route module
|
|
69
|
+
* (page, layout, boundary) never ships (#1686), so a server import in
|
|
70
|
+
* one is always fine.
|
|
71
|
+
* 2. Its transitive import closure reaches a `.server.{ts,js}` utility
|
|
72
|
+
* (no `'use server'`). `transitiveDeps` stops AT a server file (it is
|
|
73
|
+
* included but not walked into), so a server file pulled in only through
|
|
74
|
+
* another server file is not attributed to a browser module that never
|
|
75
|
+
* reaches it directly.
|
|
76
76
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* Never flagged: a `.server.ts` importing another `.server.ts` (server-to-
|
|
83
|
-
* server, and `.server.*` modules are not components nor route modules), and
|
|
84
|
-
* `middleware.ts` / `route.ts` (server-only, never page/layout/component
|
|
85
|
-
* entries, so they are not in the candidate set to begin with).
|
|
77
|
+
* Never flagged: a `.server.ts` importing another `.server.ts` (server to
|
|
78
|
+
* server; a `.server.*` file is never a scanned component, so a component in
|
|
79
|
+
* one is the server-only kind and is not a candidate), and `middleware.ts` /
|
|
80
|
+
* `route.ts` (server-only, not in the candidate set to begin with).
|
|
86
81
|
*
|
|
87
82
|
* Scope note for dynamic imports: a string-literal `import('./widget.ts')` IS
|
|
88
|
-
* tracked by the module graph
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* import of an npm specifier or an otherwise-reachable app module is perfectly
|
|
97
|
-
* valid, so it fails the check-is-correctness-only dividing line), the dev
|
|
98
|
-
* server surfaces it with a 404 hint when the target 404s (see dev.js #751).
|
|
83
|
+
* tracked by the module graph (#751), but only as a GATE edge (so the lazily
|
|
84
|
+
* imported module is servable); this rule's server-import detection runs over
|
|
85
|
+
* STATIC edges only (`transitiveDeps`), so a dynamic `import('./x.server.ts')`
|
|
86
|
+
* of a no-`'use server'` utility is not flagged here. That is deliberate: the
|
|
87
|
+
* throw-at-load crash is deferred to call time. A computed `import(expr)`
|
|
88
|
+
* cannot be resolved statically at all; rather than a false-positive-prone
|
|
89
|
+
* check rule, the dev server surfaces it with a 404 hint when the target 404s
|
|
90
|
+
* (see dev.js #751).
|
|
99
91
|
*
|
|
100
92
|
* @param {string} appDir
|
|
101
93
|
* @param {Violation[]} violations appended to in place
|
|
102
94
|
*/
|
|
103
95
|
// --- Rule: no-server-import-in-browser-module ---
|
|
104
|
-
// A
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
// module
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
// router, a reactive primitive, module-scope code, a client-effecting util
|
|
111
|
-
// on a component-free path) it ships whole, must load in the browser,
|
|
112
|
-
// and drags the server import with it: the stub throws (or a server-only
|
|
113
|
-
// export like `auth` is missing) the instant the module loads. That crash
|
|
114
|
-
// only surfaces at runtime; typecheck and every other check pass.
|
|
115
|
-
//
|
|
116
|
-
// The rule reuses the BUILD'S elision verdict (analyzeElision) instead of
|
|
117
|
-
// re-deriving it, so it fires ONLY on modules that genuinely ship: a
|
|
118
|
-
// display-only page the framework elides is never flagged (that is the
|
|
119
|
-
// legitimate pattern). The motivating case (crisp dogfood): a page that does
|
|
120
|
-
// `await auth()` (import from `lib/auth.server.ts`) AND imports a component
|
|
121
|
-
// directly, so it is not elided and ships the server import.
|
|
96
|
+
// A module the browser loads must not transitively import a server-only
|
|
97
|
+
// `.server.{ts,js}` utility. The browser gets a stub for the server file, and
|
|
98
|
+
// the stub throws (or a server-only export like `auth` is missing) the
|
|
99
|
+
// instant the module loads. That crash only surfaces at runtime; typecheck
|
|
100
|
+
// and every other check pass. A `'use server'` action is fine: its stub is a
|
|
101
|
+
// working RPC call.
|
|
122
102
|
export async function checkServerImportInBrowserModule(appDir, violations) {
|
|
103
|
+
/** Every non-server module the browser loads (the shipping candidates and
|
|
104
|
+
* their transitive closures), returned for the env rule. @type {Set<string>} */
|
|
105
|
+
const shipped = new Set();
|
|
123
106
|
// No `app/` directory means this is not a routable WebJs app (e.g. a bare
|
|
124
107
|
// component library, or a fixture with only `lib/`); nothing ships, so the
|
|
125
108
|
// rule has nothing to police. Skip rather than do the heavy analysis.
|
|
126
|
-
if (!(await pathExists(join(appDir, 'app')))) return;
|
|
109
|
+
if (!(await pathExists(join(appDir, 'app')))) return { shipped, routeFiles: new Set(), componentFiles: new Set(), moduleGraph: new Map() };
|
|
127
110
|
|
|
128
111
|
let moduleGraph, components, routeTable;
|
|
129
112
|
try {
|
|
@@ -133,91 +116,24 @@ export async function checkServerImportInBrowserModule(appDir, violations) {
|
|
|
133
116
|
} catch {
|
|
134
117
|
// A malformed app the analysis can't process is left to the other rules
|
|
135
118
|
// (and the dev server) to surface; this rule degrades to a no-op.
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Page + layout modules that the router treats as route modules, exactly the
|
|
140
|
-
// set the dev server feeds to analyzeElision (so the inert verdict matches).
|
|
141
|
-
/** @type {Set<string>} */
|
|
142
|
-
const routeModuleSet = new Set();
|
|
143
|
-
for (const page of routeTable.pages || []) {
|
|
144
|
-
if (page.file) routeModuleSet.add(page.file);
|
|
145
|
-
for (const f of page.layouts || []) routeModuleSet.add(f);
|
|
146
|
-
}
|
|
147
|
-
const routeModules = [...routeModuleSet];
|
|
148
|
-
|
|
149
|
-
// error / loading / not-found modules ALSO ship to the browser, but unlike
|
|
150
|
-
// pages + layouts they are never elided: the dev server's
|
|
151
|
-
// `computeBrowserBoundFiles` adds them to the browser-bound entry set
|
|
152
|
-
// unconditionally (only ELIDABLE-COMPONENT imports are ever stripped, and
|
|
153
|
-
// these modules have no component to strip). So a personalized 404 that does
|
|
154
|
-
// `await auth()` is a real throw-at-load crash the page+layout-only candidate
|
|
155
|
-
// set would miss. Collect them here and add them to the candidate set as
|
|
156
|
-
// always-shipping (no elision verdict to consult).
|
|
157
|
-
/** @type {Map<string, string>} abs file -> kind */
|
|
158
|
-
const alwaysShipRouteModules = new Map();
|
|
159
|
-
for (const page of routeTable.pages || []) {
|
|
160
|
-
for (const f of page.errors || []) alwaysShipRouteModules.set(f, 'error boundary');
|
|
161
|
-
for (const f of page.loadings || []) alwaysShipRouteModules.set(f, 'loading boundary');
|
|
162
|
-
// forbidden / unauthorized joined this set with #1298. They used to render
|
|
163
|
-
// with an empty boot set, so their modules never reached the browser and
|
|
164
|
-
// omitting them here was harmless. They now render inside their layout
|
|
165
|
-
// chain AND ship, so a `forbidden.ts` doing `import { auth } from
|
|
166
|
-
// '#lib/auth.server.ts'` (a natural thing on a 403: "signed in as X, but
|
|
167
|
-
// not permitted") is a real throw-at-load crash that takes every sibling
|
|
168
|
-
// registration in the same module script with it.
|
|
169
|
-
for (const f of page.forbiddens || []) alwaysShipRouteModules.set(f, 'forbidden boundary');
|
|
170
|
-
for (const f of page.unauthorizeds || []) alwaysShipRouteModules.set(f, 'unauthorized boundary');
|
|
171
|
-
}
|
|
172
|
-
if (routeTable.notFound) alwaysShipRouteModules.set(routeTable.notFound, 'not-found page');
|
|
173
|
-
if (routeTable.notFounds) {
|
|
174
|
-
for (const f of routeTable.notFounds.values()) {
|
|
175
|
-
alwaysShipRouteModules.set(f, 'not-found page');
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
// global-not-found ships for the same reason: a thrown notFound() with no
|
|
179
|
-
// nearer boundary renders it against the matched route, so it wraps and
|
|
180
|
-
// boots. global-ERROR is deliberately absent: it returns its own document
|
|
181
|
-
// verbatim with no importmap and no boot script, so nothing of it ever
|
|
182
|
-
// reaches the browser.
|
|
183
|
-
if (routeTable.globalNotFound) {
|
|
184
|
-
alwaysShipRouteModules.set(routeTable.globalNotFound, 'global-not-found page');
|
|
119
|
+
return { shipped, routeFiles: new Set(), componentFiles: new Set(), moduleGraph: new Map() };
|
|
185
120
|
}
|
|
186
121
|
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
const
|
|
193
|
-
const { elidableComponents, inertRouteModules, importOnlyRouteModules } = elideEnabled
|
|
194
|
-
? await analyzeElision(components, routeModules, moduleGraph, (f) => readFile(f, 'utf8'), appDir)
|
|
195
|
-
: { elidableComponents: new Set(), inertRouteModules: new Set(), importOnlyRouteModules: new Map() };
|
|
122
|
+
// What the browser loads (#1686): every component, as in Lit, plus
|
|
123
|
+
// `instrumentation-client.*`. A route module (page, layout, boundary) never
|
|
124
|
+
// ships, so a server import in one is fine wherever it sits. A `.server.*`
|
|
125
|
+
// file is never a scanned component (a component there is the server-only
|
|
126
|
+
// kind), so server-to-server imports are excluded by construction.
|
|
127
|
+
const routeFiles = routeTableFiles(routeTable);
|
|
196
128
|
|
|
197
|
-
|
|
198
|
-
// route modules that are NOT inert and NOT import-only (an import-only
|
|
199
|
-
// module is dropped from the boot in favour of its component frontier,
|
|
200
|
-
// #605/#963, so its own imports never load in a browser and a bare
|
|
201
|
-
// server-only import in it is harmless). A `.server.*` file is never a
|
|
202
|
-
// component (the scanner skips it) nor a route module the browser loads, so
|
|
203
|
-
// it cannot enter this set; server-to-server imports are excluded by
|
|
204
|
-
// construction.
|
|
205
|
-
/** @type {Map<string, { kind: string }>} relFile is keyed by ABS path */
|
|
129
|
+
/** @type {Map<string, { kind: string }>} keyed by ABS path */
|
|
206
130
|
const candidates = new Map();
|
|
207
131
|
for (const c of components) {
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
for (const file of routeModules) {
|
|
211
|
-
if (inertRouteModules.has(file) || importOnlyRouteModules.has(file)) continue;
|
|
212
|
-
const base = basename(file);
|
|
213
|
-
const kind = /^layout\./.test(base) ? 'layout' : 'page';
|
|
214
|
-
candidates.set(file, { kind });
|
|
132
|
+
if (routeFiles.has(c.file)) continue;
|
|
133
|
+
candidates.set(c.file, { kind: 'component' });
|
|
215
134
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
// wins (it is the more specific kind); these only add files not already seen.
|
|
219
|
-
for (const [file, kind] of alwaysShipRouteModules) {
|
|
220
|
-
if (!candidates.has(file)) candidates.set(file, { kind });
|
|
135
|
+
if (routeTable.instrumentationClient) {
|
|
136
|
+
candidates.set(routeTable.instrumentationClient, { kind: 'instrumentation-client module' });
|
|
221
137
|
}
|
|
222
138
|
|
|
223
139
|
// Report at most once per module (a page importing two server modules is one
|
|
@@ -228,6 +144,8 @@ export async function checkServerImportInBrowserModule(appDir, violations) {
|
|
|
228
144
|
// is not in the result. A direct OR indirect server import both surface,
|
|
229
145
|
// because the closure walks every non-server edge until it hits one.
|
|
230
146
|
const closure = transitiveDeps(moduleGraph, [file], appDir);
|
|
147
|
+
shipped.add(file);
|
|
148
|
+
for (const d of closure) if (!/\.server\.m?[jt]s$/.test(d)) shipped.add(d);
|
|
231
149
|
// Of the reachable server files, find one that is a genuine throw-at-load
|
|
232
150
|
// crash in the browser. TWO kinds of `.server.*` import are NOT crashes and
|
|
233
151
|
// must be skipped, or the rule false-positives on legitimate code:
|
|
@@ -265,44 +183,23 @@ export async function checkServerImportInBrowserModule(appDir, violations) {
|
|
|
265
183
|
const importer = chainFiles.length >= 2 ? chainFiles[chainFiles.length - 2] : null;
|
|
266
184
|
const viaTypesModule = importer && /(^|\/)types(\.m?[jt]s$|\/)/.test(relative(appDir, importer));
|
|
267
185
|
|
|
268
|
-
// The "elides again" remedy only applies to a page / layout: since the
|
|
269
|
-
// path-aware import-only verdict (#963), a page importing a component to
|
|
270
|
-
// register is dropped from the boot, so a page/layout on this rule ships
|
|
271
|
-
// because of its OWN client work (or a client-effecting non-component in
|
|
272
|
-
// its closure); moving that work into a component makes it a dropped
|
|
273
|
-
// carrier again and the server import never loads. The error / loading /
|
|
274
|
-
// not-found boundaries always ship and are never elided, so offering
|
|
275
|
-
// them an "elides again" fix is wrong. Branch the fix text on kind.
|
|
276
|
-
const canElide = kind === 'page' || kind === 'layout';
|
|
277
186
|
const typesHint = viaTypesModule
|
|
278
187
|
? `The edge enters via a types-shaped module (${relative(appDir, importer)}); if it re-exports a runtime VALUE from a \`.server.{ts,js}\` file, relocate that to a browser-safe typedef (a plain \`interface\` / JSDoc, or an \`import type\` which the stripper erases) so the type is shared without pinning the module. `
|
|
279
188
|
: '';
|
|
280
|
-
const fixText =
|
|
281
|
-
|
|
282
|
-
: `${typesHint}Keep the server call off this browser-shipped ${kind} (it always ships and is never elided). Options: (1) gate the route in \`middleware.ts\` (runs server-side, never ships); or (2) move the server-only call behind a \`'use server'\` action in a \`.server.{ts,js}\` file and call it as an RPC.`;
|
|
189
|
+
const fixText = `${typesHint}Keep the server call out of this ${kind}. Load the data in the page (pages run only on the server) and pass it in as a property, or move the call behind a \`'use server'\` action in a \`.server.{ts,js}\` file and call it as an RPC. A component that only displays server data can instead live in a \`*.server.ts\` file (without \`'use server'\`): it then renders on the server, may import server code, and ships nothing, like a Next.js server component.`;
|
|
190
|
+
const whyText = '';
|
|
283
191
|
|
|
284
192
|
violations.push({
|
|
285
193
|
rule: 'no-server-import-in-browser-module',
|
|
286
194
|
file: relFile,
|
|
287
195
|
message:
|
|
288
|
-
`This ${kind}
|
|
196
|
+
`This ${kind} loads in the browser but transitively imports the server-only module ${relServer} (${chain}).${whyText} In the browser that import resolves to a stub, so the module crashes at load (the stub throws, or a server-only export such as \`auth\` is missing). \`webjs typecheck\` and the rest of \`webjs check\` pass; only the running ${kind} fails.`,
|
|
289
197
|
fix: fixText,
|
|
290
198
|
});
|
|
291
199
|
}
|
|
200
|
+
return { shipped, routeFiles, componentFiles: new Set(components.map((c) => c.file)), moduleGraph };
|
|
292
201
|
}
|
|
293
202
|
|
|
294
|
-
/**
|
|
295
|
-
* Read whether component elision is enabled for `appDir`, mirroring
|
|
296
|
-
* `dev.js`'s `readElideEnabled` so the check's notion of "ships" matches the
|
|
297
|
-
* dev server's. Elision is ON unless `webjs.elide === false` in package.json or
|
|
298
|
-
* the `WEBJS_ELIDE` env var forces it off (`0` / `false` / `off` / `no`). A
|
|
299
|
-
* missing or malformed package.json keeps the default (on). Inlined rather
|
|
300
|
-
* than imported from `dev.js` so the check tool does not pull the whole dev
|
|
301
|
-
* server module just for this flag.
|
|
302
|
-
*
|
|
303
|
-
* @param {string} appDir
|
|
304
|
-
* @returns {Promise<boolean>}
|
|
305
|
-
*/
|
|
306
203
|
/**
|
|
307
204
|
* True if `file` is a `'use server'` action: a `.server.{ts,js}` module that
|
|
308
205
|
* declares the `'use server'` directive. The dev server rewrites its browser
|
|
@@ -325,21 +222,6 @@ export async function isUseServerActionFile(file) {
|
|
|
325
222
|
}
|
|
326
223
|
}
|
|
327
224
|
|
|
328
|
-
export async function readElideEnabledForCheck(appDir) {
|
|
329
|
-
const raw = process.env.WEBJS_ELIDE;
|
|
330
|
-
if (raw != null) {
|
|
331
|
-
const v = raw.trim().toLowerCase();
|
|
332
|
-
if (v === '0' || v === 'false' || v === 'off' || v === 'no') return false;
|
|
333
|
-
if (v === '1' || v === 'true' || v === 'on' || v === 'yes') return true;
|
|
334
|
-
}
|
|
335
|
-
try {
|
|
336
|
-
const pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
337
|
-
if (pkg && pkg.webjs && pkg.webjs.elide === false) return false;
|
|
338
|
-
} catch {
|
|
339
|
-
// No package.json, malformed JSON, or unreadable: keep the default.
|
|
340
|
-
}
|
|
341
|
-
return true;
|
|
342
|
-
}
|
|
343
225
|
|
|
344
226
|
/**
|
|
345
227
|
* Async fs.exists shim. Returns true if the path exists at all (file
|
package/src/check/runner.js
CHANGED
|
@@ -6,17 +6,21 @@ import {
|
|
|
6
6
|
} from '../js-scan.js';
|
|
7
7
|
import {
|
|
8
8
|
checkComponentsHaveRegister,
|
|
9
|
-
|
|
9
|
+
checkNoSignalFieldAssignment,
|
|
10
10
|
checkReactivePropsNoClassField,
|
|
11
11
|
checkArrayPropUsesArrayType,
|
|
12
12
|
checkNoBrowserGlobalsInRender,
|
|
13
13
|
checkNoShadowedNativeMember,
|
|
14
|
+
checkShadowOnlyStatics,
|
|
14
15
|
} from './rules-components.js';
|
|
15
16
|
import {
|
|
16
|
-
checkNoRedirectInApiRoute,
|
|
17
17
|
checkNoInterpolationInRawTextElement,
|
|
18
18
|
checkNoServerEnvInComponents,
|
|
19
|
+
checkNoNextRuntimeGuard,
|
|
20
|
+
checkNoNextUrl,
|
|
19
21
|
checkShellInNonRootLayout,
|
|
22
|
+
checkAppLayout,
|
|
23
|
+
checkNoJsxAttributes,
|
|
20
24
|
} from './rules-routing.js';
|
|
21
25
|
import {
|
|
22
26
|
checkErasableTypescriptOnly,
|
|
@@ -26,15 +30,20 @@ import {
|
|
|
26
30
|
checkUseServerNeedsExtension,
|
|
27
31
|
checkUseServerExportsCallable,
|
|
28
32
|
checkOneActionPerConfiguredFile,
|
|
33
|
+
checkNoInlineUseServer,
|
|
29
34
|
} from './rules-actions.js';
|
|
30
35
|
import {
|
|
31
36
|
checkTagNameHasHyphen,
|
|
32
37
|
checkNoDuplicateTag,
|
|
33
38
|
} from './rules-registry.js';
|
|
34
39
|
import { checkServerImportInBrowserModule } from './runner-support.js';
|
|
40
|
+
import { checkRouteModuleBoundaries } from './rules-route-modules.js';
|
|
41
|
+
import { checkNoConflictingRoutes } from './rules-routes.js';
|
|
35
42
|
import {
|
|
36
43
|
checkNoMissingLocalImport,
|
|
37
44
|
checkFormActionNotAGetAction,
|
|
45
|
+
checkCoreImportsExist,
|
|
46
|
+
checkNoForeignFrameworkImport,
|
|
38
47
|
} from './rules-imports.js';
|
|
39
48
|
|
|
40
49
|
/**
|
|
@@ -89,25 +98,34 @@ export async function checkConventions(appDir) {
|
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
checkComponentsHaveRegister(files, violations);
|
|
92
|
-
|
|
101
|
+
checkNoSignalFieldAssignment(files, violations);
|
|
93
102
|
checkReactivePropsNoClassField(files, violations);
|
|
94
103
|
checkArrayPropUsesArrayType(files, violations);
|
|
95
104
|
checkNoBrowserGlobalsInRender(files, violations);
|
|
96
105
|
checkNoShadowedNativeMember(files, violations);
|
|
97
|
-
|
|
106
|
+
checkShadowOnlyStatics(files, violations);
|
|
98
107
|
checkNoInterpolationInRawTextElement(files, violations);
|
|
99
|
-
checkNoServerEnvInComponents(files, violations);
|
|
100
108
|
checkShellInNonRootLayout(files, violations);
|
|
101
109
|
await checkErasableTypescriptOnly(appDir, violations);
|
|
102
110
|
await checkNoNonErasableTypescript(appDir, violations);
|
|
103
111
|
checkUseServerNeedsExtension(files, violations);
|
|
104
112
|
checkUseServerExportsCallable(files, violations);
|
|
105
113
|
checkOneActionPerConfiguredFile(files, violations);
|
|
114
|
+
checkNoInlineUseServer(files, violations);
|
|
106
115
|
checkTagNameHasHyphen(files, violations);
|
|
107
116
|
await checkNoDuplicateTag(appDir, files, violations);
|
|
108
|
-
await
|
|
117
|
+
await checkNoConflictingRoutes(appDir, violations);
|
|
118
|
+
const browser = await checkServerImportInBrowserModule(appDir, violations);
|
|
119
|
+
checkNoServerEnvInComponents(files, violations, browser.shipped, browser.routeFiles);
|
|
120
|
+
checkRouteModuleBoundaries(appDir, files, violations, browser);
|
|
121
|
+
checkNoNextRuntimeGuard(files, violations);
|
|
122
|
+
checkNoNextUrl(files, violations);
|
|
109
123
|
checkNoMissingLocalImport(appDir, files, violations);
|
|
110
124
|
checkFormActionNotAGetAction(appDir, files, violations);
|
|
125
|
+
checkCoreImportsExist(appDir, files, violations);
|
|
126
|
+
checkNoForeignFrameworkImport(files, violations);
|
|
127
|
+
checkNoJsxAttributes(files, violations);
|
|
128
|
+
await checkAppLayout(appDir, violations);
|
|
111
129
|
|
|
112
130
|
return violations;
|
|
113
131
|
}
|