arcway 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -8
- package/client/dynamic.js +48 -0
- package/client/router.js +13 -5
- package/package.json +3 -1
- package/server/boot/hooks.js +19 -0
- package/server/boot/index.js +18 -8
- package/server/context.js +10 -0
- package/server/db/index.js +9 -0
- package/server/events/handler.js +0 -1
- package/server/pages/arcway-endpoint.js +58 -0
- package/server/pages/build-client.js +110 -25
- package/server/pages/build-context.js +1 -1
- package/server/pages/build-css.js +23 -9
- package/server/pages/build-manifest.js +5 -4
- package/server/pages/build-server.js +80 -63
- package/server/pages/build.js +42 -4
- package/server/pages/chunk-graph.js +59 -0
- package/server/pages/compress.js +103 -0
- package/server/pages/discovery.js +99 -0
- package/server/pages/handler.js +107 -3
- package/server/pages/lazy-context.js +640 -0
- package/server/pages/pages-router.js +16 -21
- package/server/pages/ssr.js +16 -4
- package/server/pages/static.js +51 -4
- package/server/pages/watcher.js +59 -76
- package/server/static/index.js +1 -1
- package/server/ws/ws-router.js +45 -2
package/server/pages/handler.js
CHANGED
|
@@ -6,6 +6,7 @@ import { compilePattern, sortBySpecificity, matchPattern } from '../router/route
|
|
|
6
6
|
import { parseCookies, resolveSession, flattenHeaders } from '../session/helpers.js';
|
|
7
7
|
import {
|
|
8
8
|
buildCssLinkTag,
|
|
9
|
+
buildRouteCssLinkTags,
|
|
9
10
|
buildScriptTags,
|
|
10
11
|
loadComponent,
|
|
11
12
|
renderErrorPage,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
syncReactInternals,
|
|
14
15
|
} from './ssr.js';
|
|
15
16
|
import { MIME_TYPES } from './static.js';
|
|
17
|
+
import { createArcwayDevEndpoint } from './arcway-endpoint.js';
|
|
16
18
|
function createPagesHandler(options) {
|
|
17
19
|
const rootDir = options.rootDir;
|
|
18
20
|
const outDir = path.resolve(rootDir, options.outDir ?? '.build/pages');
|
|
@@ -21,7 +23,10 @@ function createPagesHandler(options) {
|
|
|
21
23
|
const appContext = options.appContext ?? null;
|
|
22
24
|
const mode = options.mode ?? 'production';
|
|
23
25
|
const devMode = mode === 'development';
|
|
24
|
-
|
|
26
|
+
const lazyContext = options.lazyContext ?? null;
|
|
27
|
+
// Dev with a lazy context drives the handler off an in-memory manifest; no
|
|
28
|
+
// disk `pages-manifest.json` is produced. Prod still reads from disk.
|
|
29
|
+
if (!lazyContext && !fs.existsSync(manifestPath)) {
|
|
25
30
|
return null;
|
|
26
31
|
}
|
|
27
32
|
const projectRequire = createRequire(path.join(options.rootDir, 'package.json'));
|
|
@@ -31,16 +36,47 @@ function createPagesHandler(options) {
|
|
|
31
36
|
renderToPipeableStream: projectRequire('react-dom/server').renderToPipeableStream,
|
|
32
37
|
};
|
|
33
38
|
syncReactInternals(projectReactModule);
|
|
34
|
-
let manifest =
|
|
39
|
+
let manifest = lazyContext
|
|
40
|
+
? lazyContext.manifest
|
|
41
|
+
: JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
35
42
|
let routes = compileRoutes(manifest);
|
|
36
43
|
let clientManifestJson = buildClientManifestJson(manifest);
|
|
44
|
+
let lastSeenVersion = lazyContext ? manifest.version : 0;
|
|
37
45
|
const componentCache = new Map();
|
|
38
46
|
let cacheVersion = 0;
|
|
39
47
|
const reloadEmitter = devMode ? new EventEmitter() : null;
|
|
40
|
-
if (
|
|
48
|
+
if (
|
|
49
|
+
!lazyContext &&
|
|
50
|
+
manifest.entries.length === 0 &&
|
|
51
|
+
!manifest.notFoundBundle &&
|
|
52
|
+
!manifest.errorBundle
|
|
53
|
+
) {
|
|
41
54
|
return null;
|
|
42
55
|
}
|
|
56
|
+
// Keep `routes` / `clientManifestJson` in sync with the lazy context's
|
|
57
|
+
// mutable manifest. Every `built` / `invalidate` event bumps the version,
|
|
58
|
+
// which is our cheap change signal — recompile only when it moves.
|
|
59
|
+
function refreshFromLazy() {
|
|
60
|
+
if (!lazyContext) return;
|
|
61
|
+
if (manifest.version === lastSeenVersion) return;
|
|
62
|
+
lastSeenVersion = manifest.version;
|
|
63
|
+
routes = compileRoutes(manifest);
|
|
64
|
+
clientManifestJson = buildClientManifestJson(manifest);
|
|
65
|
+
componentCache.clear();
|
|
66
|
+
cacheVersion++;
|
|
67
|
+
}
|
|
68
|
+
if (lazyContext) {
|
|
69
|
+
lazyContext.on('update', (event) => {
|
|
70
|
+
refreshFromLazy();
|
|
71
|
+
if (reloadEmitter) reloadEmitter.emit('update', event);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const arcwayEndpoint = lazyContext ? createArcwayDevEndpoint(lazyContext) : null;
|
|
43
75
|
function reload() {
|
|
76
|
+
if (lazyContext) {
|
|
77
|
+
refreshFromLazy();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
44
80
|
if (!fs.existsSync(manifestPath)) return;
|
|
45
81
|
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
46
82
|
routes = compileRoutes(manifest);
|
|
@@ -57,6 +93,9 @@ function createPagesHandler(options) {
|
|
|
57
93
|
const url = req.url ?? '/';
|
|
58
94
|
const pathname = url.split('?')[0];
|
|
59
95
|
if (method !== 'GET') return false;
|
|
96
|
+
// Internal `/_arcway/*` endpoints (dev only) resolve before the route
|
|
97
|
+
// matcher so they can never be shadowed by a user-defined page.
|
|
98
|
+
if (arcwayEndpoint && arcwayEndpoint(req, res)) return true;
|
|
60
99
|
if (reloadEmitter && pathname === '/__livereload') {
|
|
61
100
|
res.writeHead(200, {
|
|
62
101
|
'Content-Type': 'text/event-stream',
|
|
@@ -73,6 +112,9 @@ function createPagesHandler(options) {
|
|
|
73
112
|
req.on('close', () => reloadEmitter.off('update', onUpdate));
|
|
74
113
|
return true;
|
|
75
114
|
}
|
|
115
|
+
// Pick up any invalidations or completed builds that landed since the
|
|
116
|
+
// last request before we resolve the route.
|
|
117
|
+
refreshFromLazy();
|
|
76
118
|
const matched = matchPageRoute(routes, pathname);
|
|
77
119
|
if (!matched) {
|
|
78
120
|
if (manifest.notFoundBundle) {
|
|
@@ -91,6 +133,28 @@ function createPagesHandler(options) {
|
|
|
91
133
|
}
|
|
92
134
|
return false;
|
|
93
135
|
}
|
|
136
|
+
// Lazy dev: trigger on-demand build of this page's server bundle +
|
|
137
|
+
// every ancestor layout/middleware before we attempt to load any
|
|
138
|
+
// component. ensurePageBuilt() coalesces concurrent calls internally.
|
|
139
|
+
if (lazyContext) {
|
|
140
|
+
try {
|
|
141
|
+
await lazyContext.ensurePageBuilt(matched.route.pattern);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
await renderDevBuildError(
|
|
144
|
+
err,
|
|
145
|
+
res,
|
|
146
|
+
manifest,
|
|
147
|
+
outDir,
|
|
148
|
+
componentCache,
|
|
149
|
+
cacheVersion,
|
|
150
|
+
projectReact,
|
|
151
|
+
);
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
refreshFromLazy();
|
|
155
|
+
const rematched = matchPageRoute(routes, pathname);
|
|
156
|
+
if (rematched) matched.route = rematched.route;
|
|
157
|
+
}
|
|
94
158
|
if (matched.route.middlewareServerBundles.length > 0) {
|
|
95
159
|
const middlewareResult = await runPageMiddleware(
|
|
96
160
|
matched.route,
|
|
@@ -174,6 +238,43 @@ function createPagesHandler(options) {
|
|
|
174
238
|
: {}),
|
|
175
239
|
};
|
|
176
240
|
}
|
|
241
|
+
// Render a dev-only build-error page. Preferred path: use the project's
|
|
242
|
+
// `_error.jsx` bundle so devs see the same shell they'd see for runtime errors,
|
|
243
|
+
// with the esbuild message in the `error` prop. Fallback: plain-text 500 when
|
|
244
|
+
// no errorBundle is available or the response has already started.
|
|
245
|
+
async function renderDevBuildError(
|
|
246
|
+
err,
|
|
247
|
+
res,
|
|
248
|
+
manifest,
|
|
249
|
+
outDir,
|
|
250
|
+
componentCache,
|
|
251
|
+
cacheVersion,
|
|
252
|
+
projectReact,
|
|
253
|
+
) {
|
|
254
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
255
|
+
if (manifest.errorBundle && !res.headersSent) {
|
|
256
|
+
await renderErrorPage(
|
|
257
|
+
manifest.errorBundle,
|
|
258
|
+
500,
|
|
259
|
+
{ error: errorMessage },
|
|
260
|
+
outDir,
|
|
261
|
+
res,
|
|
262
|
+
componentCache,
|
|
263
|
+
manifest,
|
|
264
|
+
cacheVersion,
|
|
265
|
+
projectReact,
|
|
266
|
+
);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (!res.headersSent) {
|
|
270
|
+
const escaped = String(errorMessage)
|
|
271
|
+
.replace(/&/g, '&')
|
|
272
|
+
.replace(/</g, '<')
|
|
273
|
+
.replace(/>/g, '>');
|
|
274
|
+
res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
275
|
+
res.end(`<h1>500 - Build Error</h1><pre>${escaped}</pre>`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
177
278
|
function compileRoutes(manifest) {
|
|
178
279
|
const routes = manifest.entries.map((entry) => {
|
|
179
280
|
const { regex, paramNames, catchAllParam } = compilePattern(entry.pattern);
|
|
@@ -186,6 +287,8 @@ function compileRoutes(manifest) {
|
|
|
186
287
|
clientBundle: entry.clientBundle,
|
|
187
288
|
layoutServerBundles: entry.layoutServerBundles ?? [],
|
|
188
289
|
middlewareServerBundles: entry.middlewareServerBundles ?? [],
|
|
290
|
+
sharedChunks: entry.sharedChunks ?? [],
|
|
291
|
+
sharedCssChunks: entry.sharedCssChunks ?? [],
|
|
189
292
|
};
|
|
190
293
|
});
|
|
191
294
|
sortBySpecificity(routes);
|
|
@@ -269,6 +372,7 @@ export {
|
|
|
269
372
|
MIME_TYPES,
|
|
270
373
|
buildClientManifestJson,
|
|
271
374
|
buildCssLinkTag,
|
|
375
|
+
buildRouteCssLinkTags,
|
|
272
376
|
buildScriptTags,
|
|
273
377
|
createPagesHandler,
|
|
274
378
|
matchPageRoute,
|