@jasonshimmy/vite-plugin-cer-app 0.12.0 → 0.12.1
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 +4 -0
- package/commits.txt +1 -1
- package/dist/plugin/virtual/routes.d.ts.map +1 -1
- package/dist/plugin/virtual/routes.js +26 -2
- package/dist/plugin/virtual/routes.js.map +1 -1
- package/dist/runtime/app-template.d.ts +1 -1
- package/dist/runtime/app-template.d.ts.map +1 -1
- package/dist/runtime/app-template.js +44 -14
- package/dist/runtime/app-template.js.map +1 -1
- package/docs/routing.md +23 -7
- package/package.json +1 -1
- package/src/__tests__/plugin/virtual/routes.test.ts +75 -0
- package/src/__tests__/runtime/app-template.test.ts +69 -0
- package/src/plugin/virtual/routes.ts +26 -2
- package/src/runtime/app-template.ts +44 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
|
+
## [v0.12.1] - 2026-03-22
|
|
5
|
+
|
|
6
|
+
- fix: add meta.hydrate support for route hydration strategies and implement related tests (0513743)
|
|
7
|
+
|
|
4
8
|
## [v0.12.0] - 2026-03-22
|
|
5
9
|
|
|
6
10
|
- feat: implement ISR support with isrHandler for SSR fallback in Cloudflare, Netlify, and Vercel adapters (ddcc4d4)
|
package/commits.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
-
|
|
1
|
+
- fix: add meta.hydrate support for route hydration strategies and implement related tests (0513743)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/plugin/virtual/routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/plugin/virtual/routes.ts"],"names":[],"mappings":"AA+IA;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqJ1E"}
|
|
@@ -63,6 +63,25 @@ function extractTransition(source) {
|
|
|
63
63
|
return boolMatch[1] === 'true';
|
|
64
64
|
return null;
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Extracts the per-route `hydrate` strategy from a page file's source.
|
|
68
|
+
* Returns 'load', 'idle', 'visible', 'none', or null if absent.
|
|
69
|
+
* 'load' is the default — callers skip emitting it to keep the bundle lean.
|
|
70
|
+
*
|
|
71
|
+
* Matches patterns like:
|
|
72
|
+
* hydrate: 'idle'
|
|
73
|
+
* hydrate: 'visible'
|
|
74
|
+
* hydrate: 'none'
|
|
75
|
+
*/
|
|
76
|
+
function extractHydrate(source) {
|
|
77
|
+
const match = source.match(/hydrate\s*:\s*['"]([^'"]+)['"]/);
|
|
78
|
+
if (!match)
|
|
79
|
+
return null;
|
|
80
|
+
const val = match[1];
|
|
81
|
+
if (val === 'load' || val === 'idle' || val === 'visible' || val === 'none')
|
|
82
|
+
return val;
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
66
85
|
/**
|
|
67
86
|
* Extracts the per-route `render` strategy from a page file's source.
|
|
68
87
|
* Returns 'static', 'server', 'spa', or null if absent.
|
|
@@ -176,16 +195,17 @@ export async function generateRoutesCode(pagesDir) {
|
|
|
176
195
|
revalidate: extractRevalidate(src),
|
|
177
196
|
transition: extractTransition(src),
|
|
178
197
|
render: extractRender(src),
|
|
198
|
+
hydrate: extractHydrate(src),
|
|
179
199
|
};
|
|
180
200
|
}
|
|
181
201
|
catch {
|
|
182
|
-
return { middleware: [], layout: null, layoutChain: null, revalidate: null, transition: null, render: null };
|
|
202
|
+
return { middleware: [], layout: null, layoutChain: null, revalidate: null, transition: null, render: null, hydrate: null };
|
|
183
203
|
}
|
|
184
204
|
}));
|
|
185
205
|
const lines = ['// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app', ''];
|
|
186
206
|
// Build routes array with lazy load() functions for code splitting.
|
|
187
207
|
const routeItems = sorted.map((entry, i) => {
|
|
188
|
-
const { middleware: mw, layout, layoutChain, revalidate, transition, render } = metaPerEntry[i];
|
|
208
|
+
const { middleware: mw, layout, layoutChain, revalidate, transition, render, hydrate } = metaPerEntry[i];
|
|
189
209
|
const filePath = JSON.stringify(entry.filePath);
|
|
190
210
|
const tagName = JSON.stringify(entry.tagName);
|
|
191
211
|
const routePath = JSON.stringify(entry.routePath);
|
|
@@ -211,6 +231,10 @@ export async function generateRoutesCode(pagesDir) {
|
|
|
211
231
|
if (render !== null) {
|
|
212
232
|
metaFields.push(`render: ${JSON.stringify(render)}`);
|
|
213
233
|
}
|
|
234
|
+
// 'load' is the default — only emit non-default values to keep bundle lean.
|
|
235
|
+
if (hydrate !== null && hydrate !== 'load') {
|
|
236
|
+
metaFields.push(`hydrate: ${JSON.stringify(hydrate)}`);
|
|
237
|
+
}
|
|
214
238
|
const metaStr = metaFields.length > 0 ? ` meta: { ${metaFields.join(', ')} },\n` : '';
|
|
215
239
|
if (mw.length === 0) {
|
|
216
240
|
return (` {\n` +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/plugin/virtual/routes.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/plugin/virtual/routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* automatically receive the latest bootstrap code on plugin update.
|
|
6
6
|
* This file is gitignored and should never be edited directly.
|
|
7
7
|
*/
|
|
8
|
-
export declare const APP_ENTRY_TEMPLATE = "// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app \u2014 do not edit.\n// Regenerated automatically on every dev server start and build.\n\nimport '@jasonshimmy/custom-elements-runtime/css'\nimport 'virtual:cer-jit-css'\nimport 'virtual:cer-components'\nimport routes from 'virtual:cer-routes'\nimport layouts from 'virtual:cer-layouts'\nimport plugins from 'virtual:cer-plugins'\nimport { hasLoading, loadingTag } from 'virtual:cer-loading'\nimport { hasError, errorTag } from 'virtual:cer-error'\nimport { runtimeConfig } from 'virtual:cer-app-config'\nimport {\n component,\n ref,\n provide,\n useOnConnected,\n useOnDisconnected,\n registerBuiltinComponents,\n} from '@jasonshimmy/custom-elements-runtime'\nimport { initRouter } from '@jasonshimmy/custom-elements-runtime/router'\nimport { enableJITCSS } from '@jasonshimmy/custom-elements-runtime/jit-css'\nimport { initRuntimeConfig } from '@jasonshimmy/vite-plugin-cer-app/composables'\n\nregisterBuiltinComponents()\nenableJITCSS()\ninitRuntimeConfig(runtimeConfig)\n\nconst router = initRouter({ routes })\n\n// \u2500\u2500\u2500 Navigation state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst isNavigating = ref(false)\nconst currentError = ref(null)\n\nconst resetError = () => {\n currentError.value = null\n void router.replace(router.getCurrent().path)\n}\n;(globalThis).resetError = resetError\n\nconst _push = router.push.bind(router)\nconst _replace = router.replace.bind(router)\n\nrouter.push = async (path) => {\n isNavigating.value = true\n currentError.value = null\n try {\n await _push(path)\n } catch (err) {\n currentError.value = err instanceof Error ? err.message : String(err)\n } finally {\n isNavigating.value = false\n }\n}\n\nrouter.replace = async (path) => {\n isNavigating.value = true\n currentError.value = null\n try {\n await _replace(path)\n } catch (err) {\n currentError.value = err instanceof Error ? err.message : String(err)\n } finally {\n isNavigating.value = false\n }\n}\n\n// \u2500\u2500\u2500 Plugins \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Collect plugin-provided values so cer-layout-view can forward them into\n// the component context tree via the real provide() hook (which inject() walks).\n// Declared BEFORE component('cer-layout-view') to avoid a temporal dead zone\n// ReferenceError: customElements.define() upgrades existing DOM elements\n// synchronously, calling the render function immediately.\nconst _pluginProvides = new Map()\n// Expose plugin provides globally so page components can read them synchronously\n// regardless of render order.\n;(globalThis).__cerPluginProvides = _pluginProvides\n\n// \u2500\u2500\u2500 <cer-layout-view> \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ncomponent('cer-layout-view', () => {\n // Forward plugin-provided values into the component context so inject() in\n // any descendant component can resolve them by walking up the DOM tree.\n for (const [key, value] of _pluginProvides) {\n provide(key, value)\n }\n\n const current = ref(router.getCurrent())\n let unsub\n\n useOnConnected(() => {\n unsub = router.subscribe((s) => { current.value = s })\n })\n useOnDisconnected(() => { unsub?.(); unsub = undefined })\n\n if (currentError.value !== null) {\n if (hasError && errorTag) {\n return { tag: errorTag, props: { attrs: { error: String(currentError.value) } }, children: [] }\n }\n return { tag: 'div', props: { attrs: { style: 'padding:2rem;font-family:monospace' } }, children: String(currentError.value) }\n }\n\n if (isNavigating.value && hasLoading && loadingTag) {\n return { tag: loadingTag, props: {}, children: [] }\n }\n\n const matched = router.matchRoute(current.value.path)\n const routeMeta = matched?.route?.meta\n const routerView = { tag: 'router-view', props: {}, children: [] }\n\n // Support nested layout chains: meta.layoutChain = ['default', 'admin']\n // renders <layout-default><layout-admin><router-view/></layout-admin></layout-default>\n const chain = routeMeta?.layoutChain\n ? routeMeta.layoutChain\n : [routeMeta?.layout ?? 'default']\n\n // Build nested vnodes from innermost to outermost.\n let vnode = routerView\n for (let i = chain.length - 1; i >= 0; i--) {\n const tag = layouts[chain[i]]\n if (tag) vnode = { tag, props: {}, children: [vnode] }\n }\n return vnode\n})\n\nfor (const plugin of plugins) {\n if (plugin && typeof plugin.setup === 'function') {\n await plugin.setup({\n router,\n provide: (key, value) => { _pluginProvides.set(key, value) },\n config: {},\n })\n }\n}\n\n// \u2500\u2500\u2500 Pre-load initial route \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\
|
|
8
|
+
export declare const APP_ENTRY_TEMPLATE = "// AUTO-GENERATED by @jasonshimmy/vite-plugin-cer-app \u2014 do not edit.\n// Regenerated automatically on every dev server start and build.\n\nimport '@jasonshimmy/custom-elements-runtime/css'\nimport 'virtual:cer-jit-css'\nimport 'virtual:cer-components'\nimport routes from 'virtual:cer-routes'\nimport layouts from 'virtual:cer-layouts'\nimport plugins from 'virtual:cer-plugins'\nimport { hasLoading, loadingTag } from 'virtual:cer-loading'\nimport { hasError, errorTag } from 'virtual:cer-error'\nimport { runtimeConfig } from 'virtual:cer-app-config'\nimport {\n component,\n ref,\n provide,\n useOnConnected,\n useOnDisconnected,\n registerBuiltinComponents,\n} from '@jasonshimmy/custom-elements-runtime'\nimport { initRouter } from '@jasonshimmy/custom-elements-runtime/router'\nimport { enableJITCSS } from '@jasonshimmy/custom-elements-runtime/jit-css'\nimport { initRuntimeConfig } from '@jasonshimmy/vite-plugin-cer-app/composables'\n\nregisterBuiltinComponents()\nenableJITCSS()\ninitRuntimeConfig(runtimeConfig)\n\nconst router = initRouter({ routes })\n\n// \u2500\u2500\u2500 Navigation state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst isNavigating = ref(false)\nconst currentError = ref(null)\n\nconst resetError = () => {\n currentError.value = null\n void router.replace(router.getCurrent().path)\n}\n;(globalThis).resetError = resetError\n\nconst _push = router.push.bind(router)\nconst _replace = router.replace.bind(router)\n\nrouter.push = async (path) => {\n isNavigating.value = true\n currentError.value = null\n try {\n await _push(path)\n } catch (err) {\n currentError.value = err instanceof Error ? err.message : String(err)\n } finally {\n isNavigating.value = false\n }\n}\n\nrouter.replace = async (path) => {\n isNavigating.value = true\n currentError.value = null\n try {\n await _replace(path)\n } catch (err) {\n currentError.value = err instanceof Error ? err.message : String(err)\n } finally {\n isNavigating.value = false\n }\n}\n\n// \u2500\u2500\u2500 Plugins \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Collect plugin-provided values so cer-layout-view can forward them into\n// the component context tree via the real provide() hook (which inject() walks).\n// Declared BEFORE component('cer-layout-view') to avoid a temporal dead zone\n// ReferenceError: customElements.define() upgrades existing DOM elements\n// synchronously, calling the render function immediately.\nconst _pluginProvides = new Map()\n// Expose plugin provides globally so page components can read them synchronously\n// regardless of render order.\n;(globalThis).__cerPluginProvides = _pluginProvides\n\n// \u2500\u2500\u2500 <cer-layout-view> \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ncomponent('cer-layout-view', () => {\n // Forward plugin-provided values into the component context so inject() in\n // any descendant component can resolve them by walking up the DOM tree.\n for (const [key, value] of _pluginProvides) {\n provide(key, value)\n }\n\n const current = ref(router.getCurrent())\n let unsub\n\n useOnConnected(() => {\n unsub = router.subscribe((s) => { current.value = s })\n })\n useOnDisconnected(() => { unsub?.(); unsub = undefined })\n\n if (currentError.value !== null) {\n if (hasError && errorTag) {\n return { tag: errorTag, props: { attrs: { error: String(currentError.value) } }, children: [] }\n }\n return { tag: 'div', props: { attrs: { style: 'padding:2rem;font-family:monospace' } }, children: String(currentError.value) }\n }\n\n if (isNavigating.value && hasLoading && loadingTag) {\n return { tag: loadingTag, props: {}, children: [] }\n }\n\n const matched = router.matchRoute(current.value.path)\n const routeMeta = matched?.route?.meta\n const routerView = { tag: 'router-view', props: {}, children: [] }\n\n // Support nested layout chains: meta.layoutChain = ['default', 'admin']\n // renders <layout-default><layout-admin><router-view/></layout-admin></layout-default>\n const chain = routeMeta?.layoutChain\n ? routeMeta.layoutChain\n : [routeMeta?.layout ?? 'default']\n\n // Build nested vnodes from innermost to outermost.\n let vnode = routerView\n for (let i = chain.length - 1; i >= 0; i--) {\n const tag = layouts[chain[i]]\n if (tag) vnode = { tag, props: {}, children: [vnode] }\n }\n return vnode\n})\n\nfor (const plugin of plugins) {\n if (plugin && typeof plugin.setup === 'function') {\n await plugin.setup({\n router,\n provide: (key, value) => { _pluginProvides.set(key, value) },\n config: {},\n })\n }\n}\n\n// \u2500\u2500\u2500 Pre-load initial route + hydration strategy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Download the current page's route chunk AFTER plugins run so that\n// cer-layout-view's first render (which calls provide()) completes before\n// page components are defined and their renders are scheduled. This ensures\n// inject() in child components can find values stored by provide().\n//\n// meta.hydrate controls WHEN the initial page activates:\n// 'load' \u2014 immediately (default)\n// 'idle' \u2014 deferred until requestIdleCallback (browser idle)\n// 'visible' \u2014 deferred until cer-layout-view enters the viewport\n// 'none' \u2014 never: SSR HTML stays as-is, no JS activation\n\nif (typeof window !== 'undefined') {\n const _initMatch = router.matchRoute(window.location.pathname)\n const _hydrateStrategy = _initMatch?.route?.meta?.hydrate ?? 'load'\n\n if (_hydrateStrategy === 'none') {\n // Static HTML only \u2014 leave SSR output untouched, clean up data immediately.\n delete (globalThis).__CER_DATA__\n } else {\n const _doHydrate = async () => {\n if (_initMatch?.route?.load) {\n try { await _initMatch.route.load() } catch { /* non-fatal */ }\n }\n // Use the original (unwrapped) replace so isNavigating stays false during\n // the initial paint \u2014 the loading component must not flash over pre-rendered content.\n await _replace(window.location.pathname + window.location.search + window.location.hash)\n // Clear SSR loader data after initial navigation so subsequent client-side\n // navigations don't accidentally reuse stale server data.\n delete (globalThis).__CER_DATA__\n }\n\n if (_hydrateStrategy === 'idle') {\n // Defer until the browser has finished higher-priority work.\n if (typeof requestIdleCallback !== 'undefined') {\n requestIdleCallback(() => { void _doHydrate() })\n } else {\n // Safari / older environments fallback.\n setTimeout(() => { void _doHydrate() }, 1)\n }\n } else if (_hydrateStrategy === 'visible') {\n // Defer until cer-layout-view (or body as fallback) enters the viewport.\n const _el = document.querySelector('cer-layout-view') ?? document.body\n const _io = new IntersectionObserver(([entry]) => {\n if (entry.isIntersecting) { _io.disconnect(); void _doHydrate() }\n })\n _io.observe(_el)\n } else {\n // 'load' \u2014 hydrate immediately (default behaviour).\n await _doHydrate()\n }\n }\n}\n\nexport { router }\n";
|
|
9
9
|
//# sourceMappingURL=app-template.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-template.d.ts","sourceRoot":"","sources":["../../src/runtime/app-template.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,
|
|
1
|
+
{"version":3,"file":"app-template.d.ts","sourceRoot":"","sources":["../../src/runtime/app-template.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,slQAgM9B,CAAA"}
|
|
@@ -142,28 +142,58 @@ for (const plugin of plugins) {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
// ─── Pre-load initial route
|
|
145
|
+
// ─── Pre-load initial route + hydration strategy ─────────────────────────────
|
|
146
146
|
// Download the current page's route chunk AFTER plugins run so that
|
|
147
147
|
// cer-layout-view's first render (which calls provide()) completes before
|
|
148
148
|
// page components are defined and their renders are scheduled. This ensures
|
|
149
149
|
// inject() in child components can find values stored by provide().
|
|
150
|
+
//
|
|
151
|
+
// meta.hydrate controls WHEN the initial page activates:
|
|
152
|
+
// 'load' — immediately (default)
|
|
153
|
+
// 'idle' — deferred until requestIdleCallback (browser idle)
|
|
154
|
+
// 'visible' — deferred until cer-layout-view enters the viewport
|
|
155
|
+
// 'none' — never: SSR HTML stays as-is, no JS activation
|
|
150
156
|
|
|
151
157
|
if (typeof window !== 'undefined') {
|
|
152
158
|
const _initMatch = router.matchRoute(window.location.pathname)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
const _hydrateStrategy = _initMatch?.route?.meta?.hydrate ?? 'load'
|
|
160
|
+
|
|
161
|
+
if (_hydrateStrategy === 'none') {
|
|
162
|
+
// Static HTML only — leave SSR output untouched, clean up data immediately.
|
|
163
|
+
delete (globalThis).__CER_DATA__
|
|
164
|
+
} else {
|
|
165
|
+
const _doHydrate = async () => {
|
|
166
|
+
if (_initMatch?.route?.load) {
|
|
167
|
+
try { await _initMatch.route.load() } catch { /* non-fatal */ }
|
|
168
|
+
}
|
|
169
|
+
// Use the original (unwrapped) replace so isNavigating stays false during
|
|
170
|
+
// the initial paint — the loading component must not flash over pre-rendered content.
|
|
171
|
+
await _replace(window.location.pathname + window.location.search + window.location.hash)
|
|
172
|
+
// Clear SSR loader data after initial navigation so subsequent client-side
|
|
173
|
+
// navigations don't accidentally reuse stale server data.
|
|
174
|
+
delete (globalThis).__CER_DATA__
|
|
175
|
+
}
|
|
159
176
|
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
177
|
+
if (_hydrateStrategy === 'idle') {
|
|
178
|
+
// Defer until the browser has finished higher-priority work.
|
|
179
|
+
if (typeof requestIdleCallback !== 'undefined') {
|
|
180
|
+
requestIdleCallback(() => { void _doHydrate() })
|
|
181
|
+
} else {
|
|
182
|
+
// Safari / older environments fallback.
|
|
183
|
+
setTimeout(() => { void _doHydrate() }, 1)
|
|
184
|
+
}
|
|
185
|
+
} else if (_hydrateStrategy === 'visible') {
|
|
186
|
+
// Defer until cer-layout-view (or body as fallback) enters the viewport.
|
|
187
|
+
const _el = document.querySelector('cer-layout-view') ?? document.body
|
|
188
|
+
const _io = new IntersectionObserver(([entry]) => {
|
|
189
|
+
if (entry.isIntersecting) { _io.disconnect(); void _doHydrate() }
|
|
190
|
+
})
|
|
191
|
+
_io.observe(_el)
|
|
192
|
+
} else {
|
|
193
|
+
// 'load' — hydrate immediately (default behaviour).
|
|
194
|
+
await _doHydrate()
|
|
195
|
+
}
|
|
196
|
+
}
|
|
167
197
|
}
|
|
168
198
|
|
|
169
199
|
export { router }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-template.js","sourceRoot":"","sources":["../../src/runtime/app-template.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG
|
|
1
|
+
{"version":3,"file":"app-template.js","sourceRoot":"","sources":["../../src/runtime/app-template.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgMjC,CAAA"}
|
package/docs/routing.md
CHANGED
|
@@ -148,14 +148,30 @@ Array of named middleware to run before navigating to this page. Names must matc
|
|
|
148
148
|
|
|
149
149
|
### `meta.hydrate`
|
|
150
150
|
|
|
151
|
-
Controls when the component hydrates on the client after SSR
|
|
151
|
+
Controls when the page component activates (hydrates) on the client after SSR. The server always renders full HTML regardless of this setting — it only affects client-side JS activation timing.
|
|
152
152
|
|
|
153
|
-
| Value | Behavior |
|
|
154
|
-
|
|
155
|
-
| `'load'` | Hydrates immediately on page load |
|
|
156
|
-
| `'idle'` |
|
|
157
|
-
| `'visible'` |
|
|
158
|
-
| `'none'` | Never hydrates
|
|
153
|
+
| Value | Behavior | Browser API |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| `'load'` | Hydrates immediately on page load (default) | — |
|
|
156
|
+
| `'idle'` | Defers until the browser has finished higher-priority work | `requestIdleCallback` (falls back to `setTimeout` in Safari) |
|
|
157
|
+
| `'visible'` | Defers until `<cer-layout-view>` enters the viewport | `IntersectionObserver` |
|
|
158
|
+
| `'none'` | Never hydrates — SSR HTML stays as-is, no JS activation | — |
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
// app/pages/marketing.ts — defer activation until idle
|
|
162
|
+
export const meta = {
|
|
163
|
+
hydrate: 'idle',
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// app/pages/legal/terms.ts — fully static, no JS needed
|
|
167
|
+
export const meta = {
|
|
168
|
+
hydrate: 'none',
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**`'none'` and `usePageData`:** When `hydrate: 'none'` is set, the page component never activates and `usePageData()` loader data is not consumed. Avoid using `loader` on pages with `hydrate: 'none'` — the serialized `window.__CER_DATA__` will be present in the HTML but never read or cleaned up by the client.
|
|
173
|
+
|
|
174
|
+
**SPA mode:** `meta.hydrate` has no effect in SPA mode — there is no SSR output to preserve, so the component always activates immediately.
|
|
159
175
|
|
|
160
176
|
### `meta.ssg.paths`
|
|
161
177
|
|
package/package.json
CHANGED
|
@@ -432,6 +432,81 @@ describe('generateRoutesCode — meta.render', () => {
|
|
|
432
432
|
})
|
|
433
433
|
})
|
|
434
434
|
|
|
435
|
+
// ─── meta.hydrate ─────────────────────────────────────────────────────────────
|
|
436
|
+
|
|
437
|
+
describe('generateRoutesCode — meta.hydrate', () => {
|
|
438
|
+
beforeEach(() => {
|
|
439
|
+
vi.mocked(existsSync).mockReturnValue(true)
|
|
440
|
+
vi.mocked(scanDirectory).mockResolvedValue([])
|
|
441
|
+
vi.mocked(readFile).mockResolvedValue('' as never)
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
it('emits meta.hydrate "idle" when declared', async () => {
|
|
445
|
+
vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
|
|
446
|
+
vi.mocked(readFile).mockResolvedValue(
|
|
447
|
+
`export const meta = { hydrate: 'idle' }` as never,
|
|
448
|
+
)
|
|
449
|
+
const code = await generateRoutesCode(PAGES)
|
|
450
|
+
expect(code).toContain('hydrate: "idle"')
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
it('emits meta.hydrate "visible" when declared', async () => {
|
|
454
|
+
vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/hero.ts`])
|
|
455
|
+
vi.mocked(readFile).mockResolvedValue(
|
|
456
|
+
`export const meta = { hydrate: 'visible' }` as never,
|
|
457
|
+
)
|
|
458
|
+
const code = await generateRoutesCode(PAGES)
|
|
459
|
+
expect(code).toContain('hydrate: "visible"')
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
it('emits meta.hydrate "none" when declared', async () => {
|
|
463
|
+
vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/static-page.ts`])
|
|
464
|
+
vi.mocked(readFile).mockResolvedValue(
|
|
465
|
+
`export const meta = { hydrate: 'none' }` as never,
|
|
466
|
+
)
|
|
467
|
+
const code = await generateRoutesCode(PAGES)
|
|
468
|
+
expect(code).toContain('hydrate: "none"')
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
it('does NOT emit meta.hydrate when "load" (default — no-op)', async () => {
|
|
472
|
+
vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
|
|
473
|
+
vi.mocked(readFile).mockResolvedValue(
|
|
474
|
+
`export const meta = { hydrate: 'load' }` as never,
|
|
475
|
+
)
|
|
476
|
+
const code = await generateRoutesCode(PAGES)
|
|
477
|
+
expect(code).not.toContain('hydrate:')
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
it('omits hydrate meta when not declared', async () => {
|
|
481
|
+
vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
|
|
482
|
+
vi.mocked(readFile).mockResolvedValue(
|
|
483
|
+
`component('page-about', () => html\`<h1>About</h1>\`)` as never,
|
|
484
|
+
)
|
|
485
|
+
const code = await generateRoutesCode(PAGES)
|
|
486
|
+
expect(code).not.toContain('hydrate:')
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
it('ignores unknown hydrate values', async () => {
|
|
490
|
+
vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/about.ts`])
|
|
491
|
+
vi.mocked(readFile).mockResolvedValue(
|
|
492
|
+
`export const meta = { hydrate: 'lazy' }` as never,
|
|
493
|
+
)
|
|
494
|
+
const code = await generateRoutesCode(PAGES)
|
|
495
|
+
expect(code).not.toContain('hydrate:')
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
it('can combine hydrate with layout and render', async () => {
|
|
499
|
+
vi.mocked(scanDirectory).mockResolvedValue([`${PAGES}/dashboard.ts`])
|
|
500
|
+
vi.mocked(readFile).mockResolvedValue(
|
|
501
|
+
`export const meta = { hydrate: 'idle', layout: 'minimal', render: 'server' }` as never,
|
|
502
|
+
)
|
|
503
|
+
const code = await generateRoutesCode(PAGES)
|
|
504
|
+
expect(code).toContain('hydrate: "idle"')
|
|
505
|
+
expect(code).toContain('layout: "minimal"')
|
|
506
|
+
expect(code).toContain('render: "server"')
|
|
507
|
+
})
|
|
508
|
+
})
|
|
509
|
+
|
|
435
510
|
// ─── nested layouts (layoutChain) ────────────────────────────────────────────
|
|
436
511
|
|
|
437
512
|
describe('generateRoutesCode — layoutChain (nested layouts)', () => {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { APP_ENTRY_TEMPLATE } from '../../runtime/app-template.js'
|
|
3
|
+
|
|
4
|
+
// ─── Hydration strategy ───────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
describe('APP_ENTRY_TEMPLATE — meta.hydrate', () => {
|
|
7
|
+
it('reads meta.hydrate from the matched route', () => {
|
|
8
|
+
expect(APP_ENTRY_TEMPLATE).toContain('_initMatch?.route?.meta?.hydrate')
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('defaults to "load" when hydrate is not set', () => {
|
|
12
|
+
expect(APP_ENTRY_TEMPLATE).toContain(`?? 'load'`)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('skips hydration entirely for strategy "none"', () => {
|
|
16
|
+
expect(APP_ENTRY_TEMPLATE).toContain(`_hydrateStrategy === 'none'`)
|
|
17
|
+
// Should delete __CER_DATA__ but NOT call _replace
|
|
18
|
+
const noneBlock = APP_ENTRY_TEMPLATE.slice(
|
|
19
|
+
APP_ENTRY_TEMPLATE.indexOf(`_hydrateStrategy === 'none'`),
|
|
20
|
+
)
|
|
21
|
+
// The delete must appear before _doHydrate is defined (i.e. in the none branch)
|
|
22
|
+
expect(APP_ENTRY_TEMPLATE).toContain(`delete (globalThis).__CER_DATA__`)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('defers hydration with requestIdleCallback for strategy "idle"', () => {
|
|
26
|
+
expect(APP_ENTRY_TEMPLATE).toContain(`_hydrateStrategy === 'idle'`)
|
|
27
|
+
expect(APP_ENTRY_TEMPLATE).toContain('requestIdleCallback')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('includes a setTimeout fallback for environments without requestIdleCallback', () => {
|
|
31
|
+
expect(APP_ENTRY_TEMPLATE).toContain('typeof requestIdleCallback')
|
|
32
|
+
expect(APP_ENTRY_TEMPLATE).toContain('setTimeout')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('defers hydration with IntersectionObserver for strategy "visible"', () => {
|
|
36
|
+
expect(APP_ENTRY_TEMPLATE).toContain(`_hydrateStrategy === 'visible'`)
|
|
37
|
+
expect(APP_ENTRY_TEMPLATE).toContain('IntersectionObserver')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('observes cer-layout-view element for "visible" strategy', () => {
|
|
41
|
+
expect(APP_ENTRY_TEMPLATE).toContain(`querySelector('cer-layout-view')`)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('falls back to document.body when cer-layout-view is not found', () => {
|
|
45
|
+
expect(APP_ENTRY_TEMPLATE).toContain('document.body')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('disconnects the IntersectionObserver after first intersection', () => {
|
|
49
|
+
expect(APP_ENTRY_TEMPLATE).toContain('_io.disconnect()')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('calls _doHydrate immediately for strategy "load"', () => {
|
|
53
|
+
// The else branch (default load) calls _doHydrate directly with await
|
|
54
|
+
expect(APP_ENTRY_TEMPLATE).toContain('await _doHydrate()')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('_doHydrate loads the route chunk and calls _replace', () => {
|
|
58
|
+
expect(APP_ENTRY_TEMPLATE).toContain('_initMatch?.route?.load')
|
|
59
|
+
expect(APP_ENTRY_TEMPLATE).toContain('await _replace(window.location.pathname')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('_doHydrate clears __CER_DATA__ after navigation', () => {
|
|
63
|
+
// delete must appear inside _doHydrate, i.e. after _replace and before the closing }
|
|
64
|
+
const doHydrateStart = APP_ENTRY_TEMPLATE.indexOf('const _doHydrate')
|
|
65
|
+
const doHydrateEnd = APP_ENTRY_TEMPLATE.indexOf('\n }', doHydrateStart)
|
|
66
|
+
const doHydrateBlock = APP_ENTRY_TEMPLATE.slice(doHydrateStart, doHydrateEnd)
|
|
67
|
+
expect(doHydrateBlock).toContain('delete (globalThis).__CER_DATA__')
|
|
68
|
+
})
|
|
69
|
+
})
|
|
@@ -64,6 +64,24 @@ function extractTransition(source: string): string | boolean | null {
|
|
|
64
64
|
return null
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Extracts the per-route `hydrate` strategy from a page file's source.
|
|
69
|
+
* Returns 'load', 'idle', 'visible', 'none', or null if absent.
|
|
70
|
+
* 'load' is the default — callers skip emitting it to keep the bundle lean.
|
|
71
|
+
*
|
|
72
|
+
* Matches patterns like:
|
|
73
|
+
* hydrate: 'idle'
|
|
74
|
+
* hydrate: 'visible'
|
|
75
|
+
* hydrate: 'none'
|
|
76
|
+
*/
|
|
77
|
+
function extractHydrate(source: string): 'load' | 'idle' | 'visible' | 'none' | null {
|
|
78
|
+
const match = source.match(/hydrate\s*:\s*['"]([^'"]+)['"]/)
|
|
79
|
+
if (!match) return null
|
|
80
|
+
const val = match[1]
|
|
81
|
+
if (val === 'load' || val === 'idle' || val === 'visible' || val === 'none') return val
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
|
|
67
85
|
/**
|
|
68
86
|
* Extracts the per-route `render` strategy from a page file's source.
|
|
69
87
|
* Returns 'static', 'server', 'spa', or null if absent.
|
|
@@ -180,6 +198,7 @@ export async function generateRoutesCode(pagesDir: string): Promise<string> {
|
|
|
180
198
|
revalidate: number | null
|
|
181
199
|
transition: string | boolean | null
|
|
182
200
|
render: 'static' | 'server' | 'spa' | null
|
|
201
|
+
hydrate: 'load' | 'idle' | 'visible' | 'none' | null
|
|
183
202
|
}> = await Promise.all(
|
|
184
203
|
sorted.map(async (entry) => {
|
|
185
204
|
try {
|
|
@@ -193,9 +212,10 @@ export async function generateRoutesCode(pagesDir: string): Promise<string> {
|
|
|
193
212
|
revalidate: extractRevalidate(src),
|
|
194
213
|
transition: extractTransition(src),
|
|
195
214
|
render: extractRender(src),
|
|
215
|
+
hydrate: extractHydrate(src),
|
|
196
216
|
}
|
|
197
217
|
} catch {
|
|
198
|
-
return { middleware: [], layout: null, layoutChain: null, revalidate: null, transition: null, render: null }
|
|
218
|
+
return { middleware: [], layout: null, layoutChain: null, revalidate: null, transition: null, render: null, hydrate: null }
|
|
199
219
|
}
|
|
200
220
|
}),
|
|
201
221
|
)
|
|
@@ -204,7 +224,7 @@ export async function generateRoutesCode(pagesDir: string): Promise<string> {
|
|
|
204
224
|
|
|
205
225
|
// Build routes array with lazy load() functions for code splitting.
|
|
206
226
|
const routeItems = sorted.map((entry, i) => {
|
|
207
|
-
const { middleware: mw, layout, layoutChain, revalidate, transition, render } = metaPerEntry[i]
|
|
227
|
+
const { middleware: mw, layout, layoutChain, revalidate, transition, render, hydrate } = metaPerEntry[i]
|
|
208
228
|
const filePath = JSON.stringify(entry.filePath)
|
|
209
229
|
const tagName = JSON.stringify(entry.tagName)
|
|
210
230
|
const routePath = JSON.stringify(entry.routePath)
|
|
@@ -231,6 +251,10 @@ export async function generateRoutesCode(pagesDir: string): Promise<string> {
|
|
|
231
251
|
if (render !== null) {
|
|
232
252
|
metaFields.push(`render: ${JSON.stringify(render)}`)
|
|
233
253
|
}
|
|
254
|
+
// 'load' is the default — only emit non-default values to keep bundle lean.
|
|
255
|
+
if (hydrate !== null && hydrate !== 'load') {
|
|
256
|
+
metaFields.push(`hydrate: ${JSON.stringify(hydrate)}`)
|
|
257
|
+
}
|
|
234
258
|
const metaStr = metaFields.length > 0 ? ` meta: { ${metaFields.join(', ')} },\n` : ''
|
|
235
259
|
|
|
236
260
|
if (mw.length === 0) {
|
|
@@ -142,28 +142,58 @@ for (const plugin of plugins) {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
// ─── Pre-load initial route
|
|
145
|
+
// ─── Pre-load initial route + hydration strategy ─────────────────────────────
|
|
146
146
|
// Download the current page's route chunk AFTER plugins run so that
|
|
147
147
|
// cer-layout-view's first render (which calls provide()) completes before
|
|
148
148
|
// page components are defined and their renders are scheduled. This ensures
|
|
149
149
|
// inject() in child components can find values stored by provide().
|
|
150
|
+
//
|
|
151
|
+
// meta.hydrate controls WHEN the initial page activates:
|
|
152
|
+
// 'load' — immediately (default)
|
|
153
|
+
// 'idle' — deferred until requestIdleCallback (browser idle)
|
|
154
|
+
// 'visible' — deferred until cer-layout-view enters the viewport
|
|
155
|
+
// 'none' — never: SSR HTML stays as-is, no JS activation
|
|
150
156
|
|
|
151
157
|
if (typeof window !== 'undefined') {
|
|
152
158
|
const _initMatch = router.matchRoute(window.location.pathname)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
const _hydrateStrategy = _initMatch?.route?.meta?.hydrate ?? 'load'
|
|
160
|
+
|
|
161
|
+
if (_hydrateStrategy === 'none') {
|
|
162
|
+
// Static HTML only — leave SSR output untouched, clean up data immediately.
|
|
163
|
+
delete (globalThis).__CER_DATA__
|
|
164
|
+
} else {
|
|
165
|
+
const _doHydrate = async () => {
|
|
166
|
+
if (_initMatch?.route?.load) {
|
|
167
|
+
try { await _initMatch.route.load() } catch { /* non-fatal */ }
|
|
168
|
+
}
|
|
169
|
+
// Use the original (unwrapped) replace so isNavigating stays false during
|
|
170
|
+
// the initial paint — the loading component must not flash over pre-rendered content.
|
|
171
|
+
await _replace(window.location.pathname + window.location.search + window.location.hash)
|
|
172
|
+
// Clear SSR loader data after initial navigation so subsequent client-side
|
|
173
|
+
// navigations don't accidentally reuse stale server data.
|
|
174
|
+
delete (globalThis).__CER_DATA__
|
|
175
|
+
}
|
|
159
176
|
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
177
|
+
if (_hydrateStrategy === 'idle') {
|
|
178
|
+
// Defer until the browser has finished higher-priority work.
|
|
179
|
+
if (typeof requestIdleCallback !== 'undefined') {
|
|
180
|
+
requestIdleCallback(() => { void _doHydrate() })
|
|
181
|
+
} else {
|
|
182
|
+
// Safari / older environments fallback.
|
|
183
|
+
setTimeout(() => { void _doHydrate() }, 1)
|
|
184
|
+
}
|
|
185
|
+
} else if (_hydrateStrategy === 'visible') {
|
|
186
|
+
// Defer until cer-layout-view (or body as fallback) enters the viewport.
|
|
187
|
+
const _el = document.querySelector('cer-layout-view') ?? document.body
|
|
188
|
+
const _io = new IntersectionObserver(([entry]) => {
|
|
189
|
+
if (entry.isIntersecting) { _io.disconnect(); void _doHydrate() }
|
|
190
|
+
})
|
|
191
|
+
_io.observe(_el)
|
|
192
|
+
} else {
|
|
193
|
+
// 'load' — hydrate immediately (default behaviour).
|
|
194
|
+
await _doHydrate()
|
|
195
|
+
}
|
|
196
|
+
}
|
|
167
197
|
}
|
|
168
198
|
|
|
169
199
|
export { router }
|