arcway 0.1.22 → 0.1.24
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/client/page-loader.js +45 -0
- package/client/router.js +25 -2
- package/package.json +4 -1
- package/server/boot/index.js +10 -1
- package/server/build.js +2 -0
- package/server/config/modules/pages.js +16 -1
- package/server/pages/build-cache.js +1 -1
- package/server/pages/build-client.js +23 -3
- package/server/pages/handler.js +38 -10
- package/server/pages/hmr.js +58 -31
- package/server/pages/lazy-context.js +227 -39
- package/server/pages/out-dir.js +11 -0
- package/server/pages/pages-router.js +7 -0
- package/server/pages/ssr.js +23 -7
- package/server/pages/vite-dev.js +246 -0
- package/server/pages/watcher.js +3 -1
- package/server/watcher.js +11 -2
package/client/page-loader.js
CHANGED
|
@@ -36,6 +36,50 @@ function readClientManifest() {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function syncStylesForPath(manifest, pathname) {
|
|
40
|
+
if (typeof document === 'undefined' || !manifest) return;
|
|
41
|
+
|
|
42
|
+
const globalHref = manifest.cssBundle ?? null;
|
|
43
|
+
const matched = matchClientRoute(manifest, pathname);
|
|
44
|
+
const routeHrefs = matched?.route?.cssBundles ?? [];
|
|
45
|
+
|
|
46
|
+
const globalLinks = Array.from(
|
|
47
|
+
document.querySelectorAll('link[rel="stylesheet"][data-arcway-css="global"]'),
|
|
48
|
+
);
|
|
49
|
+
for (const link of globalLinks) {
|
|
50
|
+
if (!globalHref) continue;
|
|
51
|
+
const current = link.getAttribute('href')?.split('?')[0] ?? '';
|
|
52
|
+
if (current !== globalHref) link.setAttribute('href', globalHref);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const existingRouteLinks = Array.from(
|
|
56
|
+
document.querySelectorAll('link[rel="stylesheet"][data-arcway-css="route"]'),
|
|
57
|
+
);
|
|
58
|
+
const head = document.head || document.querySelector('head');
|
|
59
|
+
if (!head) return;
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < routeHrefs.length; i++) {
|
|
62
|
+
const href = routeHrefs[i];
|
|
63
|
+
const existing = existingRouteLinks[i];
|
|
64
|
+
if (existing) {
|
|
65
|
+
const current = existing.getAttribute('href')?.split('?')[0] ?? '';
|
|
66
|
+
if (current !== href) existing.setAttribute('href', href);
|
|
67
|
+
existing.setAttribute('data-arcway-css-index', String(i));
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const link = document.createElement('link');
|
|
71
|
+
link.rel = 'stylesheet';
|
|
72
|
+
link.setAttribute('data-arcway-css', 'route');
|
|
73
|
+
link.setAttribute('data-arcway-css-index', String(i));
|
|
74
|
+
link.href = href;
|
|
75
|
+
head.appendChild(link);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (let i = routeHrefs.length; i < existingRouteLinks.length; i++) {
|
|
79
|
+
existingRouteLinks[i].remove();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
39
83
|
function matchClientRoute(manifest, pathname) {
|
|
40
84
|
for (const route of manifest.routes) {
|
|
41
85
|
let compiled = patternCache.get(route.pattern);
|
|
@@ -109,4 +153,5 @@ export {
|
|
|
109
153
|
matchClientRoute,
|
|
110
154
|
prefetchRoute,
|
|
111
155
|
readClientManifest,
|
|
156
|
+
syncStylesForPath,
|
|
112
157
|
};
|
package/client/router.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
matchClientRoute,
|
|
16
16
|
loadLoadingComponents,
|
|
17
17
|
prefetchRoute,
|
|
18
|
+
syncStylesForPath,
|
|
18
19
|
} from './page-loader.js';
|
|
19
20
|
import { parseQuery } from './query.js';
|
|
20
21
|
|
|
@@ -72,6 +73,7 @@ function useSearchParams() {
|
|
|
72
73
|
function Router({
|
|
73
74
|
initialPath,
|
|
74
75
|
initialParams,
|
|
76
|
+
initialPattern,
|
|
75
77
|
initialComponent,
|
|
76
78
|
initialLayouts,
|
|
77
79
|
initialLoadings,
|
|
@@ -81,6 +83,7 @@ function Router({
|
|
|
81
83
|
() => initialPath ?? (typeof window !== 'undefined' ? window.location.pathname : '/'),
|
|
82
84
|
);
|
|
83
85
|
const [pageState, setPageState] = useState({
|
|
86
|
+
pattern: initialPattern ?? null,
|
|
84
87
|
component: initialComponent ?? null,
|
|
85
88
|
layouts: initialLayouts ?? [],
|
|
86
89
|
loadings: initialLoadings ?? [],
|
|
@@ -96,12 +99,24 @@ function Router({
|
|
|
96
99
|
|
|
97
100
|
useEffect(() => {
|
|
98
101
|
manifestRef.current = readClientManifest();
|
|
102
|
+
if (manifestRef.current) {
|
|
103
|
+
syncStylesForPath(manifestRef.current, pathname);
|
|
104
|
+
}
|
|
99
105
|
const onManifestUpdate = () => {
|
|
100
106
|
manifestRef.current = readClientManifest();
|
|
107
|
+
if (manifestRef.current) {
|
|
108
|
+
syncStylesForPath(manifestRef.current, window.location.pathname);
|
|
109
|
+
}
|
|
101
110
|
};
|
|
102
111
|
window.addEventListener('manifest-update', onManifestUpdate);
|
|
103
112
|
return () => window.removeEventListener('manifest-update', onManifestUpdate);
|
|
104
|
-
}, []);
|
|
113
|
+
}, [pathname]);
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (manifestRef.current) {
|
|
117
|
+
syncStylesForPath(manifestRef.current, pathname);
|
|
118
|
+
}
|
|
119
|
+
}, [pathname]);
|
|
105
120
|
|
|
106
121
|
// `bumpQuery` controls whether `queryVersion` is incremented atomically
|
|
107
122
|
// with the page-state updates. It lives inside the same `startTransition`
|
|
@@ -116,6 +131,7 @@ function Router({
|
|
|
116
131
|
startTransition(() => {
|
|
117
132
|
setPathname(newPath);
|
|
118
133
|
setPageState({
|
|
134
|
+
pattern: loaded.pattern ?? null,
|
|
119
135
|
component: loaded.component,
|
|
120
136
|
layouts: loaded.layouts,
|
|
121
137
|
loadings: loaded.loadings,
|
|
@@ -177,6 +193,7 @@ function Router({
|
|
|
177
193
|
setPathname(pathOnly);
|
|
178
194
|
setPageState((prev) => ({
|
|
179
195
|
...prev,
|
|
196
|
+
pattern: matched.route.pattern,
|
|
180
197
|
loadings: targetLoadings,
|
|
181
198
|
params: matched.params,
|
|
182
199
|
}));
|
|
@@ -217,7 +234,7 @@ function Router({
|
|
|
217
234
|
|
|
218
235
|
if (!manifest) {
|
|
219
236
|
setPathname(newPath);
|
|
220
|
-
setPageState((prev) => ({ ...prev, params: {} }));
|
|
237
|
+
setPageState((prev) => ({ ...prev, pattern: null, params: {} }));
|
|
221
238
|
setQueryVersion((v) => v + 1);
|
|
222
239
|
return;
|
|
223
240
|
}
|
|
@@ -238,6 +255,12 @@ function Router({
|
|
|
238
255
|
return () => window.removeEventListener('popstate', onPopState);
|
|
239
256
|
}, [pathname, applyLoaded]);
|
|
240
257
|
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
if (typeof window !== 'undefined') {
|
|
260
|
+
window.__arcway_current_pattern__ = pageState.pattern ?? null;
|
|
261
|
+
}
|
|
262
|
+
}, [pageState.pattern]);
|
|
263
|
+
|
|
241
264
|
const { component: PageComponent, layouts, loadings, params } = pageState;
|
|
242
265
|
|
|
243
266
|
let content;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcway",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"description": "A convention-based framework for building modular monoliths with strict domain boundaries.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -47,8 +47,10 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@aws-sdk/client-s3": "^3.987.0",
|
|
49
49
|
"@babel/core": "^7.29.0",
|
|
50
|
+
"@vitejs/plugin-react": "^5.1.0",
|
|
50
51
|
"@base-ui/react": "^1.2.0",
|
|
51
52
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
53
|
+
"@tailwindcss/vite": "^4.1.18",
|
|
52
54
|
"@tailwindcss/postcss": "^4.1.18",
|
|
53
55
|
"arktype": "^2.1.29",
|
|
54
56
|
"bcryptjs": "^3.0.3",
|
|
@@ -99,6 +101,7 @@
|
|
|
99
101
|
"tailwind-merge": "^3.4.1",
|
|
100
102
|
"tailwindcss": "^4.1.18",
|
|
101
103
|
"vaul": "^1.1.2",
|
|
104
|
+
"vite": "^7.1.12",
|
|
102
105
|
"ws": "^8.19.0",
|
|
103
106
|
"zod": "^3.24.0"
|
|
104
107
|
},
|
package/server/boot/index.js
CHANGED
|
@@ -14,6 +14,8 @@ import { StaticRouter } from '../static/index.js';
|
|
|
14
14
|
import { WsRouter } from '../ws/ws-router.js';
|
|
15
15
|
import { createWsBackplane } from '../ws/backplane.js';
|
|
16
16
|
import { runHook } from './hooks.js';
|
|
17
|
+
import { createViteDevRouter } from '../pages/vite-dev.js';
|
|
18
|
+
import { resolvePagesOutDir } from '../pages/out-dir.js';
|
|
17
19
|
import path from 'node:path';
|
|
18
20
|
|
|
19
21
|
async function boot(options) {
|
|
@@ -85,6 +87,11 @@ async function boot(options) {
|
|
|
85
87
|
|
|
86
88
|
const pagesRouter = new PagesRouter(config, { rootDir, log, mode, fileWatcher, appContext });
|
|
87
89
|
await pagesRouter.init();
|
|
90
|
+
const pagesOutDir = resolvePagesOutDir(config, rootDir, mode);
|
|
91
|
+
const viteRouter =
|
|
92
|
+
mode === 'development' && config.pages?.vite?.enabled === true
|
|
93
|
+
? await createViteDevRouter({ rootDir, log, config })
|
|
94
|
+
: null;
|
|
88
95
|
|
|
89
96
|
const healthDeps = {
|
|
90
97
|
db,
|
|
@@ -93,7 +100,7 @@ async function boot(options) {
|
|
|
93
100
|
const systemRouter = new SystemRouter({ healthDeps });
|
|
94
101
|
|
|
95
102
|
const staticRouter = new StaticRouter({
|
|
96
|
-
outDir:
|
|
103
|
+
outDir: pagesOutDir,
|
|
97
104
|
publicDir: path.join(rootDir, 'public'),
|
|
98
105
|
});
|
|
99
106
|
|
|
@@ -113,6 +120,7 @@ async function boot(options) {
|
|
|
113
120
|
webServer.use(systemRouter, '/_system');
|
|
114
121
|
webServer.use(apiRouter, apiRouter.prefix);
|
|
115
122
|
webServer.use(wsRouter, config.websocket.path);
|
|
123
|
+
if (viteRouter) webServer.use(viteRouter);
|
|
116
124
|
webServer.use(staticRouter);
|
|
117
125
|
webServer.use(pagesRouter);
|
|
118
126
|
|
|
@@ -130,6 +138,7 @@ async function boot(options) {
|
|
|
130
138
|
if (workerPool) await workerPool.shutdown();
|
|
131
139
|
await pagesRouter.close();
|
|
132
140
|
await apiRouter.close();
|
|
141
|
+
if (viteRouter) await viteRouter.close();
|
|
133
142
|
await webServer.close();
|
|
134
143
|
if (fileWatcher) await fileWatcher.close();
|
|
135
144
|
await destroyInfrastructure(infrastructure);
|
package/server/build.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { makeConfig } from './config/loader.js';
|
|
2
2
|
import { buildPages } from './pages/build.js';
|
|
3
|
+
import { resolvePagesOutDir } from './pages/out-dir.js';
|
|
3
4
|
|
|
4
5
|
async function build(options) {
|
|
5
6
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
@@ -11,6 +12,7 @@ async function build(options) {
|
|
|
11
12
|
if (pagesEnabled) {
|
|
12
13
|
pages = await buildPages({
|
|
13
14
|
rootDir,
|
|
15
|
+
outDir: resolvePagesOutDir(config, rootDir, 'production'),
|
|
14
16
|
serverTarget: buildTarget,
|
|
15
17
|
minify: true,
|
|
16
18
|
fonts: config?.pages?.fonts,
|
|
@@ -4,16 +4,31 @@ const DEFAULTS = {
|
|
|
4
4
|
enabled: true,
|
|
5
5
|
dir: 'pages',
|
|
6
6
|
outDir: '.build/pages',
|
|
7
|
+
devOutDir: '.build-dev/pages',
|
|
8
|
+
vite: {
|
|
9
|
+
enabled: false,
|
|
10
|
+
},
|
|
7
11
|
};
|
|
8
12
|
|
|
9
13
|
function resolve(config, { rootDir } = {}) {
|
|
10
|
-
const
|
|
14
|
+
const rawPages = config.pages ?? {};
|
|
15
|
+
const pages = {
|
|
16
|
+
...DEFAULTS,
|
|
17
|
+
...rawPages,
|
|
18
|
+
vite: {
|
|
19
|
+
...DEFAULTS.vite,
|
|
20
|
+
...(rawPages.vite ?? {}),
|
|
21
|
+
},
|
|
22
|
+
};
|
|
11
23
|
if (pages.dir && !path.isAbsolute(pages.dir)) {
|
|
12
24
|
pages.dir = path.resolve(rootDir, pages.dir);
|
|
13
25
|
}
|
|
14
26
|
if (pages.outDir && !path.isAbsolute(pages.outDir)) {
|
|
15
27
|
pages.outDir = path.resolve(rootDir, pages.outDir);
|
|
16
28
|
}
|
|
29
|
+
if (pages.devOutDir && !path.isAbsolute(pages.devOutDir)) {
|
|
30
|
+
pages.devOutDir = path.resolve(rootDir, pages.devOutDir);
|
|
31
|
+
}
|
|
17
32
|
return { ...config, pages };
|
|
18
33
|
}
|
|
19
34
|
|
|
@@ -263,7 +263,7 @@ async function restoreMultiFileCache({ cacheDir, outputs, destDir }) {
|
|
|
263
263
|
const src = path.join(cacheDir, rel);
|
|
264
264
|
const dst = path.join(destDir, rel);
|
|
265
265
|
await fs.mkdir(path.dirname(dst), { recursive: true });
|
|
266
|
-
await
|
|
266
|
+
await cpAtomic(src, dst);
|
|
267
267
|
}),
|
|
268
268
|
);
|
|
269
269
|
}
|
|
@@ -35,7 +35,7 @@ function planHydrationEntries(pages, layouts, loadings, tempDir) {
|
|
|
35
35
|
const outName = patternToFileName(page.pattern);
|
|
36
36
|
const layoutChain = resolveLayoutChain(page.pattern, layouts);
|
|
37
37
|
const loadingChain = resolveLoadingChain(page.pattern, loadings);
|
|
38
|
-
const content = generateHydrationEntry(page.filePath, layoutChain, loadingChain);
|
|
38
|
+
const content = generateHydrationEntry(page.filePath, layoutChain, loadingChain, page.pattern);
|
|
39
39
|
const entryPath = path.join(tempDir, `${outName}.tsx`);
|
|
40
40
|
files.push({ entryPath, content });
|
|
41
41
|
entryPoints[outName] = entryPath;
|
|
@@ -284,6 +284,7 @@ async function collectStaleOutputs(metafile, clientDir) {
|
|
|
284
284
|
for (const entry of entries) {
|
|
285
285
|
if (!entry.isFile()) continue;
|
|
286
286
|
const abs = path.join(dir, entry.name);
|
|
287
|
+
if (shouldPreserveClientAsset(abs, clientDir)) continue;
|
|
287
288
|
if (!currentOutputs.has(abs)) stale.add(abs);
|
|
288
289
|
}
|
|
289
290
|
}
|
|
@@ -295,6 +296,14 @@ async function collectStaleOutputs(metafile, clientDir) {
|
|
|
295
296
|
return stale;
|
|
296
297
|
}
|
|
297
298
|
|
|
299
|
+
function shouldPreserveClientAsset(absPath, clientDir) {
|
|
300
|
+
const rel = path.relative(clientDir, absPath).replace(/\\/g, '/');
|
|
301
|
+
if (!rel || rel.startsWith('..')) return false;
|
|
302
|
+
if (rel.startsWith('hmr/')) return true;
|
|
303
|
+
if (/^styles-[a-f0-9]+\.css$/i.test(rel)) return true;
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
|
|
298
307
|
async function tryRestoreClientFromCache({ rootDir, clientDir, coarseKey }) {
|
|
299
308
|
const lookup = await lookupMultiFileCache({ rootDir, kind: 'client', coarseKey });
|
|
300
309
|
if (!lookup.hit) return { hit: false, bucket: lookup.bucket };
|
|
@@ -459,7 +468,7 @@ function extractMetadata(result, plan, outDir) {
|
|
|
459
468
|
return { bundles, navBundles, layoutNavBundles, loadingNavBundles, entryChunks, entryCssChunks };
|
|
460
469
|
}
|
|
461
470
|
|
|
462
|
-
function generateHydrationEntry(componentPath, layouts = [], loadings = []) {
|
|
471
|
+
function generateHydrationEntry(componentPath, layouts = [], loadings = [], pattern = '/') {
|
|
463
472
|
const importPath = componentPath.replace(/\\/g, '/');
|
|
464
473
|
const imports = [
|
|
465
474
|
`import { hydrateRoot } from 'react-dom/client';`,
|
|
@@ -485,8 +494,19 @@ function generateHydrationEntry(componentPath, layouts = [], loadings = []) {
|
|
|
485
494
|
const container = document.getElementById('__app');
|
|
486
495
|
const propsEl = document.getElementById('__app_props');
|
|
487
496
|
const props = propsEl ? JSON.parse(propsEl.textContent || '{}') : {};
|
|
497
|
+
const ROOT_KEY = '__arcway_root__';
|
|
498
|
+
const rootOwner = window;
|
|
499
|
+
const element = <ApiProvider><Router initialPath={window.location.pathname} initialParams={props} initialPattern={${JSON.stringify(pattern)}} initialComponent={Component} initialLayouts={${layoutsArray}} initialLoadings={${loadingsArray}} /></ApiProvider>;
|
|
488
500
|
|
|
489
|
-
|
|
501
|
+
if (!container) {
|
|
502
|
+
throw new Error('Arcway hydrate entry could not find #__app');
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (!rootOwner[ROOT_KEY]) {
|
|
506
|
+
rootOwner[ROOT_KEY] = hydrateRoot(container, element);
|
|
507
|
+
} else {
|
|
508
|
+
rootOwner[ROOT_KEY].render(element);
|
|
509
|
+
}
|
|
490
510
|
`;
|
|
491
511
|
}
|
|
492
512
|
export { buildClientBundles, createClientBuildContext, generateHydrationEntry };
|
package/server/pages/handler.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from './ssr.js';
|
|
16
16
|
import { MIME_TYPES } from './static.js';
|
|
17
17
|
import { createArcwayDevEndpoint } from './arcway-endpoint.js';
|
|
18
|
+
import { buildViteClientManifestJson, buildViteRoute } from './vite-dev.js';
|
|
18
19
|
function createPagesHandler(options) {
|
|
19
20
|
const rootDir = options.rootDir;
|
|
20
21
|
const outDir = path.resolve(rootDir, options.outDir ?? '.build/pages');
|
|
@@ -23,6 +24,7 @@ function createPagesHandler(options) {
|
|
|
23
24
|
const appContext = options.appContext ?? null;
|
|
24
25
|
const mode = options.mode ?? 'production';
|
|
25
26
|
const devMode = mode === 'development';
|
|
27
|
+
const viteDev = options.viteDev === true;
|
|
26
28
|
const lazyContext = options.lazyContext ?? null;
|
|
27
29
|
// Dev with a lazy context drives the handler off an in-memory manifest; no
|
|
28
30
|
// disk `pages-manifest.json` is produced. Prod still reads from disk.
|
|
@@ -39,8 +41,12 @@ function createPagesHandler(options) {
|
|
|
39
41
|
let manifest = lazyContext
|
|
40
42
|
? lazyContext.manifest
|
|
41
43
|
: JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
42
|
-
let routes = compileRoutes(manifest);
|
|
43
|
-
let clientManifestJson = buildClientManifestJson(manifest
|
|
44
|
+
let routes = compileRoutes(manifest, { rootDir, outDir, viteDev });
|
|
45
|
+
let clientManifestJson = buildClientManifestJson(manifest, {
|
|
46
|
+
rootDir,
|
|
47
|
+
outDir,
|
|
48
|
+
viteDev,
|
|
49
|
+
});
|
|
44
50
|
let lastSeenVersion = lazyContext ? manifest.version : 0;
|
|
45
51
|
const componentCache = new Map();
|
|
46
52
|
let cacheVersion = 0;
|
|
@@ -60,8 +66,12 @@ function createPagesHandler(options) {
|
|
|
60
66
|
if (!lazyContext) return;
|
|
61
67
|
if (manifest.version === lastSeenVersion) return;
|
|
62
68
|
lastSeenVersion = manifest.version;
|
|
63
|
-
routes = compileRoutes(manifest);
|
|
64
|
-
clientManifestJson = buildClientManifestJson(manifest
|
|
69
|
+
routes = compileRoutes(manifest, { rootDir, outDir, viteDev });
|
|
70
|
+
clientManifestJson = buildClientManifestJson(manifest, {
|
|
71
|
+
rootDir,
|
|
72
|
+
outDir,
|
|
73
|
+
viteDev,
|
|
74
|
+
});
|
|
65
75
|
componentCache.clear();
|
|
66
76
|
cacheVersion++;
|
|
67
77
|
}
|
|
@@ -79,8 +89,12 @@ function createPagesHandler(options) {
|
|
|
79
89
|
}
|
|
80
90
|
if (!fs.existsSync(manifestPath)) return;
|
|
81
91
|
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
82
|
-
routes = compileRoutes(manifest);
|
|
83
|
-
clientManifestJson = buildClientManifestJson(manifest
|
|
92
|
+
routes = compileRoutes(manifest, { rootDir, outDir, viteDev });
|
|
93
|
+
clientManifestJson = buildClientManifestJson(manifest, {
|
|
94
|
+
rootDir,
|
|
95
|
+
outDir,
|
|
96
|
+
viteDev,
|
|
97
|
+
});
|
|
84
98
|
componentCache.clear();
|
|
85
99
|
cacheVersion++;
|
|
86
100
|
}
|
|
@@ -104,7 +118,11 @@ function createPagesHandler(options) {
|
|
|
104
118
|
});
|
|
105
119
|
res.write('data: connected\n\n');
|
|
106
120
|
const onUpdate = (event) => {
|
|
107
|
-
|
|
121
|
+
let payload = event;
|
|
122
|
+
if (event?.type === 'hmr-update' || event?.type === 'css-update') {
|
|
123
|
+
payload = { ...event, manifest: clientManifestJson };
|
|
124
|
+
}
|
|
125
|
+
res.write(`data: ${JSON.stringify(payload)}
|
|
108
126
|
|
|
109
127
|
`);
|
|
110
128
|
};
|
|
@@ -201,6 +219,7 @@ function createPagesHandler(options) {
|
|
|
201
219
|
cacheVersion,
|
|
202
220
|
projectReact,
|
|
203
221
|
devMode,
|
|
222
|
+
viteDev,
|
|
204
223
|
);
|
|
205
224
|
} catch (err) {
|
|
206
225
|
if (manifest.errorBundle && !res.headersSent) {
|
|
@@ -275,10 +294,10 @@ async function renderDevBuildError(
|
|
|
275
294
|
res.end(`<h1>500 - Build Error</h1><pre>${escaped}</pre>`);
|
|
276
295
|
}
|
|
277
296
|
}
|
|
278
|
-
function compileRoutes(manifest) {
|
|
297
|
+
function compileRoutes(manifest, { rootDir, outDir, viteDev } = {}) {
|
|
279
298
|
const routes = manifest.entries.map((entry) => {
|
|
280
299
|
const { regex, paramNames, catchAllParam } = compilePattern(entry.pattern);
|
|
281
|
-
|
|
300
|
+
let route = {
|
|
282
301
|
pattern: entry.pattern,
|
|
283
302
|
regex,
|
|
284
303
|
paramNames,
|
|
@@ -290,6 +309,10 @@ function compileRoutes(manifest) {
|
|
|
290
309
|
sharedChunks: entry.sharedChunks ?? [],
|
|
291
310
|
sharedCssChunks: entry.sharedCssChunks ?? [],
|
|
292
311
|
};
|
|
312
|
+
if (viteDev) {
|
|
313
|
+
route = buildViteRoute(route, { rootDir, outDir });
|
|
314
|
+
}
|
|
315
|
+
return route;
|
|
293
316
|
});
|
|
294
317
|
sortBySpecificity(routes);
|
|
295
318
|
return routes;
|
|
@@ -350,8 +373,12 @@ async function runPageMiddleware(
|
|
|
350
373
|
}
|
|
351
374
|
return null;
|
|
352
375
|
}
|
|
353
|
-
function buildClientManifestJson(manifest) {
|
|
376
|
+
function buildClientManifestJson(manifest, { rootDir, outDir, viteDev } = {}) {
|
|
377
|
+
if (viteDev) {
|
|
378
|
+
return buildViteClientManifestJson(manifest, rootDir);
|
|
379
|
+
}
|
|
354
380
|
const clientManifest = {
|
|
381
|
+
cssBundle: manifest.cssBundle ? `/static/${manifest.cssBundle}` : null,
|
|
355
382
|
routes: manifest.entries.map((entry) => {
|
|
356
383
|
const route = {
|
|
357
384
|
pattern: entry.pattern,
|
|
@@ -359,6 +386,7 @@ function buildClientManifestJson(manifest) {
|
|
|
359
386
|
clientBundle: `/static/${entry.navBundle}`,
|
|
360
387
|
layoutBundles: (entry.layoutClientBundles ?? []).map((b) => `/static/${b}`),
|
|
361
388
|
loadingBundles: (entry.loadingClientBundles ?? []).map((b) => `/static/${b}`),
|
|
389
|
+
cssBundles: (entry.sharedCssChunks ?? []).map((b) => `/static/${b}`),
|
|
362
390
|
};
|
|
363
391
|
if (entry.catchAllParam) {
|
|
364
392
|
route.catchAllParam = entry.catchAllParam;
|
package/server/pages/hmr.js
CHANGED
|
@@ -79,38 +79,65 @@ window.$RefreshReg$ = function() {};
|
|
|
79
79
|
window.$RefreshSig$ = function() { return function(type) { return type; }; };
|
|
80
80
|
window.__REACT_REFRESH__ = RefreshRuntime;
|
|
81
81
|
|
|
82
|
-
var es =
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
var el = document.getElementById('__app_manifest');
|
|
97
|
-
if (el) el.textContent = msg.manifest;
|
|
98
|
-
window.dispatchEvent(new CustomEvent('manifest-update'));
|
|
99
|
-
}
|
|
100
|
-
}).catch(function(err) {
|
|
101
|
-
console.warn('[HMR] Fast Refresh failed, doing full reload', err);
|
|
102
|
-
window.location.reload();
|
|
103
|
-
});
|
|
104
|
-
} else if (msg.type === 'css-update') {
|
|
105
|
-
var links = document.querySelectorAll('link[rel="stylesheet"][href*="/static/"]');
|
|
106
|
-
links.forEach(function(link) {
|
|
107
|
-
link.href = '/static/' + msg.cssBundle + '?t=' + msg.timestamp;
|
|
108
|
-
});
|
|
82
|
+
var es = null;
|
|
83
|
+
var reconnectTimer = null;
|
|
84
|
+
|
|
85
|
+
function applyManifest(manifestJson) {
|
|
86
|
+
if (!manifestJson) return;
|
|
87
|
+
var el = document.getElementById('__app_manifest');
|
|
88
|
+
if (el) el.textContent = manifestJson;
|
|
89
|
+
window.dispatchEvent(new CustomEvent('manifest-update'));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function pickCurrentHmrModules(msg) {
|
|
93
|
+
var currentPattern = window.__arcway_current_pattern__;
|
|
94
|
+
if (currentPattern && msg.routeBundles && msg.routeBundles[currentPattern]) {
|
|
95
|
+
return ['/static/' + msg.routeBundles[currentPattern] + '?t=' + msg.timestamp];
|
|
109
96
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
97
|
+
return (msg.modules || []).map(function(m) {
|
|
98
|
+
return '/static/' + m + '?t=' + msg.timestamp;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function connect() {
|
|
103
|
+
if (es) es.close();
|
|
104
|
+
es = new EventSource('/__livereload');
|
|
105
|
+
es.onmessage = function(e) {
|
|
106
|
+
if (e.data === 'connected') return;
|
|
107
|
+
var msg;
|
|
108
|
+
try { msg = JSON.parse(e.data); } catch { window.location.reload(); return; }
|
|
109
|
+
if (msg.type === 'reload') {
|
|
110
|
+
window.location.reload();
|
|
111
|
+
} else if (msg.type === 'hmr-update') {
|
|
112
|
+
var imports = pickCurrentHmrModules(msg).map(function(specifier) {
|
|
113
|
+
return import(specifier);
|
|
114
|
+
});
|
|
115
|
+
Promise.all(imports).then(function() {
|
|
116
|
+
RefreshRuntime.performReactRefresh();
|
|
117
|
+
applyManifest(msg.manifest);
|
|
118
|
+
}).catch(function(err) {
|
|
119
|
+
console.warn('[HMR] Fast Refresh failed, doing full reload', err);
|
|
120
|
+
window.location.reload();
|
|
121
|
+
});
|
|
122
|
+
} else if (msg.type === 'css-update') {
|
|
123
|
+
var links = document.querySelectorAll('link[rel="stylesheet"][data-arcway-css="global"]');
|
|
124
|
+
links.forEach(function(link) {
|
|
125
|
+
link.href = '/static/' + msg.cssBundle + '?t=' + msg.timestamp;
|
|
126
|
+
});
|
|
127
|
+
applyManifest(msg.manifest);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
es.onerror = function() {
|
|
131
|
+
if (reconnectTimer) return;
|
|
132
|
+
try { es.close(); } catch {}
|
|
133
|
+
reconnectTimer = setTimeout(function() {
|
|
134
|
+
reconnectTimer = null;
|
|
135
|
+
connect();
|
|
136
|
+
}, 1000);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
connect();
|
|
114
141
|
</script>`;
|
|
115
142
|
}
|
|
116
143
|
function diffClientMetafiles(oldMeta, newMeta, outDir) {
|