@warlock.js/web 5.5.0 → 5.7.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/CHANGELOG.md +53 -5
- package/esm/build/contribution.d.mts +2 -2
- package/esm/build/contribution.mjs +1 -1
- package/esm/build/contribution.mjs.map +1 -1
- package/esm/build/discover-pages.mjs +130 -208
- package/esm/build/discover-pages.mjs.map +1 -1
- package/esm/build/generate-pages-barrel.mjs +7 -5
- package/esm/build/generate-pages-barrel.mjs.map +1 -1
- package/esm/build/page-routes-manifest.mjs +1 -1
- package/esm/build/public-files.mjs +30 -2
- package/esm/build/public-files.mjs.map +1 -1
- package/esm/build/read-metadata-keys.mjs +225 -0
- package/esm/build/read-metadata-keys.mjs.map +1 -0
- package/esm/build/read-route-exports.mjs.map +1 -1
- package/esm/client/build-hydrated-tree.mjs +1 -1
- package/esm/client/build-hydrated-tree.mjs.map +1 -1
- package/esm/client/navigation/document-metadata.mjs +108 -0
- package/esm/client/navigation/document-metadata.mjs.map +1 -0
- package/esm/client/navigation/fetch-page-data.mjs +3 -10
- package/esm/client/navigation/fetch-page-data.mjs.map +1 -1
- package/esm/client/navigation/navigation-root.mjs +24 -104
- package/esm/client/navigation/navigation-root.mjs.map +1 -1
- package/esm/client/navigation/scroll-to-fragment.mjs.map +1 -1
- package/esm/client/navigation/use-query-string.d.mts +35 -0
- package/esm/client/navigation/use-query-string.mjs +68 -0
- package/esm/client/navigation/use-query-string.mjs.map +1 -0
- package/esm/client/runtime/index.d.mts +1 -1
- package/esm/client/runtime/index.mjs +1 -1
- package/esm/client/runtime/manifest.mjs.map +1 -1
- package/esm/client/runtime/matcher.mjs.map +1 -1
- package/esm/client/runtime/types.d.mts +1 -1
- package/esm/components/link.mjs.map +1 -1
- package/esm/core/src/router/normalize-route-path.mjs +33 -0
- package/esm/core/src/router/normalize-route-path.mjs.map +1 -0
- package/esm/{hydration → entry}/index.mjs +1 -1
- package/esm/entry/index.mjs.map +1 -0
- package/esm/hydration-payload.mjs +19 -10
- package/esm/hydration-payload.mjs.map +1 -1
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +3 -2
- package/esm/loaders.d.mts +13 -1
- package/esm/localization.mjs.map +1 -1
- package/esm/metadata.d.mts +4 -4
- package/esm/metadata.mjs.map +1 -1
- package/esm/node_modules/.pnpm/@mongez_concat-route@1.2.0/node_modules/@mongez/concat-route/esm/index.mjs +33 -0
- package/esm/node_modules/.pnpm/@mongez_concat-route@1.2.0/node_modules/@mongez/concat-route/esm/index.mjs.map +1 -0
- package/esm/{runtime/register-modules.d.mts → register-modules.d.mts} +1 -1
- package/esm/{runtime/register-modules.mjs → register-modules.mjs} +1 -1
- package/esm/register-modules.mjs.map +1 -0
- package/esm/route.d.mts +9 -0
- package/esm/routing/duplicate-route-path.mjs +22 -0
- package/esm/routing/duplicate-route-path.mjs.map +1 -0
- package/esm/routing/layout-level.mjs +50 -0
- package/esm/routing/layout-level.mjs.map +1 -0
- package/esm/routing/layout-policy.mjs.map +1 -1
- package/esm/routing/query-string.d.mts +23 -1
- package/esm/routing/query-string.mjs +23 -1
- package/esm/routing/query-string.mjs.map +1 -1
- package/esm/routing/route-identity.mjs +27 -2
- package/esm/routing/route-identity.mjs.map +1 -1
- package/esm/routing/route-table.mjs.map +1 -1
- package/esm/routing/router-events.d.mts +5 -4
- package/esm/routing/router-events.mjs.map +1 -1
- package/esm/server/create-page-route-handler.d.mts +2 -2
- package/esm/server/create-page-route-handler.mjs +18 -12
- package/esm/server/create-page-route-handler.mjs.map +1 -1
- package/esm/server/{dev-server.mjs → dev-error-transport.mjs} +2 -2
- package/esm/server/dev-error-transport.mjs.map +1 -0
- package/esm/server/execute-page-request.mjs +91 -35
- package/esm/server/execute-page-request.mjs.map +1 -1
- package/esm/server/execute-page-request.types.d.mts +21 -1
- package/esm/server/fold-layout-loaders.mjs +36 -0
- package/esm/server/fold-layout-loaders.mjs.map +1 -0
- package/esm/server/hydration-client-url.mjs +3 -3
- package/esm/server/hydration-client-url.mjs.map +1 -1
- package/esm/server/index.d.mts +4 -3
- package/esm/server/index.mjs +6 -6
- package/esm/server/install-page-routes-from-manifest.d.mts +10 -1
- package/esm/server/install-page-routes-from-manifest.mjs +61 -58
- package/esm/server/install-page-routes-from-manifest.mjs.map +1 -1
- package/esm/server/install-page-routes.d.mts +24 -26
- package/esm/server/install-page-routes.mjs +149 -60
- package/esm/server/install-page-routes.mjs.map +1 -1
- package/esm/server/install-production-page-routes.mjs.map +1 -1
- package/esm/server/match-page-route.mjs +6 -10
- package/esm/server/match-page-route.mjs.map +1 -1
- package/esm/server/not-found-page.d.mts +1 -0
- package/esm/server/not-found-page.mjs +8 -1
- package/esm/server/not-found-page.mjs.map +1 -1
- package/esm/server/page-file-change.mjs +1 -1
- package/esm/server/page-file-change.mjs.map +1 -1
- package/esm/server/page-module-shapes.d.mts +24 -0
- package/esm/server/page-route-reload.mjs +4 -3
- package/esm/server/page-route-reload.mjs.map +1 -1
- package/esm/server/register-production-public-files.mjs +26 -2
- package/esm/server/register-production-public-files.mjs.map +1 -1
- package/esm/server/render-page.d.mts +13 -49
- package/esm/server/render-page.mjs +14 -55
- package/esm/server/render-page.mjs.map +1 -1
- package/esm/server/resolve-page-metadata.mjs.map +1 -1
- package/esm/server/resolve-route-validation-input.mjs +11 -0
- package/esm/server/resolve-route-validation-input.mjs.map +1 -0
- package/esm/server/resolve-validation-data.mjs +1 -1
- package/esm/server/resolve-validation-data.mjs.map +1 -1
- package/esm/server/settle-page-response.mjs +5 -3
- package/esm/server/settle-page-response.mjs.map +1 -1
- package/esm/server/stylesheet-urls.mjs +78 -19
- package/esm/server/stylesheet-urls.mjs.map +1 -1
- package/esm/server/unregistered-pages.mjs +2 -1
- package/esm/server/unregistered-pages.mjs.map +1 -1
- package/esm/server/web-connector-factory.mjs +1 -1
- package/esm/server/web-connector-factory.mjs.map +1 -1
- package/esm/server/web-connector.mjs +25 -322
- package/esm/server/web-connector.mjs.map +1 -1
- package/esm/shared/module-key.mjs +15 -0
- package/esm/shared/module-key.mjs.map +1 -0
- package/esm/shared/to-posix.mjs +13 -0
- package/esm/shared/to-posix.mjs.map +1 -0
- package/esm/shared.mjs +24 -0
- package/esm/shared.mjs.map +1 -1
- package/esm/validation.d.mts +12 -1
- package/esm/vite/build-client.mjs +1 -1
- package/esm/vite/build-client.mjs.map +1 -1
- package/esm/vite/dev-server-config.mjs +240 -0
- package/esm/vite/dev-server-config.mjs.map +1 -0
- package/esm/vite/gate-a-resolve.d.mts +5 -4
- package/esm/vite/gate-a-resolve.mjs +32 -71
- package/esm/vite/gate-a-resolve.mjs.map +1 -1
- package/esm/vite/gate-b-secrets.mjs.map +1 -1
- package/esm/vite/gate-c-verify.d.mts +1 -1
- package/esm/vite/gate-c-verify.mjs +1 -1
- package/esm/vite/gate-c-verify.mjs.map +1 -1
- package/esm/vite/hydration-entries.mjs +6 -8
- package/esm/vite/hydration-entries.mjs.map +1 -1
- package/esm/vite/index.d.mts +1 -1
- package/esm/vite/index.mjs +4 -110
- package/esm/vite/index.mjs.map +1 -1
- package/esm/vite/page-registry-plugin.mjs +9 -26
- package/esm/vite/page-registry-plugin.mjs.map +1 -1
- package/esm/vite/projection.mjs.map +1 -1
- package/esm/vite/react-refresh-preamble.mjs +90 -0
- package/esm/vite/react-refresh-preamble.mjs.map +1 -0
- package/esm/vite/ssr-client-view.mjs +135 -0
- package/esm/vite/ssr-client-view.mjs.map +1 -0
- package/llms-full.txt +564 -482
- package/llms.txt +2 -2
- package/package.json +6 -6
- package/skills/create-a-page/SKILL.md +354 -295
- package/skills/navigate-on-the-client/SKILL.md +212 -189
- package/esm/hydration/index.mjs.map +0 -1
- package/esm/runtime/register-modules.mjs.map +0 -1
- package/esm/server/dev-server.mjs.map +0 -1
- /package/esm/{hydration → entry}/index.d.mts +0 -0
|
@@ -12,7 +12,7 @@ import { fileURLToPath } from "node:url";
|
|
|
12
12
|
* WHY THIS MODULE EXISTS AT ALL, rather than the barrel exporting
|
|
13
13
|
* `WebConnector` directly: `./web-connector.ts` imports `../vite`
|
|
14
14
|
* (`web-connector.ts:52` → `@babel/parser` + `magic-string`),
|
|
15
|
-
* `../../../core/src/router/router` (`:51`) and `./dev-
|
|
15
|
+
* `../../../core/src/router/router` (`:51`) and `./dev-error-transport` (`:53`, which
|
|
16
16
|
* itself pulls core's http stack and `../vite`) at VALUE level. Re-exporting
|
|
17
17
|
* that class from `web/src/connector/index.ts` would drag every one of those
|
|
18
18
|
* into the static graph of every consuming app's config file — the exact
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-connector-factory.mjs","names":[],"sources":["../../../../../../../web/src/server/web-connector-factory.ts"],"sourcesContent":["/**\n * `webConnector()` — the ONE thing `warlock.config.ts` imports from\n * `@warlock.js/web/connector`, and the only value the `./connector` barrel\n * adds beyond the build/runtime seams.\n *\n * WHY THIS MODULE EXISTS AT ALL, rather than the barrel exporting\n * `WebConnector` directly: `./web-connector.ts` imports `../vite`\n * (`web-connector.ts:52` → `@babel/parser` + `magic-string`),\n * `../../../core/src/router/router` (`:51`) and `./dev-
|
|
1
|
+
{"version":3,"file":"web-connector-factory.mjs","names":[],"sources":["../../../../../../../web/src/server/web-connector-factory.ts"],"sourcesContent":["/**\n * `webConnector()` — the ONE thing `warlock.config.ts` imports from\n * `@warlock.js/web/connector`, and the only value the `./connector` barrel\n * adds beyond the build/runtime seams.\n *\n * WHY THIS MODULE EXISTS AT ALL, rather than the barrel exporting\n * `WebConnector` directly: `./web-connector.ts` imports `../vite`\n * (`web-connector.ts:52` → `@babel/parser` + `magic-string`),\n * `../../../core/src/router/router` (`:51`) and `./dev-error-transport` (`:53`, which\n * itself pulls core's http stack and `../vite`) at VALUE level. Re-exporting\n * that class from `web/src/connector/index.ts` would drag every one of those\n * into the static graph of every consuming app's config file — the exact\n * config-load weight the `./connector` subpath was created to prevent.\n *\n * So the factory returns a LAZY DELEGATE: a `Connector` whose identity fields\n * (`name`, `priority`, `lifecyclePhase`, `build`) are plain data available\n * synchronously, and whose lifecycle methods `await import(\"./web-connector\")`\n * on first use. `warlock build` reads `build` off this object and never boots\n * anything, so a build never loads Vite or React through\n * here either. This is the third instance of a pattern the codebase already\n * uses twice — `core/src/connectors/access-connector.ts:39` and\n * `web/src/server/dev-cli.ts:61` — not a new one.\n *\n * KEEP THIS MODULE LIGHT. Its whole value-level static graph is `node:path`,\n * `node:url`, `../../../core/src/connectors/types` (whose own two imports are\n * both `import type` and therefore erased — `core/src/connectors/types.ts:1-2`)\n * and `../build/contribution` (`node:fs` + `node:path` + type-only core).\n * Everything else here is `import type`, which `verbatimModuleSyntax` erases.\n */\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n type Connector,\n type ConnectorBuildContribution,\n ConnectorLifecyclePhase,\n type ConnectorName,\n} from \"@warlock.js/core\";\nimport { createWebBuildContribution, type WebBuildOptions } from \"../build/contribution\";\nimport type { WebConnector, WebConnectorOptions } from \"./web-connector\";\n\n/**\n * Boot/shutdown position relative to core's own connectors.\n *\n * `ConnectorPriority.HTTP` is `5` and `ConnectorPriority.STORAGE` is `6`\n * (`core/src/connectors/types.ts:187-188`), and the manager sorts on a plain\n * numeric compare (`core/src/connectors/connectors-manager.ts:46`) — so `5.5`\n * is \"immediately after http, before everything else\".\n *\n * Defined HERE and re-exported by `./web-connector` rather than the other way\n * round: the delegate must publish `priority` synchronously, and reading it\n * from the heavy module would defeat the whole point of the delegate.\n */\nexport const WEB_CONNECTOR_PRIORITY = 5.5;\n\nexport type WebConnectorFactoryOptions = WebConnectorOptions & {\n /**\n * What web contributes to `warlock build`. Passed straight to\n * {@link createWebBuildContribution}; JSON-serializable values only.\n */\n build?: WebBuildOptions;\n};\n\n/**\n * The `@warlock.js/web` package root, derived once from THIS module's location\n * and then passed EXPLICITLY to both halves.\n *\n * `web/src/server/web-connector-factory.ts` → `web/src/server` → `web/src` →\n * `web`; published as `web/esm/server/web-connector-factory.js`, two levels up\n * is the package root under both layouts — the same arithmetic\n * `contribution.ts:136-140` documents.\n *\n * Deriving it here and handing it down means the build contribution never falls\n * back to its own `import.meta.url` guess (`contribution.ts:129-141`): one\n * derivation, one place to be wrong, and `assertWebPackageRoot` verifies it\n * against `<root>/package.json`'s `name` either way.\n */\nfunction deriveWebRoot(): string {\n return path.resolve(path.dirname(fileURLToPath(import.meta.url)), \"..\", \"..\");\n}\n\n/**\n * Construct web's connector for `warlock.config.ts > connectors`.\n *\n * @example\n * ```ts\n * export default defineConfig({ connectors: [webConnector()] });\n * ```\n */\nexport function webConnector(options: WebConnectorFactoryOptions = {}): Connector {\n const { build: buildOptions, ...connectorOptions } = options;\n const webRoot = connectorOptions.webRoot ?? deriveWebRoot();\n // One snapshot feeds both halves. Mutating the caller's array after config\n // construction cannot make dev and `warlock build` observe different lists.\n const connectorPlugins = [...(connectorOptions.plugins ?? [])];\n\n const build: ConnectorBuildContribution = createWebBuildContribution(\n {\n ...buildOptions,\n webRoot: buildOptions?.webRoot ?? webRoot,\n },\n connectorPlugins,\n );\n\n let instance: WebConnector | undefined;\n\n /**\n * Load the heavy half on first lifecycle call. `boot()` is always the first\n * of these to run (`core/src/connectors/connectors-manager.ts:87-93`), so the\n * import lands in a process that has already committed to serving pages.\n */\n const load = async (): Promise<WebConnector> => {\n if (!instance) {\n const { WebConnector: WebConnectorClass } = await import(\"./web-connector\");\n\n instance = new WebConnectorClass({\n ...connectorOptions,\n plugins: connectorPlugins,\n webRoot,\n });\n }\n\n return instance;\n };\n\n return {\n name: \"web\" satisfies ConnectorName,\n priority: WEB_CONNECTOR_PRIORITY,\n lifecyclePhase: ConnectorLifecyclePhase.Late,\n build,\n\n // Never loads the heavy half: a connector that was never booted is not\n // active, and answering that must not cost a Vite import.\n isActive: () => instance?.isActive() ?? false,\n\n boot: async () => {\n await (await load()).boot();\n },\n start: async () => {\n await (await load()).start();\n },\n restart: async () => {\n await (await load()).restart();\n },\n\n // Both of these are asked of every registered connector, including ones\n // that never booted — so neither may force the import.\n shutdown: async () => {\n await instance?.shutdown();\n },\n\n // Asked on EVERY watcher batch, so the `instance?.` is load-bearing twice:\n // it keeps the answer free for a connector that never booted, and it is the\n // only reason this delegate can forward the question at all — by the time a\n // watcher batch exists, `boot()` has already loaded the heavy half, so\n // reading the real answer off it costs nothing beyond the call.\n //\n // It used to be a hard `false`, which meant a `*.page.tsx` created while\n // `warlock dev` was running was never noticed: page routes are installed\n // once, in `WebConnector.boot()`, so the new page's URL 404'd in silence.\n // The connector itself now decides: add/remove and route-identity edits are\n // live route-table work; component-body-only edits remain Vite HMR work.\n shouldRestart: (changedFiles: string[]) => instance?.shouldRestart(changedFiles) ?? false,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,MAAa,yBAAyB;;;;;;;;;;;;;;;AAwBtC,SAAS,gBAAwB;CAC/B,OAAO,KAAK,QAAQ,KAAK,QAAQ,cAAc,OAAO,KAAK,GAAG,CAAC,GAAG,MAAM,IAAI;AAC9E;;;;;;;;;AAUA,SAAgB,aAAa,UAAsC,CAAC,GAAc;CAChF,MAAM,EAAE,OAAO,cAAc,GAAG,qBAAqB;CACrD,MAAM,UAAU,iBAAiB,WAAW,cAAc;CAG1D,MAAM,mBAAmB,CAAC,GAAI,iBAAiB,WAAW,CAAC,CAAE;CAE7D,MAAM,QAAoC,2BACxC;EACE,GAAG;EACH,SAAS,cAAc,WAAW;CACpC,GACA,gBACF;CAEA,IAAI;;;;;;CAOJ,MAAM,OAAO,YAAmC;EAC9C,IAAI,CAAC,UAAU;GACb,MAAM,EAAE,cAAc,sBAAsB,MAAM,OAAO;GAEzD,WAAW,IAAI,kBAAkB;IAC/B,GAAG;IACH,SAAS;IACT;GACF,CAAC;EACH;EAEA,OAAO;CACT;CAEA,OAAO;EACL,MAAM;EACN,UAAU;EACV,gBAAgB,wBAAwB;EACxC;EAIA,gBAAgB,UAAU,SAAS,KAAK;EAExC,MAAM,YAAY;GAChB,OAAO,MAAM,KAAK,EAAC,CAAE,KAAK;EAC5B;EACA,OAAO,YAAY;GACjB,OAAO,MAAM,KAAK,EAAC,CAAE,MAAM;EAC7B;EACA,SAAS,YAAY;GACnB,OAAO,MAAM,KAAK,EAAC,CAAE,QAAQ;EAC/B;EAIA,UAAU,YAAY;GACpB,MAAM,UAAU,SAAS;EAC3B;EAaA,gBAAgB,iBAA2B,UAAU,cAAc,YAAY,KAAK;CACtF;AACF"}
|
|
@@ -5,17 +5,17 @@ import { createHydrationClientEntry } from "../vite/hydration-entries.mjs";
|
|
|
5
5
|
import { resolveHydrationClientUrl } from "./hydration-client-url.mjs";
|
|
6
6
|
import { WEB_CONNECTOR_PRIORITY } from "./web-connector-factory.mjs";
|
|
7
7
|
import { invalidateClientPageRegistry } from "../vite/page-registry-plugin.mjs";
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import { devErrorTransportPlugin, sendCapturedDevError } from "./dev-
|
|
8
|
+
import "../vite/index.mjs";
|
|
9
|
+
import { createWebConnectorViteConfig } from "../vite/dev-server-config.mjs";
|
|
10
|
+
import { devErrorTransportPlugin, sendCapturedDevError } from "./dev-error-transport.mjs";
|
|
11
11
|
import { installProductionPageRoutes } from "./install-production-page-routes.mjs";
|
|
12
12
|
import { classifyPageFileChanges, hasPageFileChanges } from "./page-file-change.mjs";
|
|
13
13
|
import { pageRouteSourceFiles, pageRoutesNeedReplacement, registeredPageFiles } from "./page-route-reload.mjs";
|
|
14
14
|
import { registerProductionPublicFiles } from "./register-production-public-files.mjs";
|
|
15
15
|
import { createUnregisteredPageReporter } from "./unregistered-pages.mjs";
|
|
16
16
|
import { Application, BaseConnector, ConnectorLifecyclePhase, container, requestContext, router } from "@warlock.js/core";
|
|
17
|
-
import fs from "node:fs";
|
|
18
17
|
import path from "node:path";
|
|
18
|
+
import fs from "node:fs";
|
|
19
19
|
import { fileURLToPath } from "node:url";
|
|
20
20
|
|
|
21
21
|
//#region ../web/src/server/web-connector.ts
|
|
@@ -25,7 +25,7 @@ import { fileURLToPath } from "node:url";
|
|
|
25
25
|
* It runs BESIDE `HttpConnector`, never instead of it: `warlock dev` alone now boots the API *and* serves React
|
|
26
26
|
* pages on one port, and `web` no longer owns a private copy of the HTTP
|
|
27
27
|
* lifecycle. Everything this file does used to live in `startDevServer()`
|
|
28
|
-
* (`web/src/server/dev-
|
|
28
|
+
* (`web/src/server/dev-error-transport.ts`), which created its own Fastify instance,
|
|
29
29
|
* scanned the router and called `listen()` itself — three responsibilities core
|
|
30
30
|
* already owns at `core/src/connectors/http-connector.ts:72`, `:133` and `:147`.
|
|
31
31
|
*
|
|
@@ -50,7 +50,7 @@ import { fileURLToPath } from "node:url";
|
|
|
50
50
|
* the ordinary `router.get(…)` API — there is no second server matcher.
|
|
51
51
|
*
|
|
52
52
|
* DELIBERATE EXCEPTION to A.3 §2 ("web has no core dependency"), the same one
|
|
53
|
-
* `./dev-
|
|
53
|
+
* `./dev-error-transport.ts` and `./install-page-routes.ts` record in their own headers:
|
|
54
54
|
* this module is not re-exported from any package barrel (`web/src/index.ts`,
|
|
55
55
|
* `web/src/server/index.ts`, `web/src/connector/index.ts`) and is not part of
|
|
56
56
|
* `web/package.json`'s dependency graph. It is dev/CLI bootstrap code, only
|
|
@@ -59,7 +59,7 @@ import { fileURLToPath } from "node:url";
|
|
|
59
59
|
* `@warlock.js/web/connector` reaches this class ONLY through
|
|
60
60
|
* `./web-connector-factory.ts`'s `await import("./web-connector")` — a
|
|
61
61
|
* deliberate seam, because a static edge from that barrel to this file would
|
|
62
|
-
* put `../vite`, core's router and `./dev-
|
|
62
|
+
* put `../vite`, core's router and `./dev-error-transport` into the import graph of
|
|
63
63
|
* every consuming app's `warlock.config.ts`.
|
|
64
64
|
*/
|
|
65
65
|
/**
|
|
@@ -133,58 +133,6 @@ var WebClientDirMissingError = class extends Error {
|
|
|
133
133
|
}
|
|
134
134
|
};
|
|
135
135
|
/**
|
|
136
|
-
* Third-party packages core reaches through `await import(...)` and that must
|
|
137
|
-
* therefore never enter Vite's SSR transform graph.
|
|
138
|
-
*
|
|
139
|
-
* Left un-externalized, Vite's SSR module runner tries to resolve them anyway
|
|
140
|
-
* and jams: the failure is NOT a missing-module error but a `transport invoke
|
|
141
|
-
* timed out` on whatever unrelated module happened to be in flight. Derived in
|
|
142
|
-
* one pass from `peerDependenciesMeta.optional` across every workspace package
|
|
143
|
-
* reachable from `core/src/index.ts`, and carried over verbatim from
|
|
144
|
-
* `dev-server.ts`'s own list. Only THIRD-PARTY peers belong here — every
|
|
145
|
-
* `@warlock.js/*` sibling must stay in Vite's graph.
|
|
146
|
-
*
|
|
147
|
-
* This list is core's peer list, not web's; publishing it from core instead
|
|
148
|
-
* of duplicating it here is still outstanding.
|
|
149
|
-
*/
|
|
150
|
-
const CORE_OPTIONAL_PEERS = [
|
|
151
|
-
"nodemailer",
|
|
152
|
-
"@aws-sdk/client-sesv2",
|
|
153
|
-
"@react-email/render",
|
|
154
|
-
"redis",
|
|
155
|
-
"pg",
|
|
156
|
-
"mongodb",
|
|
157
|
-
"@sentry/node",
|
|
158
|
-
"sharp",
|
|
159
|
-
"socket.io",
|
|
160
|
-
"@aws-sdk/client-s3",
|
|
161
|
-
"@aws-sdk/lib-storage",
|
|
162
|
-
"@aws-sdk/s3-request-presigner",
|
|
163
|
-
"amqplib",
|
|
164
|
-
"langfuse",
|
|
165
|
-
"openai",
|
|
166
|
-
"pdf-parse"
|
|
167
|
-
];
|
|
168
|
-
/**
|
|
169
|
-
* `web`'s OWN optional peers, kept separate from {@link CORE_OPTIONAL_PEERS}
|
|
170
|
-
* because that list is core's and is documented as such.
|
|
171
|
-
*
|
|
172
|
-
* Same rule, same reason: anything this package reaches through
|
|
173
|
-
* `await import(...)` must be external to every bundler and SSR pipeline, or
|
|
174
|
-
* some pipeline will try to resolve it in an install that legitimately does not
|
|
175
|
-
* have it. `vite` itself is the precedent — `createViteServer` has always
|
|
176
|
-
* loaded it this way — and `@vitejs/plugin-react` is loaded from the same
|
|
177
|
-
* function, for the same dev-only reason, so it belongs in the same set.
|
|
178
|
-
*/
|
|
179
|
-
const WEB_OPTIONAL_PEERS = ["vite", "@vitejs/plugin-react"];
|
|
180
|
-
/**
|
|
181
|
-
* Public specifier for the React Fast Refresh preamble module this connector
|
|
182
|
-
* injects. See {@link WebConnector.resolveReactFastRefreshPlugins}.
|
|
183
|
-
*/
|
|
184
|
-
const REACT_REFRESH_PREAMBLE_ID = "virtual:warlock/react-refresh-preamble";
|
|
185
|
-
/** The `\0`-prefixed form Rollup uses to mark a module as not-a-file. */
|
|
186
|
-
const RESOLVED_REACT_REFRESH_PREAMBLE_ID = `\0${REACT_REFRESH_PREAMBLE_ID}`;
|
|
187
|
-
/**
|
|
188
136
|
* `@fastify/static`'s `maxAge` is milliseconds (the `send` package's option,
|
|
189
137
|
* not seconds like `Cache-Control`'s own `max-age`) — one year, matching the
|
|
190
138
|
* header this produces: `public, max-age=31536000, immutable`. Safe forever
|
|
@@ -207,28 +155,6 @@ function productionAssetsDirectoryOptions(clientDir) {
|
|
|
207
155
|
};
|
|
208
156
|
}
|
|
209
157
|
/**
|
|
210
|
-
* A directory, plus the path the filesystem really stores it at when the two
|
|
211
|
-
* differ. Both forms belong in `server.fs.allow` — see the `fs` block in
|
|
212
|
-
* {@link WebConnector.createViteServer} for why one of them is never enough.
|
|
213
|
-
*
|
|
214
|
-
* A missing directory is not this function's problem to report: the roots it is
|
|
215
|
-
* handed are already proven (`resolveWebPackageRoot`) or are the app's own cwd,
|
|
216
|
-
* and an allow-list entry that points nowhere simply matches nothing.
|
|
217
|
-
*/
|
|
218
|
-
function withRealPath(directory) {
|
|
219
|
-
try {
|
|
220
|
-
const realPath = fs.realpathSync(directory);
|
|
221
|
-
return realPath === directory ? [directory] : [directory, realPath];
|
|
222
|
-
} catch {
|
|
223
|
-
return [directory];
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
/** POSIX-normalised, case-folded — Vite ids are `/`-separated on Windows too. */
|
|
227
|
-
function normalizeModuleId(id) {
|
|
228
|
-
const [filepath] = id.split("?");
|
|
229
|
-
return filepath.replace(/\\/g, "/").toLowerCase();
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
158
|
* Web Connector
|
|
233
159
|
* Manages the Vite dev server and the SSR page routes, mounted on the HTTP
|
|
234
160
|
* connector's Fastify instance.
|
|
@@ -291,7 +217,7 @@ var WebConnector = class extends BaseConnector {
|
|
|
291
217
|
this.pageManifest = consumePageManifest();
|
|
292
218
|
if (isProductionRuntime() && !this.pageManifest) throw new WebPageManifestMissingError();
|
|
293
219
|
if (isProductionRuntime() && this.pageManifest) {
|
|
294
|
-
if (
|
|
220
|
+
if (this.pageManifest.clientDir !== void 0) registerProductionPublicFiles(router, this.resolveClientDir(), this.pageManifest.publicFiles ?? []);
|
|
295
221
|
this.installedPages = await installProductionPageRoutes({
|
|
296
222
|
router,
|
|
297
223
|
manifest: this.pageManifest,
|
|
@@ -334,8 +260,9 @@ var WebConnector = class extends BaseConnector {
|
|
|
334
260
|
vite: this.vite,
|
|
335
261
|
appSrcRoot: paths.appSrcRoot,
|
|
336
262
|
appFile: paths.appFile,
|
|
263
|
+
appRoot: paths.appRoot,
|
|
337
264
|
hydrationClientModuleUrl: this.resolveHydrationClientModuleUrl(paths.webRoot),
|
|
338
|
-
|
|
265
|
+
httpServer: fastify
|
|
339
266
|
});
|
|
340
267
|
this.installedPages = await this.installDevPageRoutes();
|
|
341
268
|
}
|
|
@@ -523,248 +450,24 @@ var WebConnector = class extends BaseConnector {
|
|
|
523
450
|
webServerBarrel: path.join(path.dirname(selfPath), `index${path.extname(selfPath)}`)
|
|
524
451
|
};
|
|
525
452
|
}
|
|
526
|
-
/**
|
|
527
|
-
* Vite in middleware mode, `appType: "custom"` — Warlock owns the response
|
|
528
|
-
* shape and Vite never fronts the server.
|
|
529
|
-
*
|
|
530
|
-
* `server.hmr.server` is handed the RAW node server (`fastify.server`), so the
|
|
531
|
-
* HMR websocket shares the one port the app already listens on. No `hmr.port`
|
|
532
|
-
* and no `clientPort`: Vite's HMR path stays the default `"/"`, socket.io
|
|
533
|
-
* stays on `"/socket.io"` (`core/src/connectors/socket-connector.ts:90`), and
|
|
534
|
-
* the two `upgrade` listeners coexist because each is a selective filter that
|
|
535
|
-
* leaves a non-matching socket alone — verified empirically in both attachment
|
|
536
|
-
* orders.
|
|
537
|
-
*/
|
|
538
|
-
/**
|
|
539
|
-
* React Fast Refresh, or nothing at all — never half of it.
|
|
540
|
-
*
|
|
541
|
-
* TWO plugins, and the second one is not optional. `@vitejs/plugin-react`
|
|
542
|
-
* emits, into every client component module, a wrapper that reads
|
|
543
|
-
* `window.$RefreshReg$` and THROWS "can't detect preamble" if it is missing.
|
|
544
|
-
* The preamble that defines it normally arrives through Vite's
|
|
545
|
-
* `transformIndexHtml`, and this pipeline has no HTML for Vite to transform:
|
|
546
|
-
* the document is React's SSR output and the only script appended to it is
|
|
547
|
-
* the hydration entry (`./create-page-route-handler.ts:95`). So the preamble
|
|
548
|
-
* is delivered as a virtual module that the hydration entry imports FIRST.
|
|
549
|
-
*
|
|
550
|
-
* "First" is load-bearing, twice over. ESM evaluates a module's imports in
|
|
551
|
-
* source order before the importer's own body, so an import placed at
|
|
552
|
-
* position 0 runs before `virtual:warlock/pages` pulls in a single component
|
|
553
|
-
* — which is what `$RefreshReg$` has to exist for — and before `react-dom`
|
|
554
|
-
* initialises, which is what `injectIntoGlobalHook` has to precede.
|
|
555
|
-
*
|
|
556
|
-
* The preamble TEXT is read from the plugin's own `preambleCode` export
|
|
557
|
-
* rather than copied here, so a version bump cannot leave this file holding
|
|
558
|
-
* a stale runtime contract. If that export ever stops being a string, Fast
|
|
559
|
-
* Refresh is DECLINED entirely (empty array) instead of registered without
|
|
560
|
-
* its preamble — a loud "no HMR" beats a component graph that throws on
|
|
561
|
-
* first paint.
|
|
562
|
-
*
|
|
563
|
-
* Dev-only by construction, like everything else this method is called from:
|
|
564
|
-
* `apply: "serve"` on our own plugin, `apply: "serve"` on the plugin's
|
|
565
|
-
* refresh half, and `skipFastRefresh` on `config.isProduction` inside it. The
|
|
566
|
-
* PRODUCTION client bundle cannot reach any of this — it is built by
|
|
567
|
-
* `buildWarlockHydrationClient` (`../vite/index.ts:185`), which composes
|
|
568
|
-
* `warlockClientBoundary()` and nothing else, and never calls this method.
|
|
569
|
-
*
|
|
570
|
-
* @param paths resolved by {@link WebConnector.resolvePaths}
|
|
571
|
-
*/
|
|
572
|
-
async resolveReactFastRefreshPlugins(paths) {
|
|
573
|
-
let viteReact;
|
|
574
|
-
try {
|
|
575
|
-
({default: viteReact} = await import("@vitejs/plugin-react"));
|
|
576
|
-
} catch {
|
|
577
|
-
console.warn("[warlock:web] React Fast Refresh is OFF: `@vitejs/plugin-react` is not installed. Edits to a component will reload the page instead of hot-swapping it. Install it with `npm i -D @vitejs/plugin-react` to enable it.");
|
|
578
|
-
return [];
|
|
579
|
-
}
|
|
580
|
-
const preambleCode = viteReact.preambleCode;
|
|
581
|
-
if (typeof preambleCode !== "string") {
|
|
582
|
-
console.warn("[warlock:web] React Fast Refresh is OFF: this `@vitejs/plugin-react` no longer exports `preambleCode`, so the refresh preamble cannot be injected into the hydration entry. Registering the plugin without it would make every component module throw \"can't detect preamble\" in the browser.");
|
|
583
|
-
return [];
|
|
584
|
-
}
|
|
585
|
-
const preambleSource = preambleCode.replace("__BASE__", "/");
|
|
586
|
-
const hydrationEntryId = normalizeModuleId(createHydrationClientEntry(paths.webRoot).sourcePath);
|
|
587
|
-
return [{
|
|
588
|
-
name: "warlock:react-refresh-preamble",
|
|
589
|
-
enforce: "pre",
|
|
590
|
-
apply: "serve",
|
|
591
|
-
applyToEnvironment: (environment) => environment.config.consumer === "client",
|
|
592
|
-
resolveId(source) {
|
|
593
|
-
if (source === REACT_REFRESH_PREAMBLE_ID) return RESOLVED_REACT_REFRESH_PREAMBLE_ID;
|
|
594
|
-
return null;
|
|
595
|
-
},
|
|
596
|
-
load(id) {
|
|
597
|
-
if (id === RESOLVED_REACT_REFRESH_PREAMBLE_ID) return preambleSource;
|
|
598
|
-
return null;
|
|
599
|
-
},
|
|
600
|
-
transform(code, id) {
|
|
601
|
-
if (normalizeModuleId(id) !== hydrationEntryId) return null;
|
|
602
|
-
return {
|
|
603
|
-
code: `import ${JSON.stringify(REACT_REFRESH_PREAMBLE_ID)};\n${code}`,
|
|
604
|
-
map: null
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
}, ...viteReact()];
|
|
608
|
-
}
|
|
609
453
|
async createViteServer(fastify, paths) {
|
|
610
454
|
const { createServer, buildErrorMessage, searchForWorkspaceRoot } = await import("vite");
|
|
611
455
|
const workspaceRoot = searchForWorkspaceRoot(paths.appRoot);
|
|
612
|
-
return createServer({
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
server: {
|
|
628
|
-
middlewareMode: true,
|
|
629
|
-
hmr: { server: fastify.server },
|
|
630
|
-
fs: { allow: [...new Set([
|
|
631
|
-
...withRealPath(workspaceRoot),
|
|
632
|
-
...withRealPath(paths.appRoot),
|
|
633
|
-
...withRealPath(paths.webRoot)
|
|
634
|
-
])] }
|
|
635
|
-
},
|
|
636
|
-
esbuild: {
|
|
637
|
-
target: "es2022",
|
|
638
|
-
jsx: "automatic"
|
|
639
|
-
},
|
|
640
|
-
/**
|
|
641
|
-
* REACT MUST BE PRE-BUNDLED, and naming it here is the only thing that
|
|
642
|
-
* makes that happen for the framework's own client graph.
|
|
643
|
-
*
|
|
644
|
-
* `react-dom/client` is CommonJS. A browser cannot import a named export
|
|
645
|
-
* from it, so Vite's dep optimizer normally rewrites it into an ESM shim
|
|
646
|
-
* under `/node_modules/.vite/deps/`. Whether that rewrite happens is
|
|
647
|
-
* decided in `tryNodeResolve`, and one of the conditions that SKIPS it is
|
|
648
|
-
* `importer && isInNodeModules(importer)`
|
|
649
|
-
* (`vite/dist/node/chunks/config.js:32822`). Vite's reasoning is sound in
|
|
650
|
-
* general — a dependency's own internal imports are the optimizer's job,
|
|
651
|
-
* not the resolver's — but it is exactly wrong here:
|
|
652
|
-
*
|
|
653
|
-
* `<Scripts />` points the browser at the hydration entry, and in an
|
|
654
|
-
* INSTALLED app that entry is
|
|
655
|
-
* `<app>/node_modules/@warlock.js/web/esm/hydration/index.mjs`.
|
|
656
|
-
* Every module it reaches is therefore inside `node_modules`, so every
|
|
657
|
-
* bare import it makes takes the skip branch and is served as the raw
|
|
658
|
-
* file with a `?v=<browserHash>` cache key bolted on.
|
|
659
|
-
*
|
|
660
|
-
* For `esm/**.mjs` that is harmless — they are already ESM. For
|
|
661
|
-
* `react-dom/client` it is fatal, and it is the whole defect: the browser
|
|
662
|
-
* receives `"use strict"; function checkDCE()…` and refuses the module
|
|
663
|
-
* with
|
|
664
|
-
*
|
|
665
|
-
* SyntaxError: The requested module '/node_modules/react-dom/client.js?v=…'
|
|
666
|
-
* does not provide an export named 'hydrateRoot'
|
|
667
|
-
*
|
|
668
|
-
* Nothing in the client runtime then runs at all: no hydration, so no
|
|
669
|
-
* `useState`, no Fast Refresh, and `<Link>` degrades to a full document
|
|
670
|
-
* load because the navigation listener was never attached. Measured on a
|
|
671
|
-
* published 5.0.2 install (fresh app, no symlinks): all four symptoms
|
|
672
|
-
* before this block, none after.
|
|
673
|
-
*
|
|
674
|
-
* `include` is the fix rather than `entries` because it does not depend
|
|
675
|
-
* on the scanner reaching the entry: an included id is pre-bundled at
|
|
676
|
-
* server start and `tryOptimizedResolve` matches it by NAME
|
|
677
|
-
* (`config.js:32633`) before `tryNodeResolve` — and that lookup has no
|
|
678
|
-
* importer condition, so a node_modules importer resolves to the shim
|
|
679
|
-
* like anyone else. Pointing `optimizeDeps.entries` at the hydration
|
|
680
|
-
* entry would not work: the scanner classifies a resolution inside
|
|
681
|
-
* `node_modules` as a dependency to externalize rather than a source to
|
|
682
|
-
* walk.
|
|
683
|
-
*
|
|
684
|
-
* All four names are listed even though `react` and `react/jsx-runtime`
|
|
685
|
-
* usually get discovered anyway — they are discovered only because the
|
|
686
|
-
* APP's own pages import them, which is a fact about the app and not
|
|
687
|
-
* something the framework may rely on. `react-dom/client` is imported by
|
|
688
|
-
* `client/hydrate-page.tsx` and by nothing a normal app writes, which is
|
|
689
|
-
* why it was the one that broke.
|
|
690
|
-
*
|
|
691
|
-
* This is INVISIBLE from the monorepo checkout: there the hydration entry
|
|
692
|
-
* resolves to `web/src/hydration/index.ts`, a path with no `node_modules`
|
|
693
|
-
* segment, so the skip branch never fires and React optimizes normally.
|
|
694
|
-
* Canon: nothing measured inside the checkout is evidence about a
|
|
695
|
-
* published install.
|
|
696
|
-
*/
|
|
697
|
-
optimizeDeps: {
|
|
698
|
-
include: [
|
|
699
|
-
"react",
|
|
700
|
-
"react-dom",
|
|
701
|
-
"react-dom/client",
|
|
702
|
-
"react/jsx-runtime"
|
|
703
|
-
],
|
|
704
|
-
/**
|
|
705
|
-
* The SECOND instance problem, and the reason this is `exclude` and not
|
|
706
|
-
* another `include`.
|
|
707
|
-
*
|
|
708
|
-
* An app page's `import { Link } from "@warlock.js/web"` has an importer
|
|
709
|
-
* OUTSIDE `node_modules`, so it takes the opposite branch above and the
|
|
710
|
-
* whole package is pre-bundled into
|
|
711
|
-
* `/node_modules/.vite/deps/@warlock__js_web.js`. The hydration entry
|
|
712
|
-
* cannot reach that bundle — it is loaded by absolute path through
|
|
713
|
-
* `/@fs/` and its imports are relative, so they resolve to the raw
|
|
714
|
-
* `esm/*.mjs` files. The browser then holds TWO copies of every client
|
|
715
|
-
* module in this package: the app renders `<Link>` from the bundled one
|
|
716
|
-
* while the hydration entry publishes routes and mounts the document
|
|
717
|
-
* context on the raw one. Module-level state does not cross that line.
|
|
718
|
-
*
|
|
719
|
-
* `routing/route-table` survives it by accident — it keeps its table in
|
|
720
|
-
* a `Symbol.for` slot on `globalThis`, which is realm-wide — but
|
|
721
|
-
* `components/document-context` is a React context OBJECT, and two
|
|
722
|
-
* `createContext()` calls are two different contexts no provider can
|
|
723
|
-
* bridge.
|
|
724
|
-
*
|
|
725
|
-
* Excluding the package makes the app's import resolve to
|
|
726
|
-
* `esm/index.mjs` with the same `?v=` key the hydration entry's imports
|
|
727
|
-
* carry, so there is one instance again. It costs the pre-bundle (a
|
|
728
|
-
* handful of extra dev requests for files that are already valid ESM)
|
|
729
|
-
* and nothing else — the grep across every client-reachable module in
|
|
730
|
-
* this package finds exactly three bare imports, all of them React, all
|
|
731
|
-
* of them covered by `include` above.
|
|
732
|
-
*/
|
|
733
|
-
exclude: ["@warlock.js/web"]
|
|
734
|
-
},
|
|
735
|
-
ssr: {
|
|
736
|
-
external: [
|
|
737
|
-
...CORE_OPTIONAL_PEERS,
|
|
738
|
-
...WEB_OPTIONAL_PEERS,
|
|
739
|
-
...this.options.ssrExternal ?? []
|
|
740
|
-
],
|
|
741
|
-
/**
|
|
742
|
-
* ONE `@warlock.js/web`, for the same reason `resolve.dedupe` below
|
|
743
|
-
* insists on one React — and it is invisible from inside this repo.
|
|
744
|
-
*
|
|
745
|
-
* Vite externalises `node_modules` in SSR by default, so an installed
|
|
746
|
-
* app gets TWO instances: the app's own `root.tsx` imports
|
|
747
|
-
* `@warlock.js/web` and Vite hands that off to Node, while the pipeline
|
|
748
|
-
* is loaded deliberately through `vite.ssrLoadModule(...)` and stays
|
|
749
|
-
* inside Vite's graph. `renderPage` then sets the document context on
|
|
750
|
-
* Vite's copy of `components/document-context`, and the app's `<Head/>`
|
|
751
|
-
* reads Node's copy, which has nothing in it:
|
|
752
|
-
*
|
|
753
|
-
* <Head/> was rendered outside the page pipeline's document context
|
|
754
|
-
*
|
|
755
|
-
* Measured on a published 5.0.1 install: `GET /` 500 without this line,
|
|
756
|
-
* 200 with it (404 control still 404). In THIS checkout the package
|
|
757
|
-
* resolves to source under Vite's root, never through `node_modules`,
|
|
758
|
-
* so both paths land on one instance and the bug cannot reproduce.
|
|
759
|
-
* Canon `6b7ab838`.
|
|
760
|
-
*/
|
|
761
|
-
noExternal: ["@warlock.js/web"]
|
|
762
|
-
},
|
|
763
|
-
resolve: {
|
|
764
|
-
dedupe: ["react", "react-dom"],
|
|
765
|
-
alias: [...this.options.resolveAlias ?? [], ...appConventionAliases(paths.appSrcRoot)]
|
|
766
|
-
}
|
|
767
|
-
});
|
|
456
|
+
return createServer(await createWebConnectorViteConfig({
|
|
457
|
+
appRoot: paths.appRoot,
|
|
458
|
+
appSrcRoot: paths.appSrcRoot,
|
|
459
|
+
webRoot: paths.webRoot,
|
|
460
|
+
workspaceRoot,
|
|
461
|
+
hmrServer: fastify.server,
|
|
462
|
+
handlePageHotUpdate: (file) => this.handlePageHotUpdate(file),
|
|
463
|
+
leadingPlugins: [devErrorTransportPlugin({
|
|
464
|
+
isProductionRuntime,
|
|
465
|
+
buildErrorMessage
|
|
466
|
+
})],
|
|
467
|
+
resolveAlias: this.options.resolveAlias,
|
|
468
|
+
ssrExternal: this.options.ssrExternal,
|
|
469
|
+
plugins: this.options.plugins
|
|
470
|
+
}));
|
|
768
471
|
}
|
|
769
472
|
};
|
|
770
473
|
|