@warlock.js/web 5.0.2 → 5.2.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 +250 -0
- package/README.md +126 -12
- package/esm/build/contribution.d.mts +11 -4
- package/esm/build/contribution.mjs +46 -22
- package/esm/build/contribution.mjs.map +1 -1
- package/esm/build/discover-pages.mjs +318 -44
- package/esm/build/discover-pages.mjs.map +1 -1
- package/esm/build/generate-client-registry.mjs +47 -13
- package/esm/build/generate-client-registry.mjs.map +1 -1
- package/esm/build/generate-pages-barrel.mjs +55 -13
- package/esm/build/generate-pages-barrel.mjs.map +1 -1
- package/esm/build/page-default-export.mjs +48 -0
- package/esm/build/page-default-export.mjs.map +1 -0
- package/esm/build/page-routes-manifest.mjs +21 -0
- package/esm/build/page-routes-manifest.mjs.map +1 -0
- package/esm/build/public-files.mjs +45 -0
- package/esm/build/public-files.mjs.map +1 -0
- package/esm/build/read-route-exports.mjs +1 -1
- package/esm/build/read-route-exports.mjs.map +1 -1
- package/esm/client/build-hydrated-tree.mjs +40 -9
- package/esm/client/build-hydrated-tree.mjs.map +1 -1
- package/esm/client/hydrate-page.mjs +1 -1
- package/esm/client/hydrate-page.mjs.map +1 -1
- package/esm/client/navigation/current-route.d.mts +1 -1
- package/esm/client/navigation/current-route.mjs +1 -1
- package/esm/client/navigation/current-route.mjs.map +1 -1
- package/esm/client/navigation/fetch-page-data.mjs +1 -1
- package/esm/client/navigation/fetch-page-data.mjs.map +1 -1
- package/esm/client/navigation/navigation-root.mjs +44 -7
- package/esm/client/navigation/navigation-root.mjs.map +1 -1
- package/esm/client/navigation/prefetch.mjs +1 -1
- package/esm/client/navigation/prefetch.mjs.map +1 -1
- package/esm/client/navigation/refresh.d.mts +1 -1
- package/esm/client/navigation/refresh.mjs +1 -1
- package/esm/client/navigation/refresh.mjs.map +1 -1
- package/esm/client/navigation/scroll-to-fragment.mjs +26 -0
- package/esm/client/navigation/scroll-to-fragment.mjs.map +1 -0
- package/esm/client/navigation/verbs.d.mts +1 -1
- package/esm/client/navigation/verbs.mjs +1 -1
- package/esm/client/navigation/verbs.mjs.map +1 -1
- package/esm/client/runtime/index.d.mts +2 -1
- package/esm/client/runtime/index.mjs +2 -1
- package/esm/client/runtime/manifest.d.mts +1 -1
- package/esm/client/runtime/manifest.mjs +3 -2
- package/esm/client/runtime/manifest.mjs.map +1 -1
- package/esm/client/runtime/matcher.d.mts +13 -1
- package/esm/client/runtime/matcher.mjs +13 -1
- package/esm/client/runtime/matcher.mjs.map +1 -1
- package/esm/client/runtime/types.d.mts +15 -2
- package/esm/components/default-app.mjs +1 -1
- package/esm/components/default-app.mjs.map +1 -1
- package/esm/components/document-context.d.mts +40 -2
- package/esm/components/document-context.mjs +1 -1
- package/esm/components/document-context.mjs.map +1 -1
- package/esm/components/head.d.mts +1 -1
- package/esm/components/head.mjs +1 -1
- package/esm/components/head.mjs.map +1 -1
- package/esm/components/link.d.mts +1 -1
- package/esm/components/link.mjs +1 -1
- package/esm/components/link.mjs.map +1 -1
- package/esm/components/scripts.d.mts +1 -1
- package/esm/components/scripts.mjs +4 -2
- package/esm/components/scripts.mjs.map +1 -1
- package/esm/context.d.mts +6 -102
- package/esm/hydration/index.mjs +1 -1
- package/esm/hydration/index.mjs.map +1 -1
- package/esm/hydration-payload.d.mts +1 -1
- package/esm/hydration-payload.mjs +37 -8
- package/esm/hydration-payload.mjs.map +1 -1
- package/esm/index.d.mts +4 -3
- package/esm/loaders.d.mts +7 -36
- package/esm/metadata.d.mts +15 -1
- package/esm/metadata.mjs +45 -0
- package/esm/metadata.mjs.map +1 -0
- package/esm/props.d.mts +16 -8
- package/esm/route.d.mts +2 -17
- package/esm/routing/compose-route-path.d.mts +1 -1
- package/esm/routing/compose-route-path.mjs +1 -1
- package/esm/routing/compose-route-path.mjs.map +1 -1
- package/esm/routing/data-request.mjs +1 -1
- package/esm/routing/data-request.mjs.map +1 -1
- package/esm/routing/filesystem-route.mjs +45 -0
- package/esm/routing/filesystem-route.mjs.map +1 -0
- package/esm/routing/layout-policy.mjs +1 -1
- package/esm/routing/layout-policy.mjs.map +1 -1
- package/esm/routing/navigator.mjs +1 -1
- package/esm/routing/navigator.mjs.map +1 -1
- package/esm/routing/query-string.d.mts +1 -1
- package/esm/routing/query-string.mjs +1 -1
- package/esm/routing/query-string.mjs.map +1 -1
- package/esm/routing/route-identity.mjs +1 -1
- package/esm/routing/route-identity.mjs.map +1 -1
- package/esm/routing/route-table.d.mts +1 -1
- package/esm/routing/route-table.mjs +1 -1
- package/esm/routing/route-table.mjs.map +1 -1
- package/esm/routing/router-events.d.mts +1 -1
- package/esm/routing/router-events.mjs +1 -1
- package/esm/routing/router-events.mjs.map +1 -1
- package/esm/routing/url-fragment.mjs +120 -0
- package/esm/routing/url-fragment.mjs.map +1 -0
- package/esm/runtime/register-modules.d.mts +19 -0
- package/esm/runtime/register-modules.mjs +22 -0
- package/esm/runtime/register-modules.mjs.map +1 -0
- package/esm/server/build-hydration-payload.mjs +2 -1
- package/esm/server/build-hydration-payload.mjs.map +1 -1
- package/esm/server/client-asset-url-prefix.d.mts +1 -1
- package/esm/server/client-asset-url-prefix.mjs +1 -1
- package/esm/server/client-asset-url-prefix.mjs.map +1 -1
- package/esm/server/create-page-module-loader.d.mts +1 -1
- package/esm/server/create-page-module-loader.mjs +2 -1
- package/esm/server/create-page-module-loader.mjs.map +1 -1
- package/esm/server/create-page-route-handler.d.mts +58 -6
- package/esm/server/create-page-route-handler.mjs +127 -40
- package/esm/server/create-page-route-handler.mjs.map +1 -1
- package/esm/server/dev-server.mjs +2 -13
- package/esm/server/dev-server.mjs.map +1 -1
- package/esm/server/error-page.d.mts +14 -0
- package/esm/server/error-page.mjs +39 -0
- package/esm/server/error-page.mjs.map +1 -0
- package/esm/server/execute-page-request.d.mts +7 -4
- package/esm/server/execute-page-request.mjs +79 -80
- package/esm/server/execute-page-request.mjs.map +1 -1
- package/esm/server/execute-page-request.types.d.mts +33 -92
- package/esm/server/hydration-client-url.d.mts +1 -1
- package/esm/server/hydration-client-url.mjs +2 -2
- package/esm/server/hydration-client-url.mjs.map +1 -1
- package/esm/server/index.d.mts +6 -5
- package/esm/server/index.mjs +4 -4
- package/esm/server/install-page-routes-from-manifest.d.mts +17 -7
- package/esm/server/install-page-routes-from-manifest.mjs +91 -8
- package/esm/server/install-page-routes-from-manifest.mjs.map +1 -1
- package/esm/server/install-page-routes.d.mts +32 -13
- package/esm/server/install-page-routes.mjs +112 -32
- package/esm/server/install-page-routes.mjs.map +1 -1
- package/esm/server/install-production-page-routes.mjs +3 -31
- package/esm/server/install-production-page-routes.mjs.map +1 -1
- package/esm/server/match-page-route.mjs +2 -2
- package/esm/server/match-page-route.mjs.map +1 -1
- package/esm/server/not-found-page.d.mts +126 -0
- package/esm/server/not-found-page.mjs +157 -0
- package/esm/server/not-found-page.mjs.map +1 -0
- package/esm/server/page-context.d.mts +3 -9
- package/esm/server/page-context.mjs +3 -16
- package/esm/server/page-context.mjs.map +1 -1
- package/esm/server/page-file-change.mjs +77 -0
- package/esm/server/page-file-change.mjs.map +1 -0
- package/esm/server/page-manifest.d.mts +25 -10
- package/esm/server/page-manifest.mjs +1 -1
- package/esm/server/page-manifest.mjs.map +1 -1
- package/esm/server/page-render-bundle.mjs +38 -0
- package/esm/server/page-render-bundle.mjs.map +1 -0
- package/esm/server/page-route-reload.mjs +54 -0
- package/esm/server/page-route-reload.mjs.map +1 -0
- package/esm/server/register-production-public-files.mjs +40 -0
- package/esm/server/register-production-public-files.mjs.map +1 -0
- package/esm/server/render-page.d.mts +11 -14
- package/esm/server/render-page.mjs +154 -28
- package/esm/server/render-page.mjs.map +1 -1
- package/esm/server/resolve-page-metadata.mjs +2 -2
- package/esm/server/resolve-page-metadata.mjs.map +1 -1
- 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.d.mts +58 -1
- package/esm/server/settle-page-response.mjs +111 -46
- package/esm/server/settle-page-response.mjs.map +1 -1
- package/esm/server/stylesheet-urls.d.mts +41 -30
- package/esm/server/stylesheet-urls.mjs +137 -52
- package/esm/server/stylesheet-urls.mjs.map +1 -1
- package/esm/server/unregistered-pages.mjs +42 -0
- package/esm/server/unregistered-pages.mjs.map +1 -0
- package/esm/server/web-connector-factory.d.mts +2 -3
- package/esm/server/web-connector-factory.mjs +6 -4
- package/esm/server/web-connector-factory.mjs.map +1 -1
- package/esm/server/web-connector.d.mts +1 -1
- package/esm/server/web-connector.mjs +233 -18
- package/esm/server/web-connector.mjs.map +1 -1
- package/esm/shared.d.mts +18 -3
- package/esm/shared.mjs +3 -7
- package/esm/shared.mjs.map +1 -1
- package/esm/validation.d.mts +1 -1
- package/esm/vite/app-convention-aliases.mjs +1 -1
- package/esm/vite/app-convention-aliases.mjs.map +1 -1
- package/esm/vite/build-client.d.mts +1 -1
- package/esm/vite/build-client.mjs +2 -2
- package/esm/vite/build-client.mjs.map +1 -1
- package/esm/vite/gate-a-resolve.d.mts +1 -1
- package/esm/vite/gate-a-resolve.mjs +67 -13
- package/esm/vite/gate-a-resolve.mjs.map +1 -1
- package/esm/vite/gate-b-secrets.d.mts +1 -1
- package/esm/vite/gate-b-secrets.mjs +35 -17
- 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 +6 -6
- package/esm/vite/gate-c-verify.mjs.map +1 -1
- package/esm/vite/hydration-entries.d.mts +1 -1
- package/esm/vite/hydration-entries.mjs +10 -6
- package/esm/vite/hydration-entries.mjs.map +1 -1
- package/esm/vite/index.d.mts +9 -6
- package/esm/vite/index.mjs +124 -11
- package/esm/vite/index.mjs.map +1 -1
- package/esm/vite/page-registry-plugin.d.mts +22 -3
- package/esm/vite/page-registry-plugin.mjs +247 -2
- package/esm/vite/page-registry-plugin.mjs.map +1 -1
- package/esm/vite/projection.d.mts +2 -2
- package/esm/vite/projection.mjs +38 -15
- package/esm/vite/projection.mjs.map +1 -1
- package/llms-full.txt +241 -49
- package/llms.txt +6 -2
- package/package.json +3 -3
- package/skills/add-web-to-an-app/SKILL.md +3 -3
- package/skills/create-a-page/SKILL.md +130 -7
- package/skills/load-page-data/SKILL.md +92 -12
- package/skills/navigate-on-the-client/SKILL.md +3 -1
- package/skills/serve-styles/SKILL.md +13 -12
- package/skills/use-layouts/SKILL.md +20 -8
- package/skills/write-the-root/SKILL.md +5 -1
- package/esm/server/buffered-response.d.mts +0 -58
- package/esm/server/buffered-response.mjs +0 -115
- package/esm/server/buffered-response.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,256 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@warlock.js/web` are documented here.
|
|
4
4
|
|
|
5
|
+
## 5.2.0
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`error.page.tsx`** — the application's one error boundary. It renders when
|
|
10
|
+
a middleware, loader, or component throws; declares no `route`, exactly like
|
|
11
|
+
`404.page.tsx`; and a second `error.page.tsx` anywhere beneath `src/web` is a
|
|
12
|
+
build error. Its component receives `{ error, status }` — the real thrown
|
|
13
|
+
value during SSR, a JSON-safe `{ name, message, stack? }` after hydration.
|
|
14
|
+
`robots: noindex` is a framework default on this path and cannot be
|
|
15
|
+
overridden away. If the failure happens before any page module could load —
|
|
16
|
+
a module-load or `register()` throw — the response falls back further, to a
|
|
17
|
+
framework-owned boundary with no application code at all, and is served
|
|
18
|
+
without a hydration script rather than risk hydrating against markup nothing
|
|
19
|
+
can vouch for.
|
|
20
|
+
- **A page's `route` export is now optional.** A `*.page.tsx` with no `route`
|
|
21
|
+
derives its path and its name from its location beneath `src/web`:
|
|
22
|
+
directories contribute segments, `(group)` directories contribute nothing,
|
|
23
|
+
`index.page.tsx` claims its own directory, and `[id]` becomes `:id`. An
|
|
24
|
+
explicit `route` still always wins over the derived one. This replaces the
|
|
25
|
+
5.1 behaviour, where an omitted `route` threw `MissingRouteExportError` at
|
|
26
|
+
install time — that error class no longer exists.
|
|
27
|
+
- **Live page-route re-registration in `warlock dev`.** Creating, deleting, or
|
|
28
|
+
editing a page's `route` export used to require a manual restart to take
|
|
29
|
+
effect — the route table was built once at boot and never again, so a
|
|
30
|
+
renamed route kept serving its old path and a deleted page kept 404-ing at
|
|
31
|
+
its old URL forever. The dev connector now re-registers the affected routes
|
|
32
|
+
in place, atomically, with no dev-server restart and no loss of Vite's HMR
|
|
33
|
+
state. A component-body-only edit still takes the ordinary Fast Refresh
|
|
34
|
+
path; only membership and route-identity changes go through this path.
|
|
35
|
+
- **A dev-only diagnostic for a page file that exists but isn't reachable.**
|
|
36
|
+
When a request 404s, Warlock checks whether an unregistered `*.page.tsx`
|
|
37
|
+
under `src/web` would have matched it, and if so, warns naming the file.
|
|
38
|
+
This is the case that used to be silent: a page created after boot, or one
|
|
39
|
+
whose `route` was edited to a path nothing else claims, previously 404'd
|
|
40
|
+
with no explanation anywhere in the terminal.
|
|
41
|
+
- **`export const register`** — an optional, synchronous, no-argument hook on
|
|
42
|
+
`root.tsx`, `layout.tsx`, and `*.page.tsx`. It runs once per module
|
|
43
|
+
namespace instance, on both the server and the browser, before that
|
|
44
|
+
module's middleware or loader — the seam for one-time setup a page or
|
|
45
|
+
layout needs on both sides of hydration. It must not return a Promise;
|
|
46
|
+
returning one throws.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- **Page requests now tolerate one trailing slash identically in development
|
|
51
|
+
and production.** `/about` and `/about/` serve the same page; `/` remains the
|
|
52
|
+
root path and case handling is unchanged. Previously the development
|
|
53
|
+
dispatcher accepted the slash while the production Fastify route returned
|
|
54
|
+
404.
|
|
55
|
+
|
|
56
|
+
- ⚠ **BREAKING — `process.env` is refused entirely in the client/universal
|
|
57
|
+
graph, and there is no `PUBLIC_` exception.** Neither a static key
|
|
58
|
+
(`process.env.PUBLIC_API_URL`) nor a computed one (`process.env[key]`) is
|
|
59
|
+
allowed: `process` does not exist in a browser, so there is no such thing as
|
|
60
|
+
a "public" `process.env` key. **Bare value-reads of the object now fail
|
|
61
|
+
too** — `const { X } = process.env`, `{ ...process.env }`,
|
|
62
|
+
`Object.keys(process.env)`, `JSON.stringify(process.env)`, or passing it as
|
|
63
|
+
an argument — which is the case that previously let an entire server
|
|
64
|
+
environment reach a component in one line while every keyed read was being
|
|
65
|
+
refused. `globalThis.process.env`, `window.process.env` and
|
|
66
|
+
`process["env"]` are matched as well.
|
|
67
|
+
|
|
68
|
+
**Enforcement now covers dev SSR as well as the client bundle, and a
|
|
69
|
+
violation fails the build** rather than being a production-only surprise.
|
|
70
|
+
Files under `node_modules` stay out of scope by design — a dependency's own
|
|
71
|
+
`process.env.NODE_ENV` guard is not the application's problem.
|
|
72
|
+
|
|
73
|
+
⚠ **`env("PUBLIC_X")` does not work client-side either**, and never did:
|
|
74
|
+
`env` comes from `@warlock.js/core`, which declares itself server-only, so
|
|
75
|
+
the import is refused before the call is ever examined. **The supported
|
|
76
|
+
pattern is to read the value in a page loader — server code — and pass it to
|
|
77
|
+
the page as loader data:**
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
export const loader = (async () => ({
|
|
81
|
+
siteName: env("PUBLIC_SITE_NAME"),
|
|
82
|
+
})) satisfies PageLoader;
|
|
83
|
+
|
|
84
|
+
export default function HomePage({ data }: PageProps<typeof loader>) {
|
|
85
|
+
return <h1>{data.siteName}</h1>;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
If a value must genuinely be inlined into browser code instead of passed as
|
|
90
|
+
loader data, the one supported spelling is `import.meta.env.PUBLIC_*` with a
|
|
91
|
+
static key — Vite's env surface, baked in at build time, so it cannot vary
|
|
92
|
+
per request. Server-side code is unrestricted.
|
|
93
|
+
|
|
94
|
+
- ⚠ **BREAKING — a `*.page.tsx` with no default export is now a hard
|
|
95
|
+
discovery/build failure, naming the file.** It previously built and
|
|
96
|
+
registered, then served a blank `200` at its URL — a page that looked
|
|
97
|
+
deployed, rendered nothing, and produced no error anywhere.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
The page "src/web/contact.page.tsx" has no runtime default export. Every
|
|
101
|
+
`*.page.tsx` file must default-export the React component it renders.
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`export { Page as default }` satisfies the rule — the check is for a runtime
|
|
105
|
+
default binding, not for the keyword form. `export default interface Page {}`
|
|
106
|
+
does not: a type-only default is erased and leaves no component behind. A
|
|
107
|
+
file that cannot be parsed reports as a parse failure instead, so a syntax
|
|
108
|
+
error never masquerades as a missing export.
|
|
109
|
+
|
|
110
|
+
- **Initial stylesheet links are route-scoped in development and production.**
|
|
111
|
+
Each response now links the ordered, deduplicated CSS chain for its own
|
|
112
|
+
`[root, ...matched layouts, page]`. Production follows those source entries
|
|
113
|
+
and their static imports in Vite's manifest instead of collecting CSS across
|
|
114
|
+
the whole application; development promotes direct stylesheet imports from
|
|
115
|
+
the matched page and layouts as well as the root. Unrelated page CSS no
|
|
116
|
+
longer ships on every response, and page-local critical CSS no longer waits
|
|
117
|
+
for hydration in development.
|
|
118
|
+
- **The production static-asset refusal now names the working 5.2 alternative.**
|
|
119
|
+
Imported non-stylesheet assets still work under Vite in development but are
|
|
120
|
+
refused by the esbuild server bundle rather than risk a server/client URL
|
|
121
|
+
mismatch. The diagnostic now tells the developer to place the file under the
|
|
122
|
+
application's `public/` directory and reference its root URL
|
|
123
|
+
(`public/logo.svg` → `/logo.svg`) instead of waiting for an unspecified future
|
|
124
|
+
server build. Stylesheet imports remain supported.
|
|
125
|
+
- **Loader execution is sequential, root to leaf, and terminal responses stop
|
|
126
|
+
lower work.** The `root.tsx` App loader runs first, followed by every matched
|
|
127
|
+
layout loader from outermost to innermost, then the page loader. The runtime
|
|
128
|
+
has three top-level slots (`app`, `layout`, `page`), but the layout slot
|
|
129
|
+
composes the full matched layout chain. A page still has at most one
|
|
130
|
+
*rendering* layout; loader-only and middleware-only layouts may appear at
|
|
131
|
+
multiple ancestry levels.
|
|
132
|
+
|
|
133
|
+
⚠ **This package's own documentation previously described the three levels as
|
|
134
|
+
running in parallel, and told you not to rely on ordering between them.** The
|
|
135
|
+
implementation now awaits the App slot, the composed outer-to-inner layout
|
|
136
|
+
slot, and the Page slot in that order.
|
|
137
|
+
|
|
138
|
+
**The first core `Response` a loader returns is terminal**: it stops every
|
|
139
|
+
lower loader from starting, and — because the response is returned whole — it
|
|
140
|
+
also bypasses buffer commit and metadata resolution, discarding the header
|
|
141
|
+
and cookie writes buffered at that same level. A short-circuit
|
|
142
|
+
(`response.redirect()`, `response.notFound()`) commits its own level's buffer
|
|
143
|
+
inclusively and is the right choice when those writes should survive; a throw
|
|
144
|
+
discards the throwing level's buffer and commits only the levels above it.
|
|
145
|
+
|
|
146
|
+
- **Catch-all page routes are documented as unsupported.** `[...slug].page.tsx`
|
|
147
|
+
does not do what it looks like: filesystem routing recognizes only `[name]` as
|
|
148
|
+
a dynamic segment, so `[...slug]` is taken as a **literal** segment and derives
|
|
149
|
+
the path `/docs/[...slug]` and the name `docs.[...slug]` — reachable only at
|
|
150
|
+
the literal URL `/docs/%5B...slug%5D`. ⚠ **Nothing warns about it**: no build
|
|
151
|
+
error, no dev warning, no refusal, just a page that answers a URL nobody will
|
|
152
|
+
request. A real catch-all is deferred; until then use a terminal wildcard with
|
|
153
|
+
an explicit route (`route = { path: "/docs/*" }`). This entry records the gap,
|
|
154
|
+
it does not close it.
|
|
155
|
+
|
|
156
|
+
- **`src/web` is the only page root.** A per-module `src/app/<module>/web/`
|
|
157
|
+
tree is no longer discovered, walked, or installed as a page root by either
|
|
158
|
+
`warlock dev` or `warlock build`. Move any page, layout, or root file that
|
|
159
|
+
lived under `src/app/<module>/web/` into `src/web/` (a subdirectory is
|
|
160
|
+
fine — it still contributes a route segment the same way).
|
|
161
|
+
|
|
162
|
+
### Fixed
|
|
163
|
+
|
|
164
|
+
- **A custom `404.page.tsx` loader no longer executes.** The not-found page
|
|
165
|
+
still registers and renders its real module namespace, but its request
|
|
166
|
+
triple omits the page loader in both development and production. A missing
|
|
167
|
+
URL therefore cannot trigger application data work, redirect, or fail a
|
|
168
|
+
second time through the fallback itself.
|
|
169
|
+
|
|
170
|
+
Precisely what is skipped, because "the 404 page doesn't run loaders" is a
|
|
171
|
+
useful shorthand and not the whole rule: only the **page-level `loader`** is
|
|
172
|
+
omitted. The real module namespace is still used, so `register()` runs and
|
|
173
|
+
the component renders normally, and the page's **middleware still runs**.
|
|
174
|
+
Layout loaders don't run because this page has an empty layout chain by
|
|
175
|
+
construction, not because loaders are disabled on it. And the **`root.tsx`
|
|
176
|
+
App loader does still run** on a 404 request — keep it cheap, and make sure
|
|
177
|
+
it tolerates a request that matched nothing.
|
|
178
|
+
|
|
179
|
+
## 5.1.0
|
|
180
|
+
|
|
181
|
+
> **Upgrade if you installed 5.0.0, 5.0.1 or 5.0.2.** React did not execute at all in
|
|
182
|
+
> a published install of any of them — see the first entry under *Fixed*. Every
|
|
183
|
+
> interactive page shipped on those versions was inert in the browser.
|
|
184
|
+
|
|
185
|
+
### Added
|
|
186
|
+
|
|
187
|
+
- **`404.page.tsx`** — an app-owned not-found page. It renders only when `text/html`
|
|
188
|
+
is explicitly present in the request's `Accept` header, so an unmatched `/api/...`
|
|
189
|
+
path still returns the JSON 404 an API client expects rather than a document. It
|
|
190
|
+
renders with no layouts: discovery reports an empty layout chain for this page only,
|
|
191
|
+
so the client hydration registry matches what the server has always rendered instead
|
|
192
|
+
of wrapping a failure page in chrome that can itself throw or need data. Ordinary
|
|
193
|
+
pages beside it keep their full layout chain, and nested-layout refusal on its path
|
|
194
|
+
is unchanged.
|
|
195
|
+
- **`export const metadata` is typed (`PageMetadata`) and checked at build time.** An
|
|
196
|
+
unannotated object literal with a misspelled key — `{ tittle: "x" }` — now fails the
|
|
197
|
+
build, naming the file, the line and the offending key. It previously typechecked as
|
|
198
|
+
a plain object and was silently ignored at runtime.
|
|
199
|
+
- **Fast Refresh in dev now applies only when an edit is confined to component
|
|
200
|
+
bodies.** Any module-level change — an import, a module-level declaration, or any
|
|
201
|
+
server export, `metadata` included — forces a full page reload instead of a stale hot
|
|
202
|
+
update; a JSX-only edit still hot-updates in place with component state intact.
|
|
203
|
+
|
|
204
|
+
### Changed
|
|
205
|
+
|
|
206
|
+
- **`warlock dev` now refuses a `*.page.tsx` that exports no route**, throwing
|
|
207
|
+
`MissingRouteExportError` and naming the file. It previously 404'd silently, so a
|
|
208
|
+
missing `export const route` looked like a routing bug at request time. This matches
|
|
209
|
+
what `warlock build` already did — dev and build now reject the same file.
|
|
210
|
+
|
|
211
|
+
### Removed
|
|
212
|
+
|
|
213
|
+
- **A false comment shipped in 5.0.0 through 5.0.2** claiming that a page's route is
|
|
214
|
+
derived from its file location. No such derivation has ever existed in this package;
|
|
215
|
+
the route comes from the page's `route` export and nothing else. The comment is gone
|
|
216
|
+
from the scaffold emitted by `warlock add web`, but **every app scaffolded on 5.0.0,
|
|
217
|
+
5.0.1 or 5.0.2 still carries it in its own source** — delete it by hand.
|
|
218
|
+
|
|
219
|
+
### Fixed
|
|
220
|
+
|
|
221
|
+
- **React did not run at all in published installs of 5.0.0 through 5.0.2.** The dev
|
|
222
|
+
Vite server served `react-dom/client` as raw CJS, so `hydrateRoot` did not exist and
|
|
223
|
+
the hydration module threw while being parsed. This one defect is the cause of all
|
|
224
|
+
four symptoms reported against those versions: `useState` never updated, Fast Refresh
|
|
225
|
+
never ran, metadata never refreshed, and `<Link>` fell back to a full page reload.
|
|
226
|
+
Fixed by declaring the React entries in the dev server's `optimizeDeps` so they are
|
|
227
|
+
pre-bundled to ESM before the browser asks for them. This is not a hydration
|
|
228
|
+
*improvement* — hydration did not happen.
|
|
229
|
+
- **The browser was loading two copies of every `@warlock.js/web` client module.**
|
|
230
|
+
Module-level state (context, the navigation runtime) existed twice, so a value written
|
|
231
|
+
through one copy was invisible to the component reading the other.
|
|
232
|
+
|
|
233
|
+
## 5.0.2 - 2026-08-25
|
|
234
|
+
|
|
235
|
+
### Fixed
|
|
236
|
+
|
|
237
|
+
- **`<Head/>` read an empty document context under SSR.** The connector now sets
|
|
238
|
+
`ssr.noExternal: ["@warlock.js/web"]` in `web-connector.ts`. Without it the server
|
|
239
|
+
loaded two instances of this package — one externalised, one bundled — so the context
|
|
240
|
+
the renderer wrote to was not the one `<Head/>` read from. A published 5.0.1 install
|
|
241
|
+
that returned 500 on a page request returns 200 after this fix.
|
|
242
|
+
|
|
243
|
+
## 5.0.1 - 2026-08-25
|
|
244
|
+
|
|
245
|
+
### Changed
|
|
246
|
+
|
|
247
|
+
- Narrowed the `vite` peer dependency to `">=7.3.5 <8"`, so a consumer resolving vite
|
|
248
|
+
for this package cannot land on a version outside the range it is built against.
|
|
249
|
+
|
|
250
|
+
### Fixed
|
|
251
|
+
|
|
252
|
+
- Internal: a test in `gate-b-secrets.spec.ts` depended on the ambient `NODE_ENV` and
|
|
253
|
+
failed depending on how the suite was invoked. No runtime behaviour changed.
|
|
254
|
+
|
|
5
255
|
## 5.0.0 - 2026-08-25
|
|
6
256
|
|
|
7
257
|
**First published release.**
|
package/README.md
CHANGED
|
@@ -41,7 +41,9 @@ export const route = "/products";
|
|
|
41
41
|
export const loader = (async ({ response }) => {
|
|
42
42
|
response.header("cache-control", "private, max-age=60");
|
|
43
43
|
|
|
44
|
-
return {
|
|
44
|
+
return {
|
|
45
|
+
products: productResourceCollection(await productsRepository.all()),
|
|
46
|
+
};
|
|
45
47
|
}) satisfies PageLoader;
|
|
46
48
|
|
|
47
49
|
/** Server-only. Runs after the loader and receives its data. */
|
|
@@ -54,7 +56,7 @@ export const metadata = ({ data }) => ({
|
|
|
54
56
|
export default function ProductsPage({ data }: PageProps<typeof loader>) {
|
|
55
57
|
return (
|
|
56
58
|
<ul>
|
|
57
|
-
{data.products.map(product => (
|
|
59
|
+
{data.products.map((product) => (
|
|
58
60
|
<li key={product.id}>{product.name}</li>
|
|
59
61
|
))}
|
|
60
62
|
</ul>
|
|
@@ -76,34 +78,146 @@ production.
|
|
|
76
78
|
compile error.
|
|
77
79
|
- **Loaders that are controllers.** Full request context, guards, DI, and the
|
|
78
80
|
ability to set headers, cookies and status during the render.
|
|
79
|
-
- **`
|
|
80
|
-
re-run.
|
|
81
|
+
- **`refresh()`.** POST to your own API, call it, and the page's loaders
|
|
82
|
+
re-run without pushing history. There is no `revalidate()` export.
|
|
81
83
|
- **Return values are Resources, never models.** A model does not survive the
|
|
82
84
|
wire: it carries methods, a connector handle and every column.
|
|
83
85
|
|
|
84
86
|
## The two halves of a page file
|
|
85
87
|
|
|
86
|
-
| Server-only
|
|
87
|
-
|
|
88
|
-
| `route`, `middleware`, `validation`, `loader`, `metadata` | `
|
|
88
|
+
| Server-only | Runs twice (server + browser) |
|
|
89
|
+
| ---------------------------------------------------------------------------------- | ------------------------------------- |
|
|
90
|
+
| `route`, `middleware`, `validation`, `loader`, `metadata`, `prefix` (layouts only) | default `Page` / `Layout`, `register` |
|
|
89
91
|
|
|
90
92
|
The server half is stripped before anything reaches the browser. The runs-twice
|
|
91
93
|
half never receives `request` or `response` — it also executes in a browser,
|
|
92
94
|
where neither exists — and is never `async`.
|
|
93
95
|
|
|
96
|
+
**A `.client.tsx` suffix is a naming convention, not an SSR-isolation boundary
|
|
97
|
+
in 5.2.** A module statically imported by a root, layout, page, or one of their
|
|
98
|
+
imports is still evaluated by the server. Top-level browser globals such as
|
|
99
|
+
`window` therefore crash SSR boot. Warlock 5.2 does not ship a client-only
|
|
100
|
+
component primitive; code that requires browser globals at module scope cannot
|
|
101
|
+
be part of the SSR page graph.
|
|
102
|
+
|
|
103
|
+
**Imported non-stylesheet static assets are also unsupported by the production
|
|
104
|
+
server build in 5.2.** An import such as `import logo from "./logo.svg"` works
|
|
105
|
+
under Vite in development but is refused by `warlock build`. Put the file under
|
|
106
|
+
the application's `public/` directory and reference its root URL instead:
|
|
107
|
+
`public/logo.svg` is `/logo.svg`. Stylesheet imports remain supported.
|
|
108
|
+
|
|
109
|
+
### Loader order
|
|
110
|
+
|
|
111
|
+
Loaders run **sequentially, root to leaf, each awaited before the next
|
|
112
|
+
starts**: the `root.tsx` App loader, then every matched layout loader from
|
|
113
|
+
outermost to innermost, then the page loader. The runtime has three top-level
|
|
114
|
+
slots (`app`, `layout`, `page`), but the layout slot composes the full matched
|
|
115
|
+
layout chain. Only one layout on that chain may render; loader-only and
|
|
116
|
+
middleware-only layouts still participate.
|
|
117
|
+
|
|
118
|
+
**The first core `Response` returned by a loader is terminal**: it stops every
|
|
119
|
+
lower loader from running, skips metadata, and is sent as-is. Because it is
|
|
120
|
+
returned whole, the buffered header and cookie writes made at that level go
|
|
121
|
+
with it — use `response.redirect()` / `response.notFound()` when you want those
|
|
122
|
+
writes committed, and a raw `Response` only when you mean exactly that
|
|
123
|
+
response.
|
|
124
|
+
|
|
125
|
+
### Environment variables
|
|
126
|
+
|
|
127
|
+
**`process.env` is refused entirely in the client/universal graph. There is no
|
|
128
|
+
`PUBLIC_` exception to it — static or computed — and `env("PUBLIC_X")` does not
|
|
129
|
+
work client-side either.** The supported pattern is to read the value in a page
|
|
130
|
+
loader, which is server code, and pass it to the page as loader data:
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import { env } from "@warlock.js/core";
|
|
134
|
+
import type { PageLoader, PageProps } from "@warlock.js/web";
|
|
135
|
+
|
|
136
|
+
export const loader = (async () => ({
|
|
137
|
+
siteName: env("PUBLIC_SITE_NAME"),
|
|
138
|
+
})) satisfies PageLoader;
|
|
139
|
+
|
|
140
|
+
export default function HomePage({ data }: PageProps<typeof loader>) {
|
|
141
|
+
return <h1>{data.siteName}</h1>;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The loader is server-only, so it may read configuration normally. Its return
|
|
146
|
+
value becomes page data and is serialized to the browser; return only values
|
|
147
|
+
that are safe to expose.
|
|
148
|
+
|
|
149
|
+
The refusal applies to default page and layout components, `register()`, and
|
|
150
|
+
any helper they import. It covers keyed reads (`process.env.X`,
|
|
151
|
+
`process.env.PUBLIC_X`, `process.env[key]`) and **bare value-reads** of the
|
|
152
|
+
object itself — `const { X } = process.env`, `{ ...process.env }`,
|
|
153
|
+
`Object.keys(process.env)`, `JSON.stringify(process.env)`. `process` does not
|
|
154
|
+
exist in a browser, so touching the object at all is already broken, and
|
|
155
|
+
handing the whole object to a component is how a server secret ships in one
|
|
156
|
+
line.
|
|
157
|
+
|
|
158
|
+
**Enforcement covers dev SSR as well as the client bundle, and a violation
|
|
159
|
+
fails the build** — this is not a production-only check you find out about
|
|
160
|
+
late. Server-side code is unrestricted.
|
|
161
|
+
|
|
162
|
+
If a value must genuinely be inlined into browser code rather than passed as
|
|
163
|
+
loader data, the one supported spelling is `import.meta.env.PUBLIC_*` with a
|
|
164
|
+
static key. That is Vite's surface, not Node's; it is baked in at build time,
|
|
165
|
+
so it cannot vary per request — loader data remains the answer for anything
|
|
166
|
+
request-scoped.
|
|
167
|
+
|
|
168
|
+
## Routing
|
|
169
|
+
|
|
170
|
+
A page's URL is its `route` export when it declares one. A page with no
|
|
171
|
+
`route` derives its URL from its own location beneath `src/web`: directories
|
|
172
|
+
contribute segments, `(group)` directories contribute nothing, `index.page.tsx`
|
|
173
|
+
claims its directory, and `[id]` becomes `:id`. A layout's `prefix` still
|
|
174
|
+
composes in front of either form. `route`, when present, always wins.
|
|
175
|
+
|
|
176
|
+
Every `*.page.tsx` must have a **default export**. A page file with only named
|
|
177
|
+
exports is a hard discovery/build failure naming the file — it used to build
|
|
178
|
+
and then serve a blank `200` at its URL.
|
|
179
|
+
|
|
180
|
+
**Catch-all routes are not supported.** `[...slug].page.tsx` is not a rest
|
|
181
|
+
parameter: only `[name]` is recognized as dynamic, so `[...slug]` is taken as a
|
|
182
|
+
literal segment and derives the unreachable path `/[...slug]`. Nothing warns
|
|
183
|
+
about it. Use a terminal wildcard with an explicit route
|
|
184
|
+
(`route = { path: "/docs/*" }`) until a real catch-all exists.
|
|
185
|
+
|
|
186
|
+
`src/web` is the only page root — a per-module `src/app/<module>/web/` tree is
|
|
187
|
+
not scanned.
|
|
188
|
+
|
|
189
|
+
Incoming URLs may carry one trailing slash: `/about` and `/about/` match the
|
|
190
|
+
same page in development and production. Route declarations remain canonical
|
|
191
|
+
and slash-free, `/` stays the root path, and case handling is unchanged.
|
|
192
|
+
|
|
193
|
+
Exactly two page filenames are special: `404.page.tsx` (the not-found page,
|
|
194
|
+
reached by not matching, never declares `route`, renders with no layout) and
|
|
195
|
+
`error.page.tsx` (the application's one error boundary, also declares no
|
|
196
|
+
`route`). There is no `500.page.tsx`; an unmatched URL is not an error.
|
|
197
|
+
|
|
198
|
+
**`404.page.tsx` never runs its own loader.** The module is registered and
|
|
199
|
+
rendered for real — `register()` and its middleware still run — but the page
|
|
200
|
+
loader is omitted from the request in both development and production, so a
|
|
201
|
+
missing URL cannot trigger application data work, redirect, or fail a second
|
|
202
|
+
time through the fallback. Layout loaders do not run either, because the page
|
|
203
|
+
has an empty layout chain by construction. The `root.tsx` App loader **does**
|
|
204
|
+
still run, so keep it cheap and make sure it tolerates a request that matched
|
|
205
|
+
nothing.
|
|
206
|
+
|
|
207
|
+
In development, creating, deleting, or editing a page's `route` export updates
|
|
208
|
+
the live route table without restarting `warlock dev`.
|
|
209
|
+
|
|
94
210
|
## Where things live
|
|
95
211
|
|
|
96
212
|
```
|
|
97
|
-
src/web/ app-level web layer
|
|
213
|
+
src/web/ app-level web layer and page root
|
|
98
214
|
root.tsx owns <html>, renders #root
|
|
99
215
|
layouts/ shared layouts
|
|
100
216
|
middleware/
|
|
101
|
-
src/app/<module>/web/ that module's own pages
|
|
102
217
|
```
|
|
103
218
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
page layer and leaves a working API.
|
|
219
|
+
All client code lives in `src/web/`, so `rm -rf src/web` removes the page layer
|
|
220
|
+
and leaves a working API.
|
|
107
221
|
|
|
108
222
|
**`web/` means the web layer, not the browser.** Loaders and page middleware
|
|
109
223
|
inside it are server code.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ConnectorBuildContribution } from "@warlock.js/core";
|
|
2
|
+
import { PluginOption } from "vite";
|
|
2
3
|
|
|
3
|
-
//#region
|
|
4
|
+
//#region web/src/build/contribution.d.ts
|
|
4
5
|
type WebBuildOptions = {
|
|
5
6
|
/** Source directory name under the app root. Default: `"src"`. */srcDir?: string;
|
|
6
7
|
/**
|
|
@@ -44,12 +45,18 @@ declare class WebPackageRootResolutionError extends Error {
|
|
|
44
45
|
*
|
|
45
46
|
* `generate` writes the page barrel into `ctx.productionDir` and patches
|
|
46
47
|
* esbuild; `emit` produces the client bundle esbuild cannot. The two share one
|
|
47
|
-
* closure
|
|
48
|
-
*
|
|
48
|
+
* closure facts — how many pages exist, the discovered route table, and the
|
|
49
|
+
* exact app-public file list. A build with zero pages skips the hydration
|
|
50
|
+
* bundle but still copies public files when present. `generate` itself does
|
|
49
51
|
* NOT skip: the barrel and its entry import are emitted either way, so the
|
|
50
52
|
* runtime can tell a page-free web build from a build without web.
|
|
53
|
+
*
|
|
54
|
+
* Connector plugins are a separate argument rather than a `WebBuildOptions`
|
|
55
|
+
* field: the app constructs them once, the factory snapshots that array for
|
|
56
|
+
* both dev and build, and this module keeps Vite as a type-only import until
|
|
57
|
+
* `emit` dynamically loads the real client builder.
|
|
51
58
|
*/
|
|
52
|
-
declare function createWebBuildContribution(options?: WebBuildOptions): ConnectorBuildContribution;
|
|
59
|
+
declare function createWebBuildContribution(options?: WebBuildOptions, connectorPlugins?: readonly PluginOption[]): ConnectorBuildContribution;
|
|
53
60
|
//#endregion
|
|
54
61
|
export { WebBuildOptions, WebPackageRootResolutionError, createWebBuildContribution };
|
|
55
62
|
//# sourceMappingURL=contribution.d.mts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region web/src/build/contribution.ts
|
|
5
5
|
/**
|
|
6
6
|
* Web's build-time half — the object a `WebConnector` hands to
|
|
7
7
|
* `warlock build` as its `build` contribution.
|
|
@@ -86,43 +86,67 @@ async function resolveWebPackageRoot(configured) {
|
|
|
86
86
|
*
|
|
87
87
|
* `generate` writes the page barrel into `ctx.productionDir` and patches
|
|
88
88
|
* esbuild; `emit` produces the client bundle esbuild cannot. The two share one
|
|
89
|
-
* closure
|
|
90
|
-
*
|
|
89
|
+
* closure facts — how many pages exist, the discovered route table, and the
|
|
90
|
+
* exact app-public file list. A build with zero pages skips the hydration
|
|
91
|
+
* bundle but still copies public files when present. `generate` itself does
|
|
91
92
|
* NOT skip: the barrel and its entry import are emitted either way, so the
|
|
92
93
|
* runtime can tell a page-free web build from a build without web.
|
|
94
|
+
*
|
|
95
|
+
* Connector plugins are a separate argument rather than a `WebBuildOptions`
|
|
96
|
+
* field: the app constructs them once, the factory snapshots that array for
|
|
97
|
+
* both dev and build, and this module keeps Vite as a type-only import until
|
|
98
|
+
* `emit` dynamically loads the real client builder.
|
|
93
99
|
*/
|
|
94
|
-
function createWebBuildContribution(options = {}) {
|
|
100
|
+
function createWebBuildContribution(options = {}, connectorPlugins = []) {
|
|
95
101
|
if (options.clientOutDir !== void 0) throw new ClientOutDirNotSupportedError();
|
|
96
102
|
let pageCount = 0;
|
|
103
|
+
let pageRoutes = {
|
|
104
|
+
version: 1,
|
|
105
|
+
routes: []
|
|
106
|
+
};
|
|
107
|
+
let publicFiles = [];
|
|
97
108
|
return {
|
|
98
109
|
async generate(context) {
|
|
99
110
|
const { generatePagesBarrel, WEB_ENTRY_IMPORT, WEB_ESBUILD_PATCH } = await import("./generate-pages-barrel.mjs");
|
|
100
|
-
|
|
111
|
+
const { collectPublicFiles } = await import("./public-files.mjs");
|
|
112
|
+
publicFiles = await collectPublicFiles(path.join(context.appRoot, "public"));
|
|
113
|
+
const result = await generatePagesBarrel({
|
|
101
114
|
appRoot: context.appRoot,
|
|
102
115
|
productionDir: context.productionDir,
|
|
103
116
|
srcDir: options.srcDir,
|
|
104
|
-
clientDir: path.relative(context.appRoot, resolveClientOutDir(context)).split(path.sep).join("/")
|
|
105
|
-
|
|
117
|
+
clientDir: path.relative(context.appRoot, resolveClientOutDir(context)).split(path.sep).join("/"),
|
|
118
|
+
publicFiles
|
|
119
|
+
});
|
|
120
|
+
pageCount = result.pageCount;
|
|
121
|
+
pageRoutes = result.pageRoutes;
|
|
106
122
|
return {
|
|
107
123
|
entryImports: [WEB_ENTRY_IMPORT],
|
|
108
124
|
esbuild: WEB_ESBUILD_PATCH
|
|
109
125
|
};
|
|
110
126
|
},
|
|
111
127
|
async emit(context) {
|
|
112
|
-
if (pageCount
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
if (pageCount > 0) {
|
|
129
|
+
const { buildWarlockHydrationClient } = await import("../vite/index.mjs");
|
|
130
|
+
const { appConventionAliases } = await import("../vite/app-convention-aliases.mjs");
|
|
131
|
+
const callerAliases = Object.entries(options.aliases ?? {}).map(([find, replacement]) => ({
|
|
132
|
+
find,
|
|
133
|
+
replacement
|
|
134
|
+
}));
|
|
135
|
+
await buildWarlockHydrationClient({
|
|
136
|
+
appRoot: context.appRoot,
|
|
137
|
+
webRoot: await resolveWebPackageRoot(options.webRoot),
|
|
138
|
+
outDir: resolveClientOutDir(context),
|
|
139
|
+
resolveAliases: [...callerAliases, ...appConventionAliases(path.join(context.appRoot, options.srcDir ?? "src"))],
|
|
140
|
+
external: options.external,
|
|
141
|
+
plugins: connectorPlugins
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (publicFiles.length > 0) {
|
|
145
|
+
const { copyPublicFiles } = await import("./public-files.mjs");
|
|
146
|
+
await copyPublicFiles(path.join(context.appRoot, "public"), path.join(resolveClientOutDir(context), "public"), publicFiles);
|
|
147
|
+
}
|
|
148
|
+
const { writePageRoutesManifest } = await import("./page-routes-manifest.mjs");
|
|
149
|
+
await writePageRoutesManifest(path.resolve(context.appRoot, context.options.outdir), pageRoutes);
|
|
126
150
|
}
|
|
127
151
|
};
|
|
128
152
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contribution.mjs","names":[],"sources":["../../../../../../../web/src/build/contribution.ts"],"sourcesContent":["/**\n * Web's build-time half — the object a `WebConnector` hands to\n * `warlock build` as its `build` contribution.\n *\n * KEPT DELIBERATELY LIGHT: everything this\n * module imports statically is a type or `node:path`. The heavy halves — the\n * filesystem discovery/barrel generator, and the Vite client build — are\n * `await import(...)`ed INSIDE the hooks, so a config file that merely\n * constructs the connector never drags Vite, React or the page graph into its\n * static import graph.\n *\n * {@link WebBuildOptions} carries JSON-SERIALIZABLE VALUES ONLY (constraint\n * B): no plugin instances, no functions, no class instances. Anything heavy is\n * constructed inside a hook after that hook's dynamic import.\n */\nimport { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type {\n ConnectorBuildContext,\n ConnectorBuildContribution,\n ConnectorBuildGenerateResult,\n} from \"@warlock.js/core\";\n\nexport type WebBuildOptions = {\n /** Source directory name under the app root. Default: `\"src\"`. */\n srcDir?: string;\n /**\n * Absolute path to the `@warlock.js/web` package root, which holds the\n * hydration entry (packaged as `esm/hydration/index.mjs`, with a source\n * fallback at `src/hydration/index.ts` for this checkout).\n *\n * Defaults to this module's own package root, derived from `import.meta.url`\n * at emit time. Set it explicitly when the build process loads this module\n * through a transform that rewrites `import.meta` (e.g. a CJS config\n * pipeline) — the derivation is the only thing here that depends on it.\n *\n * Derived or configured, the root is VERIFIED at emit time against\n * `<root>/package.json`'s `name`; a root that is not `@warlock.js/web`\n * throws {@link WebPackageRootResolutionError} rather than being guessed at.\n */\n webRoot?: string;\n /**\n * NOT SUPPORTED. The client bundle always lands at `<build.outdir>/client`,\n * the layout the runtime half reads (`resolveHydrationClientUrl`) — the\n * runtime does not yet consult a relocated path. Setting this option throws\n * {@link ClientOutDirNotSupportedError}.\n */\n clientOutDir?: string;\n /** Client-build resolve aliases: specifier -> absolute path. */\n aliases?: Record<string, string>;\n /** Extra package names to keep external to the client bundle. */\n external?: string[];\n};\n\nfunction resolveClientOutDir(context: ConnectorBuildContext): string {\n return path.resolve(context.appRoot, context.options.outdir, \"client\");\n}\n\n/**\n * `build.clientOutDir` was set.\n *\n * The production runtime hardcodes where it reads the client bundle from\n * (`<outdir>/client`, via `resolveHydrationClientUrl`) — it does not yet\n * consult the build config for a relocated path. A build that honored a\n * custom `clientOutDir` would therefore produce an artifact the runtime\n * cannot find, silently. Rejected at build start, before any work happens,\n * rather than left to surface later as a missing-bundle failure in\n * production.\n */\nexport class ClientOutDirNotSupportedError extends Error {\n public constructor() {\n super(\n '\"build.clientOutDir\" is not supported: the production server reads the client bundle ' +\n \"from its default location and does not consult this option, so a build honoring it \" +\n \"would produce an artifact production cannot serve correctly. Remove \" +\n '\"build.clientOutDir\" from the build config.',\n );\n this.name = \"ClientOutDirNotSupportedError\";\n }\n}\n\n/** The package name every candidate web root must declare to be one. */\nconst WEB_PACKAGE_NAME = \"@warlock.js/web\";\n\n/**\n * The resolved `@warlock.js/web` root is not that package.\n *\n * Same posture as the manifest resolver's three named errors: a root that\n * cannot be proven is REJECTED, never quietly\n * repaired by walking upwards or falling back to `process.cwd()`. A wrong root\n * would otherwise surface much later as an unintelligible Vite entry failure.\n */\nexport class WebPackageRootResolutionError extends Error {\n public constructor(webRoot: string, reason: string, cause?: unknown) {\n super(\n `Cannot resolve the \"${WEB_PACKAGE_NAME}\" package root: \"${webRoot}\" ${reason}. ` +\n \"Pass `webRoot` explicitly in the web build options to point at the package root \" +\n `whose package.json declares \"name\": \"${WEB_PACKAGE_NAME}\".`,\n { cause },\n );\n this.name = \"WebPackageRootResolutionError\";\n }\n}\n\n/**\n * Proves that `webRoot` really is the `@warlock.js/web` package root by reading\n * `<webRoot>/package.json` and matching its `name`. Returns the path unchanged.\n */\nexport function assertWebPackageRoot(webRoot: string): string {\n const manifestPath = path.join(webRoot, \"package.json\");\n\n let raw: string;\n\n try {\n raw = readFileSync(manifestPath, \"utf-8\");\n } catch (error) {\n throw new WebPackageRootResolutionError(webRoot, \"has no readable package.json\", error);\n }\n\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n throw new WebPackageRootResolutionError(\n webRoot,\n \"has a package.json that is not valid JSON\",\n error,\n );\n }\n\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new WebPackageRootResolutionError(\n webRoot,\n \"has a package.json whose root is not a JSON object\",\n );\n }\n\n const name = (parsed as { name?: unknown }).name;\n\n if (name !== WEB_PACKAGE_NAME) {\n throw new WebPackageRootResolutionError(\n webRoot,\n `declares package name ${JSON.stringify(name)}, not \"${WEB_PACKAGE_NAME}\"`,\n );\n }\n\n return webRoot;\n}\n\nexport async function resolveWebPackageRoot(configured: string | undefined): Promise<string> {\n if (configured !== undefined) {\n return assertWebPackageRoot(path.resolve(configured));\n }\n\n const { fileURLToPath } = await import(\"node:url\");\n\n // `web/src/build/contribution.ts` published as `web/esm/build/contribution.js`\n // — two levels up is the package root under both layouts.\n return assertWebPackageRoot(\n path.resolve(path.dirname(fileURLToPath(import.meta.url)), \"..\", \"..\"),\n );\n}\n\n/**\n * Builds web's `ConnectorBuildContribution`.\n *\n * `generate` writes the page barrel into `ctx.productionDir` and patches\n * esbuild; `emit` produces the client bundle esbuild cannot. The two share one\n * closure fact — how many pages exist — because a build with zero pages must\n * skip the client bundle rather than emit an orphan one. `generate` itself does\n * NOT skip: the barrel and its entry import are emitted either way, so the\n * runtime can tell a page-free web build from a build without web.\n */\nexport function createWebBuildContribution(\n options: WebBuildOptions = {},\n): ConnectorBuildContribution {\n if (options.clientOutDir !== undefined) {\n throw new ClientOutDirNotSupportedError();\n }\n\n let pageCount = 0;\n\n return {\n async generate(context: ConnectorBuildContext): Promise<ConnectorBuildGenerateResult | void> {\n const { generatePagesBarrel, WEB_ENTRY_IMPORT, WEB_ESBUILD_PATCH } = await import(\n \"./generate-pages-barrel\"\n );\n\n const result = await generatePagesBarrel({\n appRoot: context.appRoot,\n productionDir: context.productionDir,\n srcDir: options.srcDir,\n // Derived from `resolveClientOutDir` — the SAME function `emit` passes\n // to the Vite build below — so the path baked into the manifest and\n // the path the bundle is written to are one expression, not two that\n // happen to agree today.\n // POSIX-normalised inline rather than via the generator's `toPosix`:\n // this module's static graph is `node:path` and types only, and the\n // generator is reached by `await import` precisely to keep it that way.\n clientDir: path\n .relative(context.appRoot, resolveClientOutDir(context))\n .split(path.sep)\n .join(\"/\"),\n });\n\n pageCount = result.pageCount;\n\n // Contributed unconditionally, zero pages included: the barrel is always\n // written, and the entry has to IMPORT it for the empty table to reach\n // the runtime. Withholding this line on zero pages would leave the\n // manifest absent, which is the runtime's signal for \"never built with\n // web\" — the exact confusion the always-written barrel removes.\n return { entryImports: [WEB_ENTRY_IMPORT], esbuild: WEB_ESBUILD_PATCH };\n },\n\n async emit(context: ConnectorBuildContext): Promise<void> {\n if (pageCount === 0) {\n return;\n }\n\n const { buildWarlockHydrationClient } = await import(\"../vite\");\n const { appConventionAliases } = await import(\"../vite/app-convention-aliases\");\n\n // The app-tree convention (`web/*`, `app/*`) the dev server installs must\n // also reach the production build, from the SAME definition — see\n // `app-convention-aliases.ts`. Caller-supplied aliases come first so a\n // caller can win a conflict, matching the dev server's ordering.\n const callerAliases = Object.entries(options.aliases ?? {}).map(([find, replacement]) => ({\n find,\n replacement,\n }));\n\n await buildWarlockHydrationClient({\n appRoot: context.appRoot,\n webRoot: await resolveWebPackageRoot(options.webRoot),\n outDir: resolveClientOutDir(context),\n resolveAliases: [\n ...callerAliases,\n ...appConventionAliases(path.join(context.appRoot, options.srcDir ?? \"src\")),\n ],\n external: options.external,\n });\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAsDA,SAAS,oBAAoB,SAAwC;CACnE,OAAO,KAAK,QAAQ,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,QAAQ;AACvE;;;;;;;;;;;;AAaA,IAAa,gCAAb,cAAmD,MAAM;CACvD,AAAO,cAAc;EACnB,MACE,6RAIF;EACA,KAAK,OAAO;CACd;AACF;;AAGA,MAAM,mBAAmB;;;;;;;;;AAUzB,IAAa,gCAAb,cAAmD,MAAM;CACvD,AAAO,YAAY,SAAiB,QAAgB,OAAiB;EACnE,MACE,uBAAuB,iBAAiB,mBAAmB,QAAQ,IAAI,OAAO,2HAEpC,iBAAiB,KAC3D,EAAE,MAAM,CACV;EACA,KAAK,OAAO;CACd;AACF;;;;;AAMA,SAAgB,qBAAqB,SAAyB;CAC5D,MAAM,eAAe,KAAK,KAAK,SAAS,cAAc;CAEtD,IAAI;CAEJ,IAAI;EACF,MAAM,aAAa,cAAc,OAAO;CAC1C,SAAS,OAAO;EACd,MAAM,IAAI,8BAA8B,SAAS,gCAAgC,KAAK;CACxF;CAEA,IAAI;CAEJ,IAAI;EACF,SAAS,KAAK,MAAM,GAAG;CACzB,SAAS,OAAO;EACd,MAAM,IAAI,8BACR,SACA,6CACA,KACF;CACF;CAEA,IAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GACvE,MAAM,IAAI,8BACR,SACA,oDACF;CAGF,MAAM,OAAQ,OAA8B;CAE5C,IAAI,SAAS,kBACX,MAAM,IAAI,8BACR,SACA,yBAAyB,KAAK,UAAU,IAAI,EAAE,SAAS,iBAAiB,EAC1E;CAGF,OAAO;AACT;AAEA,eAAsB,sBAAsB,YAAiD;CAC3F,IAAI,eAAe,QACjB,OAAO,qBAAqB,KAAK,QAAQ,UAAU,CAAC;CAGtD,MAAM,EAAE,kBAAkB,MAAM,OAAO;CAIvC,OAAO,qBACL,KAAK,QAAQ,KAAK,QAAQ,cAAc,OAAO,KAAK,GAAG,CAAC,GAAG,MAAM,IAAI,CACvE;AACF;;;;;;;;;;;AAYA,SAAgB,2BACd,UAA2B,CAAC,GACA;CAC5B,IAAI,QAAQ,iBAAiB,QAC3B,MAAM,IAAI,8BAA8B;CAG1C,IAAI,YAAY;CAEhB,OAAO;EACL,MAAM,SAAS,SAA8E;GAC3F,MAAM,EAAE,qBAAqB,kBAAkB,sBAAsB,MAAM,OACzE;GAoBF,aAAY,MAjBS,oBAAoB;IACvC,SAAS,QAAQ;IACjB,eAAe,QAAQ;IACvB,QAAQ,QAAQ;IAQhB,WAAW,KACR,SAAS,QAAQ,SAAS,oBAAoB,OAAO,CAAC,CAAC,CACvD,MAAM,KAAK,GAAG,CAAC,CACf,KAAK,GAAG;GACb,CAAC,EAEiB,CAAC;GAOnB,OAAO;IAAE,cAAc,CAAC,gBAAgB;IAAG,SAAS;GAAkB;EACxE;EAEA,MAAM,KAAK,SAA+C;GACxD,IAAI,cAAc,GAChB;GAGF,MAAM,EAAE,gCAAgC,MAAM,OAAO;GACrD,MAAM,EAAE,yBAAyB,MAAM,OAAO;GAM9C,MAAM,gBAAgB,OAAO,QAAQ,QAAQ,WAAW,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,kBAAkB;IACxF;IACA;GACF,EAAE;GAEF,MAAM,4BAA4B;IAChC,SAAS,QAAQ;IACjB,SAAS,MAAM,sBAAsB,QAAQ,OAAO;IACpD,QAAQ,oBAAoB,OAAO;IACnC,gBAAgB,CACd,GAAG,eACH,GAAG,qBAAqB,KAAK,KAAK,QAAQ,SAAS,QAAQ,UAAU,KAAK,CAAC,CAC7E;IACA,UAAU,QAAQ;GACpB,CAAC;EACH;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"contribution.mjs","names":[],"sources":["C:/Work/Warlock.js/web/src/build/contribution.ts"],"sourcesContent":["/**\r\n * Web's build-time half — the object a `WebConnector` hands to\r\n * `warlock build` as its `build` contribution.\r\n *\r\n * KEPT DELIBERATELY LIGHT: everything this\r\n * module imports statically is a type or `node:path`. The heavy halves — the\r\n * filesystem discovery/barrel generator, and the Vite client build — are\r\n * `await import(...)`ed INSIDE the hooks, so a config file that merely\r\n * constructs the connector never drags Vite, React or the page graph into its\r\n * static import graph.\r\n *\r\n * {@link WebBuildOptions} carries JSON-SERIALIZABLE VALUES ONLY (constraint\r\n * B): no plugin instances, no functions, no class instances. Anything heavy is\r\n * constructed inside a hook after that hook's dynamic import.\r\n */\r\nimport { readFileSync } from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport type {\r\n ConnectorBuildContext,\r\n ConnectorBuildContribution,\r\n ConnectorBuildGenerateResult,\r\n} from \"@warlock.js/core\";\r\nimport type { PluginOption } from \"vite\";\r\n\r\nexport type WebBuildOptions = {\r\n /** Source directory name under the app root. Default: `\"src\"`. */\r\n srcDir?: string;\r\n /**\r\n * Absolute path to the `@warlock.js/web` package root, which holds the\r\n * hydration entry (packaged as `esm/hydration/index.mjs`, with a source\r\n * fallback at `src/hydration/index.ts` for this checkout).\r\n *\r\n * Defaults to this module's own package root, derived from `import.meta.url`\r\n * at emit time. Set it explicitly when the build process loads this module\r\n * through a transform that rewrites `import.meta` (e.g. a CJS config\r\n * pipeline) — the derivation is the only thing here that depends on it.\r\n *\r\n * Derived or configured, the root is VERIFIED at emit time against\r\n * `<root>/package.json`'s `name`; a root that is not `@warlock.js/web`\r\n * throws {@link WebPackageRootResolutionError} rather than being guessed at.\r\n */\r\n webRoot?: string;\r\n /**\r\n * NOT SUPPORTED. The client bundle always lands at `<build.outdir>/client`,\r\n * the layout the runtime half reads (`resolveHydrationClientUrl`) — the\r\n * runtime does not yet consult a relocated path. Setting this option throws\r\n * {@link ClientOutDirNotSupportedError}.\r\n */\r\n clientOutDir?: string;\r\n /** Client-build resolve aliases: specifier -> absolute path. */\r\n aliases?: Record<string, string>;\r\n /** Extra package names to keep external to the client bundle. */\r\n external?: string[];\r\n};\r\n\r\nfunction resolveClientOutDir(context: ConnectorBuildContext): string {\r\n return path.resolve(context.appRoot, context.options.outdir, \"client\");\r\n}\r\n\r\n/**\r\n * `build.clientOutDir` was set.\r\n *\r\n * The production runtime hardcodes where it reads the client bundle from\r\n * (`<outdir>/client`, via `resolveHydrationClientUrl`) — it does not yet\r\n * consult the build config for a relocated path. A build that honored a\r\n * custom `clientOutDir` would therefore produce an artifact the runtime\r\n * cannot find, silently. Rejected at build start, before any work happens,\r\n * rather than left to surface later as a missing-bundle failure in\r\n * production.\r\n */\r\nexport class ClientOutDirNotSupportedError extends Error {\r\n public constructor() {\r\n super(\r\n '\"build.clientOutDir\" is not supported: the production server reads the client bundle ' +\r\n \"from its default location and does not consult this option, so a build honoring it \" +\r\n \"would produce an artifact production cannot serve correctly. Remove \" +\r\n '\"build.clientOutDir\" from the build config.',\r\n );\r\n this.name = \"ClientOutDirNotSupportedError\";\r\n }\r\n}\r\n\r\n/** The package name every candidate web root must declare to be one. */\r\nconst WEB_PACKAGE_NAME = \"@warlock.js/web\";\r\n\r\n/**\r\n * The resolved `@warlock.js/web` root is not that package.\r\n *\r\n * Same posture as the manifest resolver's three named errors: a root that\r\n * cannot be proven is REJECTED, never quietly\r\n * repaired by walking upwards or falling back to `process.cwd()`. A wrong root\r\n * would otherwise surface much later as an unintelligible Vite entry failure.\r\n */\r\nexport class WebPackageRootResolutionError extends Error {\r\n public constructor(webRoot: string, reason: string, cause?: unknown) {\r\n super(\r\n `Cannot resolve the \"${WEB_PACKAGE_NAME}\" package root: \"${webRoot}\" ${reason}. ` +\r\n \"Pass `webRoot` explicitly in the web build options to point at the package root \" +\r\n `whose package.json declares \"name\": \"${WEB_PACKAGE_NAME}\".`,\r\n { cause },\r\n );\r\n this.name = \"WebPackageRootResolutionError\";\r\n }\r\n}\r\n\r\n/**\r\n * Proves that `webRoot` really is the `@warlock.js/web` package root by reading\r\n * `<webRoot>/package.json` and matching its `name`. Returns the path unchanged.\r\n */\r\nexport function assertWebPackageRoot(webRoot: string): string {\r\n const manifestPath = path.join(webRoot, \"package.json\");\r\n\r\n let raw: string;\r\n\r\n try {\r\n raw = readFileSync(manifestPath, \"utf-8\");\r\n } catch (error) {\r\n throw new WebPackageRootResolutionError(webRoot, \"has no readable package.json\", error);\r\n }\r\n\r\n let parsed: unknown;\r\n\r\n try {\r\n parsed = JSON.parse(raw);\r\n } catch (error) {\r\n throw new WebPackageRootResolutionError(\r\n webRoot,\r\n \"has a package.json that is not valid JSON\",\r\n error,\r\n );\r\n }\r\n\r\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\r\n throw new WebPackageRootResolutionError(\r\n webRoot,\r\n \"has a package.json whose root is not a JSON object\",\r\n );\r\n }\r\n\r\n const name = (parsed as { name?: unknown }).name;\r\n\r\n if (name !== WEB_PACKAGE_NAME) {\r\n throw new WebPackageRootResolutionError(\r\n webRoot,\r\n `declares package name ${JSON.stringify(name)}, not \"${WEB_PACKAGE_NAME}\"`,\r\n );\r\n }\r\n\r\n return webRoot;\r\n}\r\n\r\nexport async function resolveWebPackageRoot(configured: string | undefined): Promise<string> {\r\n if (configured !== undefined) {\r\n return assertWebPackageRoot(path.resolve(configured));\r\n }\r\n\r\n const { fileURLToPath } = await import(\"node:url\");\r\n\r\n // `web/src/build/contribution.ts` published as `web/esm/build/contribution.js`\r\n // — two levels up is the package root under both layouts.\r\n return assertWebPackageRoot(\r\n path.resolve(path.dirname(fileURLToPath(import.meta.url)), \"..\", \"..\"),\r\n );\r\n}\r\n\r\n/**\r\n * Builds web's `ConnectorBuildContribution`.\r\n *\r\n * `generate` writes the page barrel into `ctx.productionDir` and patches\r\n * esbuild; `emit` produces the client bundle esbuild cannot. The two share one\r\n * closure facts — how many pages exist, the discovered route table, and the\n * exact app-public file list. A build with zero pages skips the hydration\n * bundle but still copies public files when present. `generate` itself does\n * NOT skip: the barrel and its entry import are emitted either way, so the\n * runtime can tell a page-free web build from a build without web.\n *\r\n * Connector plugins are a separate argument rather than a `WebBuildOptions`\r\n * field: the app constructs them once, the factory snapshots that array for\r\n * both dev and build, and this module keeps Vite as a type-only import until\r\n * `emit` dynamically loads the real client builder.\r\n */\r\nexport function createWebBuildContribution(\r\n options: WebBuildOptions = {},\r\n connectorPlugins: readonly PluginOption[] = [],\r\n): ConnectorBuildContribution {\r\n if (options.clientOutDir !== undefined) {\r\n throw new ClientOutDirNotSupportedError();\r\n }\r\n\r\n let pageCount = 0;\r\n let pageRoutes: import(\"./generate-pages-barrel\").PageRoutesManifest = {\n version: 1,\n routes: [],\n };\n let publicFiles: string[] = [];\n\r\n return {\r\n async generate(context: ConnectorBuildContext): Promise<ConnectorBuildGenerateResult | void> {\r\n const { generatePagesBarrel, WEB_ENTRY_IMPORT, WEB_ESBUILD_PATCH } =\n await import(\"./generate-pages-barrel\");\n const { collectPublicFiles } = await import(\"./public-files\");\n\n publicFiles = await collectPublicFiles(path.join(context.appRoot, \"public\"));\n\n const result = await generatePagesBarrel({\n appRoot: context.appRoot,\r\n productionDir: context.productionDir,\r\n srcDir: options.srcDir,\r\n // Derived from `resolveClientOutDir` — the SAME function `emit` passes\r\n // to the Vite build below — so the path baked into the manifest and\r\n // the path the bundle is written to are one expression, not two that\r\n // happen to agree today.\r\n // POSIX-normalised inline rather than via the generator's `toPosix`:\r\n // this module's static graph is `node:path` and types only, and the\r\n // generator is reached by `await import` precisely to keep it that way.\r\n clientDir: path\r\n .relative(context.appRoot, resolveClientOutDir(context))\r\n .split(path.sep)\n .join(\"/\"),\n publicFiles,\n });\n\r\n pageCount = result.pageCount;\r\n pageRoutes = result.pageRoutes;\r\n\r\n // Contributed unconditionally, zero pages included: the barrel is always\r\n // written, and the entry has to IMPORT it for the empty table to reach\r\n // the runtime. Withholding this line on zero pages would leave the\r\n // manifest absent, which is the runtime's signal for \"never built with\r\n // web\" — the exact confusion the always-written barrel removes.\r\n return { entryImports: [WEB_ENTRY_IMPORT], esbuild: WEB_ESBUILD_PATCH };\r\n },\r\n\r\n async emit(context: ConnectorBuildContext): Promise<void> {\r\n if (pageCount > 0) {\n const { buildWarlockHydrationClient } = await import(\"../vite\");\r\n const { appConventionAliases } = await import(\"../vite/app-convention-aliases\");\r\n\r\n // The app-tree convention (`web/*`, `app/*`) the dev server installs must\r\n // also reach the production build, from the SAME definition — see\r\n // `app-convention-aliases.ts`. Caller-supplied aliases come first so a\r\n // caller can win a conflict, matching the dev server's ordering.\r\n const callerAliases = Object.entries(options.aliases ?? {}).map(([find, replacement]) => ({\r\n find,\r\n replacement,\r\n }));\r\n\r\n await buildWarlockHydrationClient({\r\n appRoot: context.appRoot,\r\n webRoot: await resolveWebPackageRoot(options.webRoot),\r\n outDir: resolveClientOutDir(context),\r\n resolveAliases: [\r\n ...callerAliases,\r\n ...appConventionAliases(path.join(context.appRoot, options.srcDir ?? \"src\")),\r\n ],\r\n external: options.external,\r\n plugins: connectorPlugins,\r\n });\n }\n\n if (publicFiles.length > 0) {\n const { copyPublicFiles } = await import(\"./public-files\");\n await copyPublicFiles(\n path.join(context.appRoot, \"public\"),\n path.join(resolveClientOutDir(context), \"public\"),\n publicFiles,\n );\n }\n\n const { writePageRoutesManifest } = await import(\"./page-routes-manifest\");\n await writePageRoutesManifest(\r\n path.resolve(context.appRoot, context.options.outdir),\r\n pageRoutes,\r\n );\r\n },\r\n };\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAuDA,SAAS,oBAAoB,SAAwC;CACnE,OAAO,KAAK,QAAQ,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,QAAQ;AACvE;;;;;;;;;;;;AAaA,IAAa,gCAAb,cAAmD,MAAM;CACvD,AAAO,cAAc;EACnB,MACE,6RAIF;EACA,KAAK,OAAO;CACd;AACF;;AAGA,MAAM,mBAAmB;;;;;;;;;AAUzB,IAAa,gCAAb,cAAmD,MAAM;CACvD,AAAO,YAAY,SAAiB,QAAgB,OAAiB;EACnE,MACE,uBAAuB,iBAAiB,mBAAmB,QAAQ,IAAI,OAAO,2HAEpC,iBAAiB,KAC3D,EAAE,MAAM,CACV;EACA,KAAK,OAAO;CACd;AACF;;;;;AAMA,SAAgB,qBAAqB,SAAyB;CAC5D,MAAM,eAAe,KAAK,KAAK,SAAS,cAAc;CAEtD,IAAI;CAEJ,IAAI;EACF,MAAM,aAAa,cAAc,OAAO;CAC1C,SAAS,OAAO;EACd,MAAM,IAAI,8BAA8B,SAAS,gCAAgC,KAAK;CACxF;CAEA,IAAI;CAEJ,IAAI;EACF,SAAS,KAAK,MAAM,GAAG;CACzB,SAAS,OAAO;EACd,MAAM,IAAI,8BACR,SACA,6CACA,KACF;CACF;CAEA,IAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GACvE,MAAM,IAAI,8BACR,SACA,oDACF;CAGF,MAAM,OAAQ,OAA8B;CAE5C,IAAI,SAAS,kBACX,MAAM,IAAI,8BACR,SACA,yBAAyB,KAAK,UAAU,IAAI,EAAE,SAAS,iBAAiB,EAC1E;CAGF,OAAO;AACT;AAEA,eAAsB,sBAAsB,YAAiD;CAC3F,IAAI,eAAe,QACjB,OAAO,qBAAqB,KAAK,QAAQ,UAAU,CAAC;CAGtD,MAAM,EAAE,kBAAkB,MAAM,OAAO;CAIvC,OAAO,qBACL,KAAK,QAAQ,KAAK,QAAQ,cAAc,OAAO,KAAK,GAAG,CAAC,GAAG,MAAM,IAAI,CACvE;AACF;;;;;;;;;;;;;;;;;AAkBA,SAAgB,2BACd,UAA2B,CAAC,GAC5B,mBAA4C,CAAC,GACjB;CAC5B,IAAI,QAAQ,iBAAiB,QAC3B,MAAM,IAAI,8BAA8B;CAG1C,IAAI,YAAY;CAChB,IAAI,aAAmE;EACrE,SAAS;EACT,QAAQ,CAAC;CACX;CACA,IAAI,cAAwB,CAAC;CAE7B,OAAO;EACL,MAAM,SAAS,SAA8E;GAC3F,MAAM,EAAE,qBAAqB,kBAAkB,sBAC7C,MAAM,OAAO;GACf,MAAM,EAAE,uBAAuB,MAAM,OAAO;GAE5C,cAAc,MAAM,mBAAmB,KAAK,KAAK,QAAQ,SAAS,QAAQ,CAAC;GAE3E,MAAM,SAAS,MAAM,oBAAoB;IACvC,SAAS,QAAQ;IACjB,eAAe,QAAQ;IACvB,QAAQ,QAAQ;IAQhB,WAAW,KACR,SAAS,QAAQ,SAAS,oBAAoB,OAAO,CAAC,EACtD,MAAM,KAAK,GAAG,EACd,KAAK,GAAG;IACX;GACF,CAAC;GAED,YAAY,OAAO;GACnB,aAAa,OAAO;GAOpB,OAAO;IAAE,cAAc,CAAC,gBAAgB;IAAG,SAAS;GAAkB;EACxE;EAEA,MAAM,KAAK,SAA+C;GACxD,IAAI,YAAY,GAAG;IACjB,MAAM,EAAE,gCAAgC,MAAM,OAAO;IACrD,MAAM,EAAE,yBAAyB,MAAM,OAAO;IAM9C,MAAM,gBAAgB,OAAO,QAAQ,QAAQ,WAAW,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,kBAAkB;KACxF;KACA;IACF,EAAE;IAEF,MAAM,4BAA4B;KAChC,SAAS,QAAQ;KACjB,SAAS,MAAM,sBAAsB,QAAQ,OAAO;KACpD,QAAQ,oBAAoB,OAAO;KACnC,gBAAgB,CACd,GAAG,eACH,GAAG,qBAAqB,KAAK,KAAK,QAAQ,SAAS,QAAQ,UAAU,KAAK,CAAC,CAC7E;KACA,UAAU,QAAQ;KAClB,SAAS;IACX,CAAC;GACH;GAEA,IAAI,YAAY,SAAS,GAAG;IAC1B,MAAM,EAAE,oBAAoB,MAAM,OAAO;IACzC,MAAM,gBACJ,KAAK,KAAK,QAAQ,SAAS,QAAQ,GACnC,KAAK,KAAK,oBAAoB,OAAO,GAAG,QAAQ,GAChD,WACF;GACF;GAEA,MAAM,EAAE,4BAA4B,MAAM,OAAO;GACjD,MAAM,wBACJ,KAAK,QAAQ,QAAQ,SAAS,QAAQ,QAAQ,MAAM,GACpD,UACF;EACF;CACF;AACF"}
|