arcway 0.1.12 → 0.1.13
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/provider.js +32 -7
- package/client/router.js +46 -7
- package/package.json +4 -1
- package/server/pages/build.js +103 -56
- package/server/pages/hmr.js +19 -13
- package/server/pages/ssr.js +3 -2
- package/server/pages/watcher.js +4 -3
package/client/provider.js
CHANGED
|
@@ -3,7 +3,7 @@ import { WsManager } from './ws.js';
|
|
|
3
3
|
|
|
4
4
|
const SOLO_CTX_KEY = '__provider_context__';
|
|
5
5
|
const WS_CTX_KEY = '__ws_context__';
|
|
6
|
-
const DEFAULT_CONFIG = { pathPrefix: '' };
|
|
6
|
+
const DEFAULT_CONFIG = { pathPrefix: '/api' };
|
|
7
7
|
|
|
8
8
|
const ApiContext = (globalThis[SOLO_CTX_KEY] ??= createContext(DEFAULT_CONFIG));
|
|
9
9
|
const WsContext = (globalThis[WS_CTX_KEY] ??= createContext(null));
|
|
@@ -18,8 +18,25 @@ function useWsManager() {
|
|
|
18
18
|
|
|
19
19
|
const useSoloContext = useApiContext;
|
|
20
20
|
|
|
21
|
-
function
|
|
22
|
-
const
|
|
21
|
+
function toWsProtocol(loc, wsPath) {
|
|
22
|
+
const proto = loc.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
23
|
+
return `${proto}//${loc.host}${wsPath}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveWsUrl(input) {
|
|
27
|
+
if (typeof window === 'undefined') return null;
|
|
28
|
+
if (!input) return toWsProtocol(window.location, '/ws');
|
|
29
|
+
if (/^wss?:\/\//.test(input)) return input;
|
|
30
|
+
if (input.startsWith('/')) return toWsProtocol(window.location, input);
|
|
31
|
+
return input;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ApiProvider({ children, pathPrefix = '/api', headers, wsUrl }) {
|
|
35
|
+
const resolvedWsUrl = useMemo(() => resolveWsUrl(wsUrl), [wsUrl]);
|
|
36
|
+
const config = useMemo(
|
|
37
|
+
() => ({ pathPrefix, headers, wsUrl: resolvedWsUrl }),
|
|
38
|
+
[pathPrefix, headers, resolvedWsUrl],
|
|
39
|
+
);
|
|
23
40
|
|
|
24
41
|
const wsManagerRef = useRef(null);
|
|
25
42
|
const wsManager = useMemo(() => {
|
|
@@ -27,11 +44,11 @@ function ApiProvider({ children, pathPrefix = '', headers, wsUrl }) {
|
|
|
27
44
|
wsManagerRef.current.disconnect();
|
|
28
45
|
wsManagerRef.current = null;
|
|
29
46
|
}
|
|
30
|
-
if (!
|
|
31
|
-
const manager = new WsManager({ url:
|
|
47
|
+
if (!resolvedWsUrl) return null;
|
|
48
|
+
const manager = new WsManager({ url: resolvedWsUrl });
|
|
32
49
|
wsManagerRef.current = manager;
|
|
33
50
|
return manager;
|
|
34
|
-
}, [
|
|
51
|
+
}, [resolvedWsUrl]);
|
|
35
52
|
|
|
36
53
|
useEffect(() => {
|
|
37
54
|
if (wsManager) {
|
|
@@ -50,4 +67,12 @@ function ApiProvider({ children, pathPrefix = '', headers, wsUrl }) {
|
|
|
50
67
|
const Provider = ApiProvider;
|
|
51
68
|
const SoloProvider = ApiProvider;
|
|
52
69
|
|
|
53
|
-
export {
|
|
70
|
+
export {
|
|
71
|
+
ApiProvider,
|
|
72
|
+
Provider,
|
|
73
|
+
SoloProvider,
|
|
74
|
+
resolveWsUrl,
|
|
75
|
+
useApiContext,
|
|
76
|
+
useSoloContext,
|
|
77
|
+
useWsManager,
|
|
78
|
+
};
|
package/client/router.js
CHANGED
|
@@ -51,7 +51,9 @@ function useRouter() {
|
|
|
51
51
|
if (typeof window !== 'undefined') window.location.reload();
|
|
52
52
|
},
|
|
53
53
|
}),
|
|
54
|
-
|
|
54
|
+
// queryVersion bumps on every query-only navigation or same-path popstate,
|
|
55
|
+
// so memoised consumers (notably `useSearchParams`) re-read the URL.
|
|
56
|
+
[ctx.pathname, ctx.params, ctx.navigate, ctx.queryVersion],
|
|
55
57
|
);
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -86,6 +88,10 @@ function Router({
|
|
|
86
88
|
});
|
|
87
89
|
const [isNavigating, setIsNavigating] = useState(false);
|
|
88
90
|
const [isPending, startTransition] = useTransition();
|
|
91
|
+
// Bumped on query-only navigations (both push/replace and popstate) so that
|
|
92
|
+
// `useRouter`/`useSearchParams` consumers re-render even though pathname and
|
|
93
|
+
// params are unchanged.
|
|
94
|
+
const [queryVersion, setQueryVersion] = useState(0);
|
|
89
95
|
const manifestRef = useRef(null);
|
|
90
96
|
|
|
91
97
|
useEffect(() => {
|
|
@@ -114,7 +120,28 @@ function Router({
|
|
|
114
120
|
async (to, options) => {
|
|
115
121
|
const scroll = options?.scroll !== false;
|
|
116
122
|
const replace = options?.replace === true;
|
|
117
|
-
|
|
123
|
+
|
|
124
|
+
// `matchClientRoute` only knows how to match pathnames, so any query
|
|
125
|
+
// string has to be stripped before the lookup. Splitting also lets us
|
|
126
|
+
// special-case same-path/different-query navigations as a history-only
|
|
127
|
+
// update — no bundle loading, no scroll reset, no full reload.
|
|
128
|
+
const qIdx = to.indexOf('?');
|
|
129
|
+
const pathOnly = qIdx === -1 ? to : to.slice(0, qIdx);
|
|
130
|
+
const search = qIdx === -1 ? '' : to.slice(qIdx);
|
|
131
|
+
const currentSearch =
|
|
132
|
+
typeof window !== 'undefined' ? window.location.search : '';
|
|
133
|
+
|
|
134
|
+
if (pathOnly === pathname && search === currentSearch) return;
|
|
135
|
+
|
|
136
|
+
if (pathOnly === pathname) {
|
|
137
|
+
if (replace) {
|
|
138
|
+
window.history.replaceState(null, '', to);
|
|
139
|
+
} else {
|
|
140
|
+
window.history.pushState(null, '', to);
|
|
141
|
+
}
|
|
142
|
+
setQueryVersion((v) => v + 1);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
118
145
|
|
|
119
146
|
const manifest = manifestRef.current;
|
|
120
147
|
if (!manifest) {
|
|
@@ -122,7 +149,7 @@ function Router({
|
|
|
122
149
|
return;
|
|
123
150
|
}
|
|
124
151
|
|
|
125
|
-
const matched = matchClientRoute(manifest,
|
|
152
|
+
const matched = matchClientRoute(manifest, pathOnly);
|
|
126
153
|
if (!matched) {
|
|
127
154
|
window.location.href = to;
|
|
128
155
|
return;
|
|
@@ -137,7 +164,7 @@ function Router({
|
|
|
137
164
|
|
|
138
165
|
const targetLoadings = await loadLoadingComponents(matched.route);
|
|
139
166
|
if (targetLoadings.length > 0) {
|
|
140
|
-
setPathname(
|
|
167
|
+
setPathname(pathOnly);
|
|
141
168
|
setPageState((prev) => ({
|
|
142
169
|
...prev,
|
|
143
170
|
loadings: targetLoadings,
|
|
@@ -146,13 +173,14 @@ function Router({
|
|
|
146
173
|
}
|
|
147
174
|
|
|
148
175
|
try {
|
|
149
|
-
const loaded = await loadPage(manifest,
|
|
176
|
+
const loaded = await loadPage(manifest, pathOnly);
|
|
150
177
|
if (!loaded) {
|
|
151
178
|
window.location.href = to;
|
|
152
179
|
return;
|
|
153
180
|
}
|
|
154
181
|
|
|
155
|
-
applyLoaded(loaded,
|
|
182
|
+
applyLoaded(loaded, pathOnly);
|
|
183
|
+
if (search !== currentSearch) setQueryVersion((v) => v + 1);
|
|
156
184
|
|
|
157
185
|
if (scroll) {
|
|
158
186
|
window.scrollTo(0, 0);
|
|
@@ -168,11 +196,20 @@ function Router({
|
|
|
168
196
|
useEffect(() => {
|
|
169
197
|
async function onPopState() {
|
|
170
198
|
const newPath = window.location.pathname;
|
|
199
|
+
|
|
200
|
+
// Same-path back/forward is a query-only history step (or identical URL);
|
|
201
|
+
// skip the page load and just force re-render of search-param readers.
|
|
202
|
+
if (newPath === pathname) {
|
|
203
|
+
setQueryVersion((v) => v + 1);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
171
207
|
const manifest = manifestRef.current;
|
|
172
208
|
|
|
173
209
|
if (!manifest) {
|
|
174
210
|
setPathname(newPath);
|
|
175
211
|
setPageState((prev) => ({ ...prev, params: {} }));
|
|
212
|
+
setQueryVersion((v) => v + 1);
|
|
176
213
|
return;
|
|
177
214
|
}
|
|
178
215
|
|
|
@@ -180,6 +217,7 @@ function Router({
|
|
|
180
217
|
const loaded = await loadPage(manifest, newPath);
|
|
181
218
|
if (loaded) {
|
|
182
219
|
applyLoaded(loaded, newPath);
|
|
220
|
+
setQueryVersion((v) => v + 1);
|
|
183
221
|
} else {
|
|
184
222
|
window.location.reload();
|
|
185
223
|
}
|
|
@@ -190,7 +228,7 @@ function Router({
|
|
|
190
228
|
|
|
191
229
|
window.addEventListener('popstate', onPopState);
|
|
192
230
|
return () => window.removeEventListener('popstate', onPopState);
|
|
193
|
-
}, [applyLoaded]);
|
|
231
|
+
}, [pathname, applyLoaded]);
|
|
194
232
|
|
|
195
233
|
const { component: PageComponent, layouts, loadings, params } = pageState;
|
|
196
234
|
|
|
@@ -228,6 +266,7 @@ function Router({
|
|
|
228
266
|
pathname,
|
|
229
267
|
params,
|
|
230
268
|
navigate: navigateToPage,
|
|
269
|
+
queryVersion,
|
|
231
270
|
},
|
|
232
271
|
},
|
|
233
272
|
content,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcway",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "A convention-based framework for building modular monoliths with strict domain boundaries.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"test": "vitest run --project=unit",
|
|
33
|
+
"test:watch": "VITEST_MAX_WORKERS=2 vitest --project=unit",
|
|
34
|
+
"test:serial": "VITEST_MAX_WORKERS=1 vitest run --project=unit",
|
|
35
|
+
"test:coverage": "vitest run --project=unit --coverage",
|
|
33
36
|
"test:storybook": "TMPDIR=~/tmp PLAYWRIGHT_BROWSERS_PATH=~/.cache/playwright vitest run --project=storybook",
|
|
34
37
|
"test:all": "TMPDIR=~/tmp PLAYWRIGHT_BROWSERS_PATH=~/.cache/playwright vitest run",
|
|
35
38
|
"format": "prettier --write 'server/**/*.js' 'client/**/*.js' 'tests/**/*.js'",
|
package/server/pages/build.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
3
4
|
import {
|
|
4
5
|
discoverPages,
|
|
5
6
|
discoverLayouts,
|
|
@@ -38,65 +39,111 @@ async function buildPages(options) {
|
|
|
38
39
|
if (pages.length === 0) {
|
|
39
40
|
return { pageCount: 0, outDir, manifest: { entries: [], sharedChunks: [] } };
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
42
|
+
// Build into a uniquely-suffixed staging directory and atomically swap into place
|
|
43
|
+
// only after every step succeeds. If the build fails partway through, the previous
|
|
44
|
+
// `outDir` is left intact so the server keeps serving the old pages instead of
|
|
45
|
+
// falling back to 404s. The random suffix keeps concurrent callers against the same
|
|
46
|
+
// `outDir` (e.g. parallel vitest workers) from racing on the same staging path.
|
|
47
|
+
const buildDir = outDir + '-next-' + crypto.randomBytes(6).toString('hex');
|
|
48
|
+
await fs.mkdir(path.join(buildDir, 'server'), { recursive: true });
|
|
49
|
+
await fs.mkdir(path.join(buildDir, 'client'), { recursive: true });
|
|
50
|
+
const builds = [];
|
|
51
|
+
try {
|
|
52
|
+
let fontFaceCss;
|
|
53
|
+
let fontPreloadHtml;
|
|
54
|
+
if (options.fonts && options.fonts.length > 0) {
|
|
55
|
+
const publicDir = path.join(rootDir, 'public');
|
|
56
|
+
const fontResult = await resolveFonts(options.fonts, publicDir);
|
|
57
|
+
if (fontResult.fontFaceCss) fontFaceCss = fontResult.fontFaceCss;
|
|
58
|
+
if (fontResult.preloadHtml) fontPreloadHtml = fontResult.preloadHtml;
|
|
59
|
+
}
|
|
60
|
+
builds.push(
|
|
61
|
+
buildServerBundles(pages, buildDir, serverTarget),
|
|
62
|
+
buildLayoutServerBundles(layouts, buildDir, serverTarget),
|
|
63
|
+
buildMiddlewareServerBundles(middlewares, buildDir, serverTarget),
|
|
64
|
+
buildClientBundles(
|
|
65
|
+
pages,
|
|
66
|
+
layouts,
|
|
67
|
+
buildDir,
|
|
68
|
+
clientTarget,
|
|
69
|
+
minify,
|
|
70
|
+
rootDir,
|
|
71
|
+
loadings,
|
|
72
|
+
minify ? 'production' : 'development',
|
|
73
|
+
devMode,
|
|
74
|
+
),
|
|
75
|
+
buildErrorPageBundles(errorPages, buildDir, serverTarget),
|
|
76
|
+
buildCssBundle(pagesDir, buildDir, minify, stylesPath, fontFaceCss),
|
|
77
|
+
);
|
|
78
|
+
if (devMode) builds.push(buildHmrRuntimeBundle(buildDir));
|
|
79
|
+
// Fail fast: `Promise.all` rejects the moment any build throws, so a broken
|
|
80
|
+
// page produces a prompt error instead of waiting for slower sibling builds
|
|
81
|
+
// to finish. Cleanup of the staging dir is scheduled in the catch block.
|
|
82
|
+
const [
|
|
83
|
+
serverResult,
|
|
84
|
+
layoutServerResult,
|
|
85
|
+
middlewareServerResult,
|
|
86
|
+
clientResult,
|
|
87
|
+
errorBundles,
|
|
88
|
+
cssBundle,
|
|
89
|
+
] = await Promise.all(builds);
|
|
90
|
+
const manifest = generateManifest(
|
|
57
91
|
pages,
|
|
58
92
|
layouts,
|
|
59
|
-
outDir,
|
|
60
|
-
clientTarget,
|
|
61
|
-
minify,
|
|
62
|
-
rootDir,
|
|
63
93
|
loadings,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
middlewares,
|
|
95
|
+
serverResult,
|
|
96
|
+
layoutServerResult,
|
|
97
|
+
middlewareServerResult,
|
|
98
|
+
clientResult,
|
|
99
|
+
);
|
|
100
|
+
if (errorBundles.error) manifest.errorBundle = errorBundles.error;
|
|
101
|
+
if (errorBundles.notFound) manifest.notFoundBundle = errorBundles.notFound;
|
|
102
|
+
if (cssBundle) manifest.cssBundle = cssBundle;
|
|
103
|
+
if (fontPreloadHtml) manifest.fontPreloadHtml = fontPreloadHtml;
|
|
104
|
+
await fs.writeFile(
|
|
105
|
+
path.join(buildDir, 'pages-manifest.json'),
|
|
106
|
+
JSON.stringify(manifest, null, 2),
|
|
107
|
+
);
|
|
108
|
+
await fs.rm(outDir, { recursive: true, force: true });
|
|
109
|
+
await fs.rename(buildDir, outDir);
|
|
110
|
+
const clientMetafile =
|
|
111
|
+
devMode && clientResult.metafile
|
|
112
|
+
? rewriteMetafilePaths(clientResult.metafile, buildDir, outDir)
|
|
113
|
+
: undefined;
|
|
114
|
+
return {
|
|
115
|
+
pageCount: pages.length,
|
|
116
|
+
outDir,
|
|
117
|
+
manifest,
|
|
118
|
+
...(clientMetafile ? { clientMetafile } : {}),
|
|
119
|
+
};
|
|
120
|
+
} catch (err) {
|
|
121
|
+
// esbuild has no cancellation API, so sibling builds may still be writing
|
|
122
|
+
// into `buildDir` when we land here. Wait for every pending build to settle
|
|
123
|
+
// before removing the directory — otherwise `fs.rm` races the writes and
|
|
124
|
+
// fails with ENOTEMPTY. The cleanup is fire-and-forget so the caller sees
|
|
125
|
+
// the original error immediately; the unique staging-dir suffix means any
|
|
126
|
+
// lingering files can't pollute a subsequent `buildPages` call.
|
|
127
|
+
void Promise.allSettled(builds).then(() =>
|
|
128
|
+
fs.rm(buildDir, { recursive: true, force: true }).catch(() => {}),
|
|
129
|
+
);
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// esbuild's metafile keys reference the outdir it was given. After the staging
|
|
134
|
+
// directory is swapped into place, rewrite those keys to reference the final
|
|
135
|
+
// outDir so HMR diffs across successive builds compare like-for-like paths.
|
|
136
|
+
function rewriteMetafilePaths(metafile, fromDir, toDir) {
|
|
137
|
+
const outputs = {};
|
|
138
|
+
for (const [key, value] of Object.entries(metafile.outputs)) {
|
|
139
|
+
const abs = path.resolve(key);
|
|
140
|
+
const newKey =
|
|
141
|
+
abs === fromDir || abs.startsWith(fromDir + path.sep)
|
|
142
|
+
? toDir + abs.slice(fromDir.length)
|
|
143
|
+
: abs;
|
|
144
|
+
outputs[newKey] = value;
|
|
145
|
+
}
|
|
146
|
+
return { ...metafile, outputs };
|
|
100
147
|
}
|
|
101
148
|
import { patternToFileName, layoutDirToFileName } from './build-server.js';
|
|
102
149
|
import { generateHydrationEntry } from './build-client.js';
|
package/server/pages/hmr.js
CHANGED
|
@@ -123,7 +123,12 @@ function diffClientMetafiles(oldMeta, newMeta, outDir) {
|
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
if (newFiles.length === 0) {
|
|
126
|
-
return {
|
|
126
|
+
return {
|
|
127
|
+
changedNavBundles: [],
|
|
128
|
+
changedHydrationBundles: [],
|
|
129
|
+
changedCssBundle: null,
|
|
130
|
+
needsFullReload: false,
|
|
131
|
+
};
|
|
127
132
|
}
|
|
128
133
|
const getEntryNames = (meta) => {
|
|
129
134
|
const names = new Set();
|
|
@@ -149,28 +154,29 @@ function diffClientMetafiles(oldMeta, newMeta, outDir) {
|
|
|
149
154
|
}
|
|
150
155
|
}
|
|
151
156
|
const changedNavBundles = [];
|
|
157
|
+
const changedHydrationBundles = [];
|
|
152
158
|
let changedCssBundle = null;
|
|
153
159
|
for (const f of newFiles) {
|
|
154
160
|
const rel = path.relative(outDir, f).replace(/\\/g, '/');
|
|
155
|
-
if (/^client\/nav_
|
|
161
|
+
if (/^client\/nav_.*\.js$/.test(rel)) {
|
|
156
162
|
changedNavBundles.push(rel);
|
|
157
163
|
} else if (rel.endsWith('.css')) {
|
|
158
164
|
changedCssBundle = rel;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
const hasChangedHydrationBundles = newFiles.some((f) => {
|
|
162
|
-
const rel = path.relative(outDir, f).replace(/\\/g, '/');
|
|
163
|
-
return (
|
|
165
|
+
} else if (
|
|
164
166
|
rel.startsWith('client/') &&
|
|
165
|
-
!rel.startsWith('client/nav_') &&
|
|
166
167
|
!rel.startsWith('client/chunks/') &&
|
|
167
|
-
!rel.endsWith('.css') &&
|
|
168
168
|
!rel.startsWith('client/hmr/')
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
) {
|
|
170
|
+
changedHydrationBundles.push(rel);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (
|
|
174
|
+
changedNavBundles.length === 0 &&
|
|
175
|
+
changedHydrationBundles.length === 0 &&
|
|
176
|
+
!changedCssBundle
|
|
177
|
+
) {
|
|
172
178
|
needsFullReload = true;
|
|
173
179
|
}
|
|
174
|
-
return { changedNavBundles, changedCssBundle, needsFullReload };
|
|
180
|
+
return { changedNavBundles, changedHydrationBundles, changedCssBundle, needsFullReload };
|
|
175
181
|
}
|
|
176
182
|
export { buildHmrRuntimeBundle, buildHmrScript, diffClientMetafiles, reactRefreshPlugin };
|
package/server/pages/ssr.js
CHANGED
|
@@ -63,7 +63,7 @@ function wrapWithProviders(createElement, element, pathname, params) {
|
|
|
63
63
|
}
|
|
64
64
|
const providerCtx = globalThis[PROVIDER_CTX_KEY];
|
|
65
65
|
if (providerCtx) {
|
|
66
|
-
const apiValue = { pathPrefix: '' };
|
|
66
|
+
const apiValue = { pathPrefix: '/api' };
|
|
67
67
|
element = createElement(providerCtx.Provider, { value: apiValue }, element);
|
|
68
68
|
}
|
|
69
69
|
return element;
|
|
@@ -94,7 +94,7 @@ function buildHtmlShell({ headHtml, fontPreloadTags, cssLinkTag, bodysuffix }) {
|
|
|
94
94
|
<html>
|
|
95
95
|
<head>
|
|
96
96
|
<meta charset="utf-8" />
|
|
97
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
97
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
98
98
|
${fontPreloadTags}
|
|
99
99
|
${cssLinkTag}
|
|
100
100
|
${headHtml}
|
|
@@ -266,6 +266,7 @@ async function renderErrorPage(
|
|
|
266
266
|
export {
|
|
267
267
|
buildCssLinkTag,
|
|
268
268
|
buildFontPreloadTags,
|
|
269
|
+
buildHtmlShell,
|
|
269
270
|
buildLiveReloadScript,
|
|
270
271
|
buildScriptTags,
|
|
271
272
|
loadComponent,
|
package/server/pages/watcher.js
CHANGED
|
@@ -24,19 +24,20 @@ function createPagesWatcher(options) {
|
|
|
24
24
|
const elapsed = Date.now() - start;
|
|
25
25
|
if (handler.emitEvent && prevClientMetafile && result.clientMetafile && !isStructuralChange) {
|
|
26
26
|
const diff = diffClientMetafiles(prevClientMetafile, result.clientMetafile, outDir);
|
|
27
|
+
const hmrModules = [...diff.changedNavBundles, ...diff.changedHydrationBundles];
|
|
27
28
|
if (diff.needsFullReload) {
|
|
28
29
|
handler.emitEvent({ type: 'reload' });
|
|
29
30
|
log.info(`Pages rebuilt in ${elapsed}ms (full reload)`);
|
|
30
|
-
} else if (
|
|
31
|
+
} else if (hmrModules.length > 0) {
|
|
31
32
|
const manifestJson = handler.getClientManifestJson?.();
|
|
32
33
|
handler.emitEvent({
|
|
33
34
|
type: 'hmr-update',
|
|
34
|
-
modules:
|
|
35
|
+
modules: hmrModules,
|
|
35
36
|
timestamp: Date.now(),
|
|
36
37
|
...(manifestJson ? { manifest: manifestJson } : {}),
|
|
37
38
|
});
|
|
38
39
|
log.info(
|
|
39
|
-
`Pages rebuilt in ${elapsed}ms (HMR: ${
|
|
40
|
+
`Pages rebuilt in ${elapsed}ms (HMR: ${hmrModules.length} module(s))`,
|
|
40
41
|
);
|
|
41
42
|
} else if (diff.changedCssBundle) {
|
|
42
43
|
handler.emitEvent({
|