From 555178a01ad017e2033fc5770cb58df6d15d5e35 Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Tue, 25 Mar 2025 09:33:20 +0100 Subject: [PATCH 01/17] feat(core): dynamic modulepreload --- .changeset/fair-cars-fry.md | 6 + packages/docs/src/root.tsx | 4 +- .../docs/src/routes/api/qwik-server/api.json | 4 +- .../docs/src/routes/api/qwik-server/index.md | 23 ++- packages/qwik/src/core/qrl/preload.ts | 174 ++++++++++++++++++ packages/qwik/src/core/qrl/qrl-class.ts | 6 + packages/qwik/src/core/util/markers.ts | 2 +- .../qwik/src/optimizer/src/plugins/vite.ts | 47 +++-- .../src/optimizer/src/plugins/vite.unit.ts | 12 +- packages/qwik/src/server/api.md | 2 + .../src/server/prefetch-implementation.ts | 71 ++++--- packages/qwik/src/server/prefetch-strategy.ts | 20 +- packages/qwik/src/server/prefetch-utils.ts | 16 +- .../qwik/src/server/prefetch-utils.unit.ts | 50 ++++- packages/qwik/src/server/render.ts | 1 + packages/qwik/src/server/types.ts | 17 +- starters/e2e/e2e.containers.spec.ts | 16 +- 17 files changed, 395 insertions(+), 76 deletions(-) create mode 100644 .changeset/fair-cars-fry.md create mode 100644 packages/qwik/src/core/qrl/preload.ts diff --git a/.changeset/fair-cars-fry.md b/.changeset/fair-cars-fry.md new file mode 100644 index 00000000000..eb9e9fac6ad --- /dev/null +++ b/.changeset/fair-cars-fry.md @@ -0,0 +1,6 @@ +--- +'@builder.io/qwik': minor +--- + +PERF: Prefetching now happens dynamically without service worker if the prefetchImplementation is set to "html-append" (default). +PERF: Initial prefetching now includes dynamic imports, which improves initial click delay. diff --git a/packages/docs/src/root.tsx b/packages/docs/src/root.tsx index 1935489e3fa..e3c16689546 100644 --- a/packages/docs/src/root.tsx +++ b/packages/docs/src/root.tsx @@ -1,11 +1,11 @@ import { component$, useContextProvider, useStore } from '@builder.io/qwik'; import { QwikCityProvider, RouterOutlet, ServiceWorkerRegister } from '@builder.io/qwik-city'; +import { Insights } from '@builder.io/qwik-labs'; import RealMetricsOptimization from './components/real-metrics-optimization/real-metrics-optimization'; import { RouterHead } from './components/router-head/router-head'; +import { BUILDER_PUBLIC_API_KEY } from './constants'; import { GlobalStore, type SiteStore } from './context'; import './global.css'; -import { BUILDER_PUBLIC_API_KEY } from './constants'; -import { Insights } from '@builder.io/qwik-labs'; export const uwu = /*javascript*/ ` ;(function () { diff --git a/packages/docs/src/routes/api/qwik-server/api.json b/packages/docs/src/routes/api/qwik-server/api.json index 0d267468458..75fce1bfecc 100644 --- a/packages/docs/src/routes/api/qwik-server/api.json +++ b/packages/docs/src/routes/api/qwik-server/api.json @@ -82,7 +82,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface PrefetchImplementation \n```\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[linkFetchPriority?](#)\n\n\n\n\n\n\n\n'auto' \\| 'low' \\| 'high' \\| null\n\n\n\n\n_(Optional)_ Value of the `` attribute when link is used. Defaults to `null` if links are inserted.\n\n\n
\n\n[linkInsert?](#)\n\n\n\n\n\n\n\n'js-append' \\| 'html-append' \\| null\n\n\n\n\n_(Optional)_ `js-append`: Use JS runtime to create each `` and append to the body.\n\n`html-append`: Render each `` within html, appended at the end of the body.\n\n\n
\n\n[linkRel?](#)\n\n\n\n\n\n\n\n'prefetch' \\| 'preload' \\| 'modulepreload' \\| null\n\n\n\n\n_(Optional)_ Value of the `` attribute when link is used. Defaults to `prefetch` if links are inserted.\n\n\n
\n\n[prefetchEvent?](#)\n\n\n\n\n\n\n\n'always' \\| null\n\n\n\n\n_(Optional)_ Dispatch a `qprefetch` event with detail data containing the bundles that should be prefetched. The event dispatch script will be inlined into the document's HTML so any listeners of this event should already be ready to handle the event.\n\nThis implementation will inject a script similar to:\n\n```\n\n```\nBy default, the `prefetchEvent` implementation will be set to `always`.\n\n\n
\n\n[workerFetchInsert?](#)\n\n\n\n\n\n\n\n'always' \\| 'no-link-support' \\| null\n\n\n\n\n_(Optional)_ `always`: Always include the worker fetch JS runtime.\n\n`no-link-support`: Only include the worker fetch JS runtime when the browser doesn't support `` prefetch/preload/modulepreload.\n\n\n
", + "content": "```typescript\nexport interface PrefetchImplementation \n```\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[linkFetchPriority?](#)\n\n\n\n\n\n\n\n'auto' \\| 'low' \\| 'high' \\| null\n\n\n\n\n_(Optional)_ Value of the `` attribute when link is used. Defaults to `null`.\n\n\n
\n\n[linkInsert?](#)\n\n\n\n\n\n\n\n'js-append' \\| 'html-append' \\| null\n\n\n\n\n_(Optional)_ `js-append`: Use JS runtime to create each `` and append to the body.\n\n`html-append`: Render each `` within html, appended at the end of the body.\n\nDefaults to `html-append`.\n\n\n
\n\n[linkRel?](#)\n\n\n\n\n\n\n\n'prefetch' \\| 'preload' \\| 'modulepreload' \\| null\n\n\n\n\n_(Optional)_ Value of the `` attribute when link is used. Defaults to `modulepreload`.\n\n\n
\n\n[prefetchEvent?](#)\n\n\n\n\n\n\n\n'always' \\| null\n\n\n\n\n_(Optional)_ Dispatch a `qprefetch` event with detail data containing the bundles that should be prefetched. The event dispatch script will be inlined into the document's HTML so any listeners of this event should already be ready to handle the event.\n\nThis implementation will inject a script similar to:\n\n```\n\n```\nBy default, the `prefetchEvent` implementation will be set to `null`.\n\n\n
\n\n[workerFetchInsert?](#)\n\n\n\n\n\n\n\n'always' \\| 'no-link-support' \\| null\n\n\n\n\n_(Optional)_ `always`: Always include the worker fetch JS runtime.\n\n`no-link-support`: Only include the worker fetch JS runtime when the browser doesn't support `` prefetch/preload/modulepreload.\n\nDefaults to `null`.\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/server/types.ts", "mdFile": "qwik.prefetchimplementation.md" }, @@ -96,7 +96,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface PrefetchResource \n```\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[imports](#)\n\n\n\n\n\n\n\n[PrefetchResource](#prefetchresource)\\[\\]\n\n\n\n\n\n
\n\n[url](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
", + "content": "```typescript\nexport interface PrefetchResource \n```\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[imports](#)\n\n\n\n\n\n\n\n[PrefetchResource](#prefetchresource)\\[\\]\n\n\n\n\n\n
\n\n[priority](#)\n\n\n\n\n\n\n\nboolean\n\n\n\n\n\n
\n\n[url](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/server/types.ts", "mdFile": "qwik.prefetchresource.md" }, diff --git a/packages/docs/src/routes/api/qwik-server/index.md b/packages/docs/src/routes/api/qwik-server/index.md index 819658c38ec..12ad8ba55dc 100644 --- a/packages/docs/src/routes/api/qwik-server/index.md +++ b/packages/docs/src/routes/api/qwik-server/index.md @@ -243,7 +243,7 @@ Description -_(Optional)_ Value of the `` attribute when link is used. Defaults to `null` if links are inserted. +_(Optional)_ Value of the `` attribute when link is used. Defaults to `null`. @@ -262,6 +262,8 @@ _(Optional)_ `js-append`: Use JS runtime to create each `` and append to t `html-append`: Render each `` within html, appended at the end of the body. +Defaults to `html-append`. + @@ -275,7 +277,7 @@ _(Optional)_ `js-append`: Use JS runtime to create each `` and append to t -_(Optional)_ Value of the `` attribute when link is used. Defaults to `prefetch` if links are inserted. +_(Optional)_ Value of the `` attribute when link is used. Defaults to `modulepreload`. @@ -300,7 +302,7 @@ This implementation will inject a script similar to: ``` -By default, the `prefetchEvent` implementation will be set to `always`. +By default, the `prefetchEvent` implementation will be set to `null`. @@ -319,6 +321,8 @@ _(Optional)_ `always`: Always include the worker fetch JS runtime. `no-link-support`: Only include the worker fetch JS runtime when the browser doesn't support `` prefetch/preload/modulepreload. +Defaults to `null`. + @@ -362,6 +366,19 @@ Description +[priority](#) + + + + + +boolean + + + + + + [url](#) diff --git a/packages/qwik/src/core/qrl/preload.ts b/packages/qwik/src/core/qrl/preload.ts new file mode 100644 index 00000000000..1b81e05a584 --- /dev/null +++ b/packages/qwik/src/core/qrl/preload.ts @@ -0,0 +1,174 @@ +/** + * Here we handle preloading of chunks. + * + * Given a symbol hash (in fact any string), we can find all the chunks that it depends on, via the + * bundle graph. We then generate preload link tags for each of those chunks. + * + * The priority is set to high for direct imports and low for indirect imports. + * + * There are several parts to this: + * + * - Load the bundle graph from the preload link tag that was injected during SSR + * - Given a string, find all the chunks that it depends on + * - Generate the preload link tags if needed + */ + +import { isDev } from '@builder.io/qwik/build'; +import type { QwikBundleGraph } from '../../optimizer/src/types'; +import { QBaseAttr, QManifestHash } from '../util/markers'; + +import { QContainerSelector } from '../util/markers'; + +let bundlesP: Promise | undefined; +enum BundleImportState { + None, + Low, + Loading, +} +type BundleImport = { + $url$: string | null; + $state$: BundleImportState; + $imports$: string[]; + $dynamicImports$: string[]; +}; +let bundles: Map | undefined; +type WantedBundle = { + name: string; + priority: boolean; +}; +const wantedBundles: Set = new Set(); + +const parseBundleGraph = (text: string, base: string) => { + try { + const graph = JSON.parse(text) as QwikBundleGraph; + bundles ||= new Map(); + let i = 0; + while (i < graph.length) { + const name = graph[i++] as string; + const url = name.endsWith('.js') ? `${base}${name}` : null; + const imports: string[] = []; + const dynamicImports: string[] = []; + let idx: number | string; + let collection = imports; + while (((idx = graph[i]), typeof idx === 'number')) { + if (idx === -1) { + collection = dynamicImports; + } else { + collection.push(graph[idx] as string); + } + i++; + } + bundles.set(name, { + $url$: url, + $state$: BundleImportState.None, + $imports$: imports, + $dynamicImports$: dynamicImports, + }); + } + for (const { name, priority } of wantedBundles) { + preload(name, priority); + } + wantedBundles.clear(); + } catch (e) { + console.error('Error parsing bundle graph', e, text); + throw e; + } +}; + +export const loadBundleGraph = (element: Element) => { + if (typeof window === 'undefined' || bundlesP) { + return; + } + const container = element.closest(QContainerSelector); + if (!container) { + return; + } + const hash = container.getAttribute(QManifestHash); + const base = container.getAttribute(QBaseAttr) || '/'; + const link = hash && (container.querySelector(`link#qwik-bg-${hash}`) as HTMLLinkElement | null); + if (!link) { + bundlesP = Promise.reject('No preload link found'); + // prevent uncaught promise rejection + bundlesP.catch(() => {}); + return; + } + bundlesP = fetch(link.href) + .then((res) => res.text()) + .then((text) => parseBundleGraph(text, base)) + .catch((e) => { + console.error('Error loading bundle graph, retrying later', e); + setTimeout(() => { + bundlesP = undefined; + }, 60000); + }); +}; + +let canModulePreload: boolean | null = null; +const makePreloadLink = (bundle: BundleImport, priority: boolean) => { + const link = document.createElement('link'); + if (canModulePreload === null) { + if (link.relList.supports('modulepreload')) { + canModulePreload = true; + } else { + canModulePreload = false; + } + } + link.rel = canModulePreload ? 'modulepreload' : 'preload'; + link.href = bundle.$url$!; + link.fetchPriority = priority ? 'high' : 'low'; + if (!canModulePreload) { + link.as = 'script'; + } + document.head.appendChild(link); +}; + +const prioritizeLink = (url: string) => { + const link = document.querySelector(`link[href="${url}"]`) as HTMLLinkElement | null; + if (link) { + link.fetchPriority = 'high'; + } else { + console.warn(`Preload link ${url} not found`); + } +}; + +const preloadBundle = (bundle: BundleImport, priority: boolean) => { + if (bundle.$state$ >= BundleImportState.Loading) { + return; + } + if (bundle.$url$) { + if (bundle.$state$ === BundleImportState.None) { + makePreloadLink(bundle, priority); + } else if (priority && bundle.$state$ === BundleImportState.Low) { + prioritizeLink(bundle.$url$!); + } else { + return; + } + } + bundle.$state$ = priority ? BundleImportState.Loading : BundleImportState.Low; +}; + +export const preload = (name: string, priority: boolean) => { + if (!bundles) { + wantedBundles.add({ name, priority }); + return; + } + const bundle = bundles.get(name); + if (!bundle) { + isDev && console.warn(`Bundle ${name} not found`); + return; + } + const isReprioritize = priority && bundle.$state$ === BundleImportState.Low; + if (bundle.$state$ !== BundleImportState.None && !isReprioritize) { + // prevent loops + return; + } + preloadBundle(bundle, priority); + for (const importName of bundle.$imports$) { + preload(importName, priority); + } + if (!isReprioritize) { + for (const importName of bundle.$dynamicImports$) { + preload(importName, false); + } + } +}; diff --git a/packages/qwik/src/core/qrl/qrl-class.ts b/packages/qwik/src/core/qrl/qrl-class.ts index 228226dd8d8..7314baee78e 100644 --- a/packages/qwik/src/core/qrl/qrl-class.ts +++ b/packages/qwik/src/core/qrl/qrl-class.ts @@ -15,6 +15,7 @@ import { getQFuncs, QInstance } from '../util/markers'; import { isPromise, maybeThen } from '../util/promises'; import { qDev, qSerialize, qTest, seal } from '../util/qdev'; import { isArray, isFunction, type ValueOrPromise } from '../util/types'; +import { loadBundleGraph, preload } from './preload'; import type { QRLDev } from './qrl'; import type { QRL, QrlArgs, QrlReturn } from './qrl.public'; @@ -89,6 +90,10 @@ export const createQRL = ( if (!_containerEl) { _containerEl = el; } + // try every time just in case + if (el) { + loadBundleGraph(el); + } return _containerEl; }; @@ -221,6 +226,7 @@ export const createQRL = ( if (qDev) { seal(qrl); } + preload(hash, true); return qrl; }; diff --git a/packages/qwik/src/core/util/markers.ts b/packages/qwik/src/core/util/markers.ts index 13f44709d5b..187b27ee1e8 100644 --- a/packages/qwik/src/core/util/markers.ts +++ b/packages/qwik/src/core/util/markers.ts @@ -29,7 +29,7 @@ export const getQFuncs = (document: Document, hash: string): Function[] => { export const QLocaleAttr = 'q:locale'; export const QContainerAttr = 'q:container'; - +export const QBaseAttr = 'q:base'; export const QContainerSelector = '[q\\:container]'; export const ResourceEvent = 'qResource'; diff --git a/packages/qwik/src/optimizer/src/plugins/vite.ts b/packages/qwik/src/optimizer/src/plugins/vite.ts index 2e4f00d525d..8be04927970 100644 --- a/packages/qwik/src/optimizer/src/plugins/vite.ts +++ b/packages/qwik/src/optimizer/src/plugins/vite.ts @@ -1140,6 +1140,20 @@ function absolutePathAwareJoin(path: Path, ...segments: string[]): string { return path.join(...segments); } +const dynamicTag = ''; +/** + * This creates a compact array of dependencies for each bundle. It also contains the symbols. The + * format is: + * + * ``` + * [...(bundleName: string, ...directImports: index[], ...dynamicImports: [-1, ...index[]] | [])] + * ``` + * + * (index is the position of the dependency in the bundleGraph array) + * + * This format allows any string to denote a set of dependencies, which is useful for symbols and + * SPA paths. + */ export function convertManifestToBundleGraph(manifest: QwikManifest): QwikBundleGraph { const bundleGraph: QwikBundleGraph = []; const graph = manifest.bundles; @@ -1175,20 +1189,12 @@ export function convertManifestToBundleGraph(manifest: QwikManifest): QwikBundle } clearTransitiveDeps(deps, new Set(), depName); } - let didAdd = false; - for (const depName of bundle.dynamicImports || []) { - // If we dynamically import a qrl segment that is not a handler, we'll probably need it soon - const dep = graph[depName]; - if (!graph[depName]) { - // external dependency - continue; - } - // prevent importing all segments chunks - if (dep.hasSymbols) { - if (!didAdd) { - deps.add(''); - didAdd = true; - } + const internalDynamicImports = bundle.dynamicImports?.filter((d) => graph[d]) || []; + // If we have a lot of dynamic imports, we don't know which ones are needed, so we don't add any + // This can happen with registry bundles like for routing + if (internalDynamicImports.length > 0 && internalDynamicImports.length < 10) { + deps.add(dynamicTag); + for (const depName of internalDynamicImports) { deps.add(depName); } } @@ -1198,6 +1204,17 @@ export function convertManifestToBundleGraph(manifest: QwikManifest): QwikBundle bundleGraph.push(null!); } } + // Add the symbols to the bundle graph + for (const [symbol, chunkname] of Object.entries(manifest.mapping)) { + const bundle = map.get(chunkname); + if (!bundle) { + console.warn(`Chunk ${chunkname} for symbol ${symbol} not found in the bundle graph.`); + } else { + const idx = symbol.lastIndexOf('_'); + const hash = idx === -1 ? symbol : symbol.slice(idx + 1); + bundleGraph.push(hash, bundle.index); + } + } // Second pass to to update dependency pointers for (const bundleName of names) { const bundle = map.get(bundleName); @@ -1209,7 +1226,7 @@ export function convertManifestToBundleGraph(manifest: QwikManifest): QwikBundle let { index, deps } = bundle; index++; for (const depName of deps) { - if (depName === '') { + if (depName === dynamicTag) { bundleGraph[index++] = -1; continue; } diff --git a/packages/qwik/src/optimizer/src/plugins/vite.unit.ts b/packages/qwik/src/optimizer/src/plugins/vite.unit.ts index f75d71ede43..e393654709c 100644 --- a/packages/qwik/src/optimizer/src/plugins/vite.unit.ts +++ b/packages/qwik/src/optimizer/src/plugins/vite.unit.ts @@ -516,7 +516,17 @@ suite('convertManifestToBundleGraph', () => { size: 0, }, } as Record, + mapping: {}, } as QwikManifest; - expect(convertManifestToBundleGraph(manifest)).toEqual(['a.js', 2, 'b.js', 'c.js']); + expect(convertManifestToBundleGraph(manifest)).toEqual([ + 'a.js', + 4, + -1, + 7, + 'b.js', + -1, + 7, + 'c.js', + ]); }); }); diff --git a/packages/qwik/src/server/api.md b/packages/qwik/src/server/api.md index 4e0a9fbb0a3..9f4bcb9c34b 100644 --- a/packages/qwik/src/server/api.md +++ b/packages/qwik/src/server/api.md @@ -55,6 +55,8 @@ export interface PrefetchResource { // (undocumented) imports: PrefetchResource[]; // (undocumented) + priority: boolean; + // (undocumented) url: string; } diff --git a/packages/qwik/src/server/prefetch-implementation.ts b/packages/qwik/src/server/prefetch-implementation.ts index ac401c10405..ad2b66e7380 100644 --- a/packages/qwik/src/server/prefetch-implementation.ts +++ b/packages/qwik/src/server/prefetch-implementation.ts @@ -9,6 +9,7 @@ import type { PrefetchImplementation, PrefetchResource, PrefetchStrategy } from export function applyPrefetchImplementation( base: string, + manifestHash: string | undefined, prefetchStrategy: PrefetchStrategy | undefined, prefetchResources: PrefetchResource[], nonce?: string @@ -24,7 +25,14 @@ export function applyPrefetchImplementation( } if (prefetchImpl.linkInsert === 'html-append') { - linkHtmlImplementation(prefetchNodes, prefetchResources, prefetchImpl); + linkHtmlImplementation( + base, + manifestHash, + nonce, + prefetchNodes, + prefetchResources, + prefetchImpl + ); } if (prefetchImpl.linkInsert === 'js-append') { @@ -67,30 +75,45 @@ function prefetchUrlsEvent( ); } -/** Creates the `` within the rendered html. Optionally add the JS worker fetch */ +/** Creates the `` within the rendered html */ function linkHtmlImplementation( + base: string, + manifestHash: string | undefined, + nonce: string | undefined, prefetchNodes: JSXNode[], prefetchResources: PrefetchResource[], prefetchImpl: Required ) { const urls = flattenPrefetchResources(prefetchResources); - const rel = prefetchImpl.linkRel || 'prefetch'; - const priority = prefetchImpl.linkFetchPriority; + const rel = prefetchImpl.linkRel || 'modulepreload'; - for (const url of urls) { - const attributes: Record = {}; - attributes['href'] = url; - attributes['rel'] = rel; - if (priority) { - attributes['fetchpriority'] = priority; - } - if (rel === 'prefetch' || rel === 'preload') { - if (url.endsWith('.js')) { - attributes['as'] = 'script'; - } - } - - prefetchNodes.push(jsx('link', attributes, undefined)); + if (manifestHash) { + prefetchNodes.push( + jsx('link', { + rel: 'fetch', + id: `qwik-bg-${manifestHash}`, + href: `${base}q-bundle-graph-${manifestHash}.json`, + as: 'fetch', + crossorigin: 'anonymous', + priority: prefetchImpl.linkFetchPriority || undefined, + }) + ); + } + for (const [url, priority] of urls) { + const fetchpriority = priority + ? prefetchImpl.linkFetchPriority || 'high' + : prefetchImpl.linkFetchPriority === 'low' + ? 'low' + : undefined; + prefetchNodes.push( + jsx('link', { + href: url, + rel, + fetchpriority, + nonce, + // TODO: add integrity + }) + ); } } @@ -104,7 +127,7 @@ function linkJsImplementation( prefetchImpl: Required, nonce?: string ) { - const rel = prefetchImpl.linkRel || 'prefetch'; + const rel = prefetchImpl.linkRel || 'modulepreload'; const priority = prefetchImpl.linkFetchPriority; let s = ``; @@ -112,7 +135,7 @@ function linkJsImplementation( s += `let supportsLinkRel = true;`; } - s += `const u=${JSON.stringify(flattenPrefetchResources(prefetchResources))};`; + s += `const u=${JSON.stringify(flattenPrefetchResources(prefetchResources).keys())};`; s += `u.map((u,i)=>{`; s += `const l=document.createElement('link');`; @@ -159,7 +182,7 @@ function workerFetchImplementation( prefetchResources: PrefetchResource[], nonce?: string ) { - let s = `const u=${JSON.stringify(flattenPrefetchResources(prefetchResources))};`; + let s = `const u=${JSON.stringify(flattenPrefetchResources(prefetchResources).keys())};`; s += workerFetchScript(); prefetchNodes.push( @@ -179,9 +202,9 @@ function normalizePrefetchImplementation( } const PrefetchImplementationDefault: Required = { - linkInsert: null, - linkRel: null, + linkInsert: 'html-append', + linkRel: 'modulepreload', linkFetchPriority: null, workerFetchInsert: null, - prefetchEvent: 'always', + prefetchEvent: null, }; diff --git a/packages/qwik/src/server/prefetch-strategy.ts b/packages/qwik/src/server/prefetch-strategy.ts index 3eb15339635..a28d4982191 100644 --- a/packages/qwik/src/server/prefetch-strategy.ts +++ b/packages/qwik/src/server/prefetch-strategy.ts @@ -65,7 +65,7 @@ function getAutoPrefetch( const resolvedSymbol = mapper[qrlSymbolName]; if (resolvedSymbol) { const bundleFileName = resolvedSymbol[1]; - addBundle(manifest, urls, prefetchResources, buildBase, bundleFileName); + addBundle(manifest, urls, prefetchResources, buildBase, bundleFileName, true); } } } @@ -77,7 +77,8 @@ function addBundle( urls: Map, prefetchResources: PrefetchResource[], buildBase: string, - bundleFileName: string + bundleFileName: string, + priority: boolean ) { const url = qDev ? bundleFileName : buildBase + bundleFileName; let prefetchResource = urls.get(url); @@ -85,6 +86,7 @@ function addBundle( prefetchResource = { url, imports: [], + priority, }; urls.set(url, prefetchResource); @@ -92,7 +94,19 @@ function addBundle( if (bundle) { if (Array.isArray(bundle.imports)) { for (const importedFilename of bundle.imports) { - addBundle(manifest, urls, prefetchResource.imports, buildBase, importedFilename); + addBundle( + manifest, + urls, + prefetchResource.imports, + buildBase, + importedFilename, + priority + ); + } + } + if (Array.isArray(bundle.dynamicImports) && bundle.dynamicImports.length < 10) { + for (const importedFilename of bundle.dynamicImports) { + addBundle(manifest, urls, prefetchResource.imports, buildBase, importedFilename, false); } } } diff --git a/packages/qwik/src/server/prefetch-utils.ts b/packages/qwik/src/server/prefetch-utils.ts index d3b2903db96..a4b29220d90 100644 --- a/packages/qwik/src/server/prefetch-utils.ts +++ b/packages/qwik/src/server/prefetch-utils.ts @@ -1,6 +1,7 @@ import type { QPrefetchData } from '../../../qwik-city/src/runtime/src/service-worker/types'; import type { PrefetchResource } from './types'; +// TODO deprecation message and fallback to preload export function workerFetchScript() { const fetch = `Promise.all(e.data.map(u=>fetch(u))).finally(()=>{setTimeout(postMessage({}),9999)})`; @@ -21,7 +22,9 @@ export function workerFetchScript() { export function prefetchUrlsEventScript(base: string, prefetchResources: PrefetchResource[]) { const data: QPrefetchData = { - bundles: flattenPrefetchResources(prefetchResources).map((u) => u.split('/').pop()!), + bundles: Array.from(flattenPrefetchResources(prefetchResources).keys()).map( + (u) => u.split('/').pop()! + ), }; const args = JSON.stringify(['prefetch', base, ...data.bundles!]); @@ -30,13 +33,16 @@ export function prefetchUrlsEventScript(base: string, prefetchResources: Prefetc } export function flattenPrefetchResources(prefetchResources: PrefetchResource[]) { - const urls: string[] = []; + const urls = new Map(); const addPrefetchResource = (prefetchResources: PrefetchResource[]) => { if (Array.isArray(prefetchResources)) { for (const prefetchResource of prefetchResources) { - if (!urls.includes(prefetchResource.url)) { - urls.push(prefetchResource.url); - addPrefetchResource(prefetchResource.imports); + const prev = urls.get(prefetchResource.url); + if (!prev) { + urls.set(prefetchResource.url, prefetchResource.priority); + if (prev === undefined) { + addPrefetchResource(prefetchResource.imports); + } } } } diff --git a/packages/qwik/src/server/prefetch-utils.unit.ts b/packages/qwik/src/server/prefetch-utils.unit.ts index 44f777fb3a9..0e697778a2f 100644 --- a/packages/qwik/src/server/prefetch-utils.unit.ts +++ b/packages/qwik/src/server/prefetch-utils.unit.ts @@ -3,11 +3,18 @@ import { flattenPrefetchResources } from './prefetch-utils'; test('flattenPrefetchResources, no imports', () => { const p = [ - { url: 'a.js', imports: [] }, - { url: 'b.js', imports: [] }, - { url: 'c.js', imports: [] }, + { url: 'a.js', imports: [], priority: false }, + { url: 'b.js', imports: [], priority: true }, + { url: 'c.js', imports: [], priority: false }, ]; - assert.deepEqual(flattenPrefetchResources(p), ['a.js', 'b.js', 'c.js']); + assert.deepEqual( + flattenPrefetchResources(p), + new Map([ + ['a.js', false], + ['b.js', true], + ['c.js', false], + ]) + ); }); test('flattenPrefetchResources, w/ imports', () => { @@ -15,23 +22,46 @@ test('flattenPrefetchResources, w/ imports', () => { { url: 'a.js', imports: [ - { url: 'x.js', imports: [{ url: 'y.js', imports: [] }] }, - { url: 'y.js', imports: [] }, + { url: 'x.js', imports: [{ url: 'y.js', imports: [], priority: false }], priority: false }, + { url: 'y.js', imports: [], priority: false }, ], + priority: false, }, { url: 'b.js', imports: [ - { url: 'x.js', imports: [{ url: 'y.js', imports: [] }] }, - { url: 'y.js', imports: [] }, + { url: 'x.js', imports: [{ url: 'y.js', imports: [], priority: false }], priority: false }, + { url: 'y.js', imports: [], priority: false }, ], + priority: true, }, { url: 'c.js', imports: [ - { url: 'z.js', imports: [{ url: 'x.js', imports: [{ url: 'y.js', imports: [] }] }] }, + { + url: 'z.js', + imports: [ + { + url: 'x.js', + imports: [{ url: 'y.js', imports: [], priority: false }], + priority: false, + }, + ], + priority: false, + }, ], + priority: false, }, ]; - assert.deepEqual(flattenPrefetchResources(p), ['a.js', 'x.js', 'y.js', 'b.js', 'c.js', 'z.js']); + assert.deepEqual( + flattenPrefetchResources(p), + new Map([ + ['a.js', false], + ['x.js', false], + ['y.js', false], + ['b.js', true], + ['c.js', false], + ['z.js', false], + ]) + ); }); diff --git a/packages/qwik/src/server/render.ts b/packages/qwik/src/server/render.ts index d0f34d89127..7801213f372 100644 --- a/packages/qwik/src/server/render.ts +++ b/packages/qwik/src/server/render.ts @@ -186,6 +186,7 @@ export async function renderToStream( if (prefetchResources.length > 0) { const prefetchImpl = applyPrefetchImplementation( base, + resolvedManifest?.manifest.manifestHash, opts.prefetchStrategy, prefetchResources, opts.serverData?.nonce diff --git a/packages/qwik/src/server/types.ts b/packages/qwik/src/server/types.ts index c9158054cb6..b1f756e8afb 100644 --- a/packages/qwik/src/server/types.ts +++ b/packages/qwik/src/server/types.ts @@ -25,23 +25,21 @@ export interface PrefetchImplementation { * `js-append`: Use JS runtime to create each `` and append to the body. * * `html-append`: Render each `` within html, appended at the end of the body. + * + * Defaults to `html-append`. */ linkInsert?: 'js-append' | 'html-append' | null; - /** - * Value of the `` attribute when link is used. Defaults to `prefetch` if links - * are inserted. - */ + /** Value of the `` attribute when link is used. Defaults to `modulepreload`. */ linkRel?: 'prefetch' | 'preload' | 'modulepreload' | null; - /** - * Value of the `` attribute when link is used. Defaults to `null` if - * links are inserted. - */ + /** Value of the `` attribute when link is used. Defaults to `null`. */ linkFetchPriority?: 'auto' | 'low' | 'high' | null; /** * `always`: Always include the worker fetch JS runtime. * * `no-link-support`: Only include the worker fetch JS runtime when the browser doesn't support * `` prefetch/preload/modulepreload. + * + * Defaults to `null`. */ workerFetchInsert?: 'always' | 'no-link-support' | null; /** @@ -57,7 +55,7 @@ export interface PrefetchImplementation { * * ``` * - * By default, the `prefetchEvent` implementation will be set to `always`. + * By default, the `prefetchEvent` implementation will be set to `null`. */ prefetchEvent?: 'always' | null; } @@ -73,6 +71,7 @@ export type SymbolsToPrefetch = 'auto' | ((opts: { manifest: QwikManifest }) => export interface PrefetchResource { url: string; imports: PrefetchResource[]; + priority: boolean; } /** @public */ diff --git a/starters/e2e/e2e.containers.spec.ts b/starters/e2e/e2e.containers.spec.ts index 406da69fb31..18bb708da90 100644 --- a/starters/e2e/e2e.containers.spec.ts +++ b/starters/e2e/e2e.containers.spec.ts @@ -20,7 +20,7 @@ test.describe("container", () => { }); test("should handle inner counter", async ({ page }) => { - const container = page.locator(".inline-container"); + const container = page.locator(".inline-container container"); const anchor = container.locator("a"); await expect(anchor).toHaveText("1 / 1"); @@ -36,4 +36,18 @@ test.describe("container", () => { await anchor.click(); await expect(anchor).toHaveText("2 / 3"); }); + + test("dynamic preload", async ({ page }) => { + const container = page.locator(".inline-container container"); + const hash = await container.getAttribute("q:manifest-hash"); + const bundleLink = page.locator(`link#qwik-bg-${hash}`).first(); + await expect(bundleLink).toHaveAttribute("href"); + + const headPreload = page + .locator(`head > link[rel="modulepreload"]`) + .first(); + await expect(headPreload).not.toBeAttached(); + await container.locator("a").click(); + await expect(headPreload).toBeAttached(); + }); }); From 351e1526c0c2b05b93364a693b177dd68d1660a3 Mon Sep 17 00:00:00 2001 From: Shai Reznik Date: Mon, 20 Jan 2025 12:14:32 +0200 Subject: [PATCH 02/17] chore(router): put routes bundles into the bundle graph Co-authored-by: Wout Mertens --- .../src/routes/api/qwik-optimizer/api.json | 30 ++++- .../src/routes/api/qwik-optimizer/index.md | 40 ++++++ .../buildtime/vite/bundle-graph-modifier.ts | 44 +++++++ .../vite/bundle-graph-modifier.unit.ts | 115 +++++++++++++++++ .../qwik-city/src/buildtime/vite/plugin.ts | 44 ++++--- packages/qwik/src/optimizer/src/api.md | 8 ++ packages/qwik/src/optimizer/src/index.ts | 9 +- .../src/optimizer/src/plugins/bundle-graph.ts | 122 ++++++++++++++++++ .../qwik/src/optimizer/src/plugins/vite.ts | 114 ++-------------- .../src/optimizer/src/plugins/vite.unit.ts | 8 +- 10 files changed, 401 insertions(+), 133 deletions(-) create mode 100644 packages/qwik-city/src/buildtime/vite/bundle-graph-modifier.ts create mode 100644 packages/qwik-city/src/buildtime/vite/bundle-graph-modifier.unit.ts create mode 100644 packages/qwik/src/optimizer/src/plugins/bundle-graph.ts diff --git a/packages/docs/src/routes/api/qwik-optimizer/api.json b/packages/docs/src/routes/api/qwik-optimizer/api.json index 04faea738c8..bb1fe034273 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/api.json +++ b/packages/docs/src/routes/api/qwik-optimizer/api.json @@ -19,6 +19,20 @@ "content": "```typescript\nbasename(path: string, ext?: string): string;\n```\n\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\npath\n\n\n\n\nstring\n\n\n\n\n\n
\n\next\n\n\n\n\nstring\n\n\n\n\n_(Optional)_\n\n\n
\n**Returns:**\n\nstring", "mdFile": "qwik.path.basename.md" }, + { + "name": "BundleGraphModifier", + "id": "bundlegraphmodifier", + "hierarchy": [ + { + "name": "BundleGraphModifier", + "id": "bundlegraphmodifier" + } + ], + "kind": "TypeAlias", + "content": "A function that creates a modified version of the bundle graph. Used to inject routes and their dependencies into the bundle graph.\n\n\n```typescript\nexport type BundleGraphModifier = (graph: QwikBundleGraph, manifest: QwikManifest) => QwikBundleGraph;\n```\n**References:** [QwikBundleGraph](#qwikbundlegraph), [QwikManifest](#qwikmanifest)", + "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts", + "mdFile": "qwik.bundlegraphmodifier.md" + }, { "name": "ComponentEntryStrategy", "id": "componententrystrategy", @@ -410,6 +424,20 @@ "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts", "mdFile": "qwik.qwikbundle.md" }, + { + "name": "QwikBundleGraph", + "id": "qwikbundlegraph", + "hierarchy": [ + { + "name": "QwikBundleGraph", + "id": "qwikbundlegraph" + } + ], + "kind": "TypeAlias", + "content": "Bundle graph.\n\nFormat: \\[ 'bundle-a.js', 3, 5 // Depends on 'bundle-b.js' and 'bundle-c.js' 'bundle-b.js', 5, // Depends on 'bundle-c.js' 'bundle-c.js', \\]\n\n\n```typescript\nexport type QwikBundleGraph = Array;\n```", + "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts", + "mdFile": "qwik.qwikbundlegraph.md" + }, { "name": "QwikManifest", "id": "qwikmanifest", @@ -518,7 +546,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface QwikVitePluginApi \n```\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[getAssetsDir](#)\n\n\n\n\n\n\n\n() => string \\| undefined\n\n\n\n\n\n
\n\n[getClientOutDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[getClientPublicOutDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[getInsightsManifest](#)\n\n\n\n\n\n\n\n(clientOutDir?: string \\| null) => Promise<[InsightManifest](#insightmanifest) \\| null>\n\n\n\n\n\n
\n\n[getManifest](#)\n\n\n\n\n\n\n\n() => [QwikManifest](#qwikmanifest) \\| null\n\n\n\n\n\n
\n\n[getOptimizer](#)\n\n\n\n\n\n\n\n() => [Optimizer](#optimizer) \\| null\n\n\n\n\n\n
\n\n[getOptions](#)\n\n\n\n\n\n\n\n() => NormalizedQwikPluginOptions\n\n\n\n\n\n
\n\n[getRootDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
", + "content": "```typescript\nexport interface QwikVitePluginApi \n```\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[getAssetsDir](#)\n\n\n\n\n\n\n\n() => string \\| undefined\n\n\n\n\n\n
\n\n[getClientOutDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[getClientPublicOutDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[getInsightsManifest](#)\n\n\n\n\n\n\n\n(clientOutDir?: string \\| null) => Promise<[InsightManifest](#insightmanifest) \\| null>\n\n\n\n\n\n
\n\n[getManifest](#)\n\n\n\n\n\n\n\n() => [QwikManifest](#qwikmanifest) \\| null\n\n\n\n\n\n
\n\n[getOptimizer](#)\n\n\n\n\n\n\n\n() => [Optimizer](#optimizer) \\| null\n\n\n\n\n\n
\n\n[getOptions](#)\n\n\n\n\n\n\n\n() => NormalizedQwikPluginOptions\n\n\n\n\n\n
\n\n[getRootDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[registerBundleGraphModifier](#)\n\n\n\n\n\n\n\n(modifier: [BundleGraphModifier](#bundlegraphmodifier)) => void\n\n\n\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/plugins/vite.ts", "mdFile": "qwik.qwikvitepluginapi.md" }, diff --git a/packages/docs/src/routes/api/qwik-optimizer/index.md b/packages/docs/src/routes/api/qwik-optimizer/index.md index 1664f10545f..29fbf41c061 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/index.md +++ b/packages/docs/src/routes/api/qwik-optimizer/index.md @@ -52,6 +52,21 @@ _(Optional)_ string +## BundleGraphModifier + +A function that creates a modified version of the bundle graph. Used to inject routes and their dependencies into the bundle graph. + +```typescript +export type BundleGraphModifier = ( + graph: QwikBundleGraph, + manifest: QwikManifest, +) => QwikBundleGraph; +``` + +**References:** [QwikBundleGraph](#qwikbundlegraph), [QwikManifest](#qwikmanifest) + +[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts) + ## ComponentEntryStrategy ```typescript @@ -1395,6 +1410,18 @@ _(Optional)_ [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts) +## QwikBundleGraph + +Bundle graph. + +Format: [ 'bundle-a.js', 3, 5 // Depends on 'bundle-b.js' and 'bundle-c.js' 'bundle-b.js', 5, // Depends on 'bundle-c.js' 'bundle-c.js', ] + +```typescript +export type QwikBundleGraph = Array; +``` + +[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts) + ## QwikManifest The metadata of the build. One of its uses is storing where QRL symbols are located. @@ -2220,6 +2247,19 @@ Description + + + +[registerBundleGraphModifier](#) + + + + + +(modifier: [BundleGraphModifier](#bundlegraphmodifier)) => void + + + diff --git a/packages/qwik-city/src/buildtime/vite/bundle-graph-modifier.ts b/packages/qwik-city/src/buildtime/vite/bundle-graph-modifier.ts new file mode 100644 index 00000000000..53acb657f0d --- /dev/null +++ b/packages/qwik-city/src/buildtime/vite/bundle-graph-modifier.ts @@ -0,0 +1,44 @@ +import type { QwikBundle, QwikBundleGraph, QwikManifest } from '@builder.io/qwik/optimizer'; +import { removeExtension } from '../../utils/fs'; +import type { BuildRoute } from '../types'; + +export function modifyBundleGraph( + routes: BuildRoute[], + originalGraph: QwikBundleGraph, + manifest: QwikManifest +) { + const graph = [...originalGraph]; + + routes.forEach((route) => { + const routePath = removeExtension(route.filePath); + const layoutPaths = route.layouts + ? route.layouts.map((layout) => removeExtension(layout.filePath)) + : []; + const routeAndLayoutPaths = [routePath, ...layoutPaths]; + + const routeDeps = []; + + for (const [bundleName, bundle] of Object.entries(manifest.bundles)) { + if (isBundlePartOfRoute(bundle, routeAndLayoutPaths)) { + const bundleIndex = originalGraph.indexOf(bundleName); + if (bundleIndex !== -1) { + routeDeps.push(bundleIndex); + } + } + } + if (routeDeps.length > 0) { + graph.push(route.routeName, ...routeDeps); + } + }); + return graph; +} + +function isBundlePartOfRoute(bundle: QwikBundle, routeAndLayoutPaths: string[]) { + if (!bundle.origins) { + return false; + } + for (const bundleOrigin of bundle.origins) { + const originPath = removeExtension(bundleOrigin); + return routeAndLayoutPaths.some((path) => path.endsWith(originPath)); + } +} diff --git a/packages/qwik-city/src/buildtime/vite/bundle-graph-modifier.unit.ts b/packages/qwik-city/src/buildtime/vite/bundle-graph-modifier.unit.ts new file mode 100644 index 00000000000..76fa855bf4f --- /dev/null +++ b/packages/qwik-city/src/buildtime/vite/bundle-graph-modifier.unit.ts @@ -0,0 +1,115 @@ +import { + type QwikBundle, + type QwikBundleGraph, + type QwikManifest, +} from '@builder.io/qwik/optimizer'; +import { describe, expect, test } from 'vitest'; +import type { BuildLayout, BuildRoute } from '../types'; +import { modifyBundleGraph } from './bundle-graph-modifier'; + +describe('modifyBundleGraph', () => { + test(`GIVEN 2 routes, one with a layout + AND a manifest with 3 bundles + THEN the bundle graph should contain the routes and their dependencies`, () => { + const fakeManifest = { + bundles: { + 'fake-bundle1.js': { + size: 0, + imports: ['fake-bundle-static-dep.js'], + origins: ['src/routes/index.tsx'], + }, + 'fake-bundle-static-dep.js': { + size: 0, + dynamicImports: ['fake-bundle-dynamic-dep.js'], + }, + 'fake-bundle-dynamic-dep.js': { + size: 0, + }, + 'fake-bundle-part-of-sub-route.js': { + size: 0, + origins: ['src/routes/subroute/index.tsx', 'src/some/other/component.tsx'], + }, + 'fake-bundle-part-of-layout.js': { + size: 0, + origins: ['src/routes/layout.tsx'], + }, + } as Record, + } as QwikManifest; + + const fakeBundleGraph: QwikBundleGraph = [ + 'fake-bundle1.js', + 2, + 'fake-bundle-static-dep.js', + -1, + 4, + 'fake-bundle-dynamic-dep.js', + 'fake-bundle-part-of-sub-route.js', + 'fake-bundle-part-of-layout.js', + ]; + + const fakeRoutes: BuildRoute[] = [ + { + routeName: '/', + filePath: '/home/qwik-app/src/routes/index.tsx', + }, + { + routeName: '/subroute', + filePath: '/home/qwik-app/src/routes/subroute/index.tsx', + layouts: [ + { + filePath: '/home/qwik-app/src/routes/layout.tsx', + }, + ] as BuildLayout[], + }, + ] as BuildRoute[]; + + const actualResult = modifyBundleGraph(fakeRoutes, fakeBundleGraph, fakeManifest); + + const expectedResult: QwikBundleGraph = [ + ...fakeBundleGraph, + '/', + 0, // fake-bundle1.js + '/subroute', + 6, // fake-bundle-part-of-sub-route.js + 7, // fake-bundle-part-of-layout.js + ]; + + expect(actualResult).toEqual(expectedResult); + }); + + test(`GIVEN a mismatch between the bundle graph and the manifest + THEN the resulted bundle graph routes should not contain -1 (not found) indices `, () => { + const fakeManifest = { + bundles: { + 'fake-bundle1.js': { + size: 0, + origins: ['src/routes/index.tsx'], + }, + // 👇 doesn't exist in the bundle graph for some reason + 'fake-bundle2.js': { + size: 0, + origins: ['src/routes/index.tsx'], + }, + } as Record, + } as QwikManifest; + + const fakeBundleGraph: QwikBundleGraph = ['fake-bundle1.js']; + + const fakeRoutes: BuildRoute[] = [ + { + routeName: '/', + filePath: '/home/qwik-app/src/routes/index.tsx', + }, + ] as BuildRoute[]; + + const actualResult = modifyBundleGraph(fakeRoutes, fakeBundleGraph, fakeManifest); + + const expectedResult: QwikBundleGraph = [ + ...fakeBundleGraph, + '/', + 0, // fake-bundle1.js + ]; + + expect(actualResult).toEqual(expectedResult); + }); +}); diff --git a/packages/qwik-city/src/buildtime/vite/plugin.ts b/packages/qwik-city/src/buildtime/vite/plugin.ts index b7ac8dbb904..34574311a8e 100644 --- a/packages/qwik-city/src/buildtime/vite/plugin.ts +++ b/packages/qwik-city/src/buildtime/vite/plugin.ts @@ -1,33 +1,34 @@ +import type { QwikVitePlugin } from '@builder.io/qwik/optimizer'; import swRegister from '@qwik-city-sw-register-build'; -import { createMdxTransformer, type MdxTransform } from '../markdown/mdx'; -import { basename, join, resolve, extname } from 'node:path'; -import type { Plugin, PluginOption, UserConfig, Rollup } from 'vite'; +import fs from 'node:fs'; +import { basename, extname, join, resolve } from 'node:path'; +import type { Plugin, PluginOption, Rollup, UserConfig } from 'vite'; import { loadEnv } from 'vite'; -import { generateQwikCityPlan } from '../runtime-generation/generate-qwik-city-plan'; -import type { BuildContext } from '../types'; -import { createBuildContext, resetBuildContext } from '../context'; +import { + NOT_FOUND_PATHS_ID, + RESOLVED_NOT_FOUND_PATHS_ID, + RESOLVED_STATIC_PATHS_ID, + STATIC_PATHS_ID, +} from '../../adapters/shared/vite'; +import { postBuild } from '../../adapters/shared/vite/post-build'; +import { patchGlobalThis } from '../../middleware/node/node-fetch'; import { isMenuFileName, normalizePath, removeExtension } from '../../utils/fs'; -import { validatePlugin } from './validate-plugin'; -import type { QwikCityPluginApi, QwikCityVitePluginOptions } from './types'; import { build } from '../build'; -import { ssrDevMiddleware, staticDistMiddleware } from './dev-server'; +import { createBuildContext, resetBuildContext } from '../context'; +import { createMdxTransformer, type MdxTransform } from '../markdown/mdx'; import { transformMenu } from '../markdown/menu'; import { generateQwikCityEntries } from '../runtime-generation/generate-entries'; -import { patchGlobalThis } from '../../middleware/node/node-fetch'; -import type { QwikVitePlugin } from '@builder.io/qwik/optimizer'; -import fs from 'node:fs'; +import { generateQwikCityPlan } from '../runtime-generation/generate-qwik-city-plan'; import { generateServiceWorkerRegister, prependManifestToServiceWorker, } from '../runtime-generation/generate-service-worker'; -import { - NOT_FOUND_PATHS_ID, - RESOLVED_NOT_FOUND_PATHS_ID, - RESOLVED_STATIC_PATHS_ID, - STATIC_PATHS_ID, -} from '../../adapters/shared/vite'; -import { postBuild } from '../../adapters/shared/vite/post-build'; +import type { BuildContext } from '../types'; +import { modifyBundleGraph } from './bundle-graph-modifier'; +import { ssrDevMiddleware, staticDistMiddleware } from './dev-server'; import { imagePlugin } from './image-jsx'; +import type { QwikCityPluginApi, QwikCityVitePluginOptions } from './types'; +import { validatePlugin } from './validate-plugin'; /** @public */ export function qwikCity(userOpts?: QwikCityVitePluginOptions): PluginOption[] { @@ -172,6 +173,7 @@ function qwikCityPlugin(userOpts?: QwikCityVitePluginOptions): any { if (isCityPlan || isSwRegister) { if (!ctx.isDevServer && ctx.isDirty) { await build(ctx); + ctx.isDirty = false; ctx.diagnostics.forEach((d) => { this.warn(d.message); @@ -237,6 +239,10 @@ function qwikCityPlugin(userOpts?: QwikCityVitePluginOptions): any { generateBundle(_, bundles) { // client bundles if (ctx?.target === 'client') { + qwikPlugin!.api.registerBundleGraphModifier((graph, manifest) => { + return modifyBundleGraph(ctx!.routes, graph, manifest); + }); + const entries = [...ctx.entries, ...ctx.serviceWorkers].map((entry) => { return { chunkFileName: entry.chunkFileName, diff --git a/packages/qwik/src/optimizer/src/api.md b/packages/qwik/src/optimizer/src/api.md index 0d3f1adc115..0a096142a36 100644 --- a/packages/qwik/src/optimizer/src/api.md +++ b/packages/qwik/src/optimizer/src/api.md @@ -6,6 +6,9 @@ import type { Plugin as Plugin_2 } from 'vite'; +// @public +export type BundleGraphModifier = (graph: QwikBundleGraph, manifest: QwikManifest) => QwikBundleGraph; + // @public (undocumented) export interface ComponentEntryStrategy { // (undocumented) @@ -188,6 +191,9 @@ export interface QwikBundle { symbols?: string[]; } +// @public +export type QwikBundleGraph = Array; + // @public export interface QwikManifest { bundles: { @@ -301,6 +307,8 @@ export interface QwikVitePluginApi { getOptions: () => NormalizedQwikPluginOptions; // (undocumented) getRootDir: () => string | null; + // (undocumented) + registerBundleGraphModifier: (modifier: BundleGraphModifier) => void; } // Warning: (ae-forgotten-export) The symbol "QwikVitePluginCSROptions" needs to be exported by the entry point index.d.ts diff --git a/packages/qwik/src/optimizer/src/index.ts b/packages/qwik/src/optimizer/src/index.ts index e2497947484..278ec62bf81 100644 --- a/packages/qwik/src/optimizer/src/index.ts +++ b/packages/qwik/src/optimizer/src/index.ts @@ -8,9 +8,7 @@ export type { EntryStrategy, GlobalInjections, SegmentAnalysis as HookAnalysis, - SegmentAnalysis, SegmentEntryStrategy as HookEntryStrategy, - SegmentEntryStrategy, InlineEntryStrategy, InsightManifest, MinifyMode, @@ -19,9 +17,12 @@ export type { OptimizerSystem, Path, QwikBundle, + QwikBundleGraph, QwikManifest, QwikSymbol, ResolvedManifest, + SegmentAnalysis, + SegmentEntryStrategy, SingleEntryStrategy, SmartEntryStrategy, SourceLocation, @@ -38,7 +39,7 @@ export type { TranspileOption, } from './types'; -export type { QwikBuildMode, QwikBuildTarget, ExperimentalFeatures } from './plugins/plugin'; +export type { ExperimentalFeatures, QwikBuildMode, QwikBuildTarget } from './plugins/plugin'; export type { QwikRollupPluginOptions } from './plugins/rollup'; export type { QwikViteDevResponse, @@ -47,6 +48,8 @@ export type { QwikVitePluginOptions, } from './plugins/vite'; +export type { BundleGraphModifier } from './plugins/bundle-graph'; + export { qwikRollup } from './plugins/rollup'; export { qwikVite } from './plugins/vite'; export { symbolMapper } from './plugins/vite-dev-server'; diff --git a/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts b/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts new file mode 100644 index 00000000000..3f59051ca76 --- /dev/null +++ b/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts @@ -0,0 +1,122 @@ +import type { QwikBundleGraph, QwikManifest } from '../types'; + +/** + * A function that creates a modified version of the bundle graph. Used to inject routes and their + * dependencies into the bundle graph. + * + * @public + */ +export type BundleGraphModifier = ( + graph: QwikBundleGraph, + manifest: QwikManifest +) => QwikBundleGraph; + +const dynamicTag = ''; +/** + * This creates a compact array of dependencies for each bundle. It also contains the symbols. The + * format is: + * + * ``` + * [...(bundleName: string, ...directImports: index[], ...dynamicImports: [-1, ...index[]] | [])] + * ``` + * + * (index is the position of the dependency in the bundleGraph array) + * + * This format allows any string to denote a set of dependencies, which is useful for symbols and + * SPA paths. + */ +export function convertManifestToBundleGraph( + manifest: QwikManifest, + bundleGraphModifiers?: Set +): QwikBundleGraph { + let bundleGraph: QwikBundleGraph = []; + const graph = manifest.bundles; + if (!graph) { + return []; + } + const names = Object.keys(graph).sort(); + const map = new Map }>(); + const clearTransitiveDeps = (parentDeps: Set, seen: Set, bundleName: string) => { + const bundle = graph[bundleName]; + if (!bundle) { + // external dependency + return; + } + for (const dep of bundle.imports || []) { + if (parentDeps.has(dep)) { + parentDeps.delete(dep); + } + if (!seen.has(dep)) { + seen.add(dep); + clearTransitiveDeps(parentDeps, seen, dep); + } + } + }; + for (const bundleName of names) { + const bundle = graph[bundleName]; + const index = bundleGraph.length; + const deps = new Set(bundle.imports); + for (const depName of deps) { + if (!graph[depName]) { + // external dependency + continue; + } + clearTransitiveDeps(deps, new Set(), depName); + } + const internalDynamicImports = bundle.dynamicImports?.filter((d) => graph[d]) || []; + // If we have a lot of dynamic imports, we don't know which ones are needed, so we don't add any + // This can happen with registry bundles like for routing + if (internalDynamicImports.length > 0 && internalDynamicImports.length < 10) { + deps.add(dynamicTag); + for (const depName of internalDynamicImports) { + deps.add(depName); + } + } + map.set(bundleName, { index, deps }); + bundleGraph.push(bundleName); + while (index + deps.size >= bundleGraph.length) { + bundleGraph.push(null!); + } + } + // Add the symbols to the bundle graph + for (const [symbol, chunkname] of Object.entries(manifest.mapping)) { + const bundle = map.get(chunkname); + if (!bundle) { + console.warn(`Chunk ${chunkname} for symbol ${symbol} not found in the bundle graph.`); + } else { + const idx = symbol.lastIndexOf('_'); + const hash = idx === -1 ? symbol : symbol.slice(idx + 1); + bundleGraph.push(hash, bundle.index); + } + } + // Second pass to to update dependency pointers + for (const bundleName of names) { + const bundle = map.get(bundleName); + if (!bundle) { + console.warn(`Bundle ${bundleName} not found in the bundle graph.`); + continue; + } + // eslint-disable-next-line prefer-const + let { index, deps } = bundle; + index++; + for (const depName of deps) { + if (depName === dynamicTag) { + bundleGraph[index++] = -1; + continue; + } + const dep = map.get(depName); + if (!dep) { + console.warn(`Dependency ${depName} of ${bundleName} not found in the bundle graph.`); + continue; + } + const depIndex = dep.index; + bundleGraph[index++] = depIndex; + } + } + if (bundleGraphModifiers && bundleGraphModifiers.size > 0) { + for (const modifier of bundleGraphModifiers) { + bundleGraph = modifier(bundleGraph, manifest); + } + } + return bundleGraph; +} diff --git a/packages/qwik/src/optimizer/src/plugins/vite.ts b/packages/qwik/src/optimizer/src/plugins/vite.ts index 8be04927970..50bf52eda74 100644 --- a/packages/qwik/src/optimizer/src/plugins/vite.ts +++ b/packages/qwik/src/optimizer/src/plugins/vite.ts @@ -8,11 +8,11 @@ import type { OptimizerOptions, OptimizerSystem, Path, - QwikBundleGraph, QwikManifest, TransformModule, } from '../types'; import { versions } from '../versions'; +import { convertManifestToBundleGraph, type BundleGraphModifier } from './bundle-graph'; import { getImageSizeServer } from './image-size-server'; import { CLIENT_OUT_DIR, @@ -93,6 +93,8 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any { return null; } + const bundleGraphModifiers = new Set(); + const api: QwikVitePluginApi = { getOptimizer: () => qwikPlugin.getOptimizer(), getOptions: () => qwikPlugin.getOptions(), @@ -102,6 +104,8 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any { getClientOutDir: () => clientOutDir, getClientPublicOutDir: () => clientPublicOutDir, getAssetsDir: () => viteAssetsDir, + registerBundleGraphModifier: (modifier: BundleGraphModifier) => + bundleGraphModifiers.add(modifier), }; // We provide two plugins to Vite. The first plugin is the main plugin that handles all the @@ -599,6 +603,9 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any { const assetsDir = qwikPlugin.getOptions().assetsDir || ''; const useAssetsDir = !!assetsDir && assetsDir !== '_astro'; const sys = qwikPlugin.getSys(); + + const bundleGraph = convertManifestToBundleGraph(manifest, bundleGraphModifiers); + this.emitFile({ type: 'asset', fileName: sys.path.join( @@ -606,7 +613,7 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any { 'build', `q-bundle-graph-${manifest.manifestHash}.json` ), - source: JSON.stringify(convertManifestToBundleGraph(manifest)), + source: JSON.stringify(bundleGraph), }); const fs: typeof import('fs') = await sys.dynamicImport('node:fs'); const workerScriptPath = (await this.resolve('@builder.io/qwik/qwik-prefetch.js'))!.id; @@ -1107,6 +1114,7 @@ export interface QwikVitePluginApi { getClientOutDir: () => string | null; getClientPublicOutDir: () => string | null; getAssetsDir: () => string | undefined; + registerBundleGraphModifier: (modifier: BundleGraphModifier) => void; } /** @@ -1139,105 +1147,3 @@ function absolutePathAwareJoin(path: Path, ...segments: string[]): string { } return path.join(...segments); } - -const dynamicTag = ''; -/** - * This creates a compact array of dependencies for each bundle. It also contains the symbols. The - * format is: - * - * ``` - * [...(bundleName: string, ...directImports: index[], ...dynamicImports: [-1, ...index[]] | [])] - * ``` - * - * (index is the position of the dependency in the bundleGraph array) - * - * This format allows any string to denote a set of dependencies, which is useful for symbols and - * SPA paths. - */ -export function convertManifestToBundleGraph(manifest: QwikManifest): QwikBundleGraph { - const bundleGraph: QwikBundleGraph = []; - const graph = manifest.bundles; - if (!graph) { - return []; - } - const names = Object.keys(graph).sort(); - const map = new Map }>(); - const clearTransitiveDeps = (parentDeps: Set, seen: Set, bundleName: string) => { - const bundle = graph[bundleName]; - if (!bundle) { - // external dependency - return; - } - for (const dep of bundle.imports || []) { - if (parentDeps.has(dep)) { - parentDeps.delete(dep); - } - if (!seen.has(dep)) { - seen.add(dep); - clearTransitiveDeps(parentDeps, seen, dep); - } - } - }; - for (const bundleName of names) { - const bundle = graph[bundleName]; - const index = bundleGraph.length; - const deps = new Set(bundle.imports); - for (const depName of deps) { - if (!graph[depName]) { - // external dependency - continue; - } - clearTransitiveDeps(deps, new Set(), depName); - } - const internalDynamicImports = bundle.dynamicImports?.filter((d) => graph[d]) || []; - // If we have a lot of dynamic imports, we don't know which ones are needed, so we don't add any - // This can happen with registry bundles like for routing - if (internalDynamicImports.length > 0 && internalDynamicImports.length < 10) { - deps.add(dynamicTag); - for (const depName of internalDynamicImports) { - deps.add(depName); - } - } - map.set(bundleName, { index, deps }); - bundleGraph.push(bundleName); - while (index + deps.size >= bundleGraph.length) { - bundleGraph.push(null!); - } - } - // Add the symbols to the bundle graph - for (const [symbol, chunkname] of Object.entries(manifest.mapping)) { - const bundle = map.get(chunkname); - if (!bundle) { - console.warn(`Chunk ${chunkname} for symbol ${symbol} not found in the bundle graph.`); - } else { - const idx = symbol.lastIndexOf('_'); - const hash = idx === -1 ? symbol : symbol.slice(idx + 1); - bundleGraph.push(hash, bundle.index); - } - } - // Second pass to to update dependency pointers - for (const bundleName of names) { - const bundle = map.get(bundleName); - if (!bundle) { - console.warn(`Bundle ${bundleName} not found in the bundle graph.`); - continue; - } - // eslint-disable-next-line prefer-const - let { index, deps } = bundle; - index++; - for (const depName of deps) { - if (depName === dynamicTag) { - bundleGraph[index++] = -1; - continue; - } - const dep = map.get(depName); - if (!dep) { - console.warn(`Dependency ${depName} of ${bundleName} not found in the bundle graph.`); - continue; - } - const depIndex = dep.index; - bundleGraph[index++] = depIndex; - } - } - return bundleGraph; -} diff --git a/packages/qwik/src/optimizer/src/plugins/vite.unit.ts b/packages/qwik/src/optimizer/src/plugins/vite.unit.ts index e393654709c..a68dec779d7 100644 --- a/packages/qwik/src/optimizer/src/plugins/vite.unit.ts +++ b/packages/qwik/src/optimizer/src/plugins/vite.unit.ts @@ -3,12 +3,8 @@ import type { Rollup } from 'vite'; import { assert, expect, suite, test } from 'vitest'; import { normalizePath } from '../../../testing/util'; import type { OptimizerOptions, QwikBundle, QwikManifest } from '../types'; -import { - convertManifestToBundleGraph, - qwikVite, - type QwikVitePlugin, - type QwikVitePluginOptions, -} from './vite'; +import { convertManifestToBundleGraph } from './bundle-graph'; +import { qwikVite, type QwikVitePlugin, type QwikVitePluginOptions } from './vite'; const cwd = process.cwd(); From 97affb0df6063ad7c1a395591b267e1af285c5e4 Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Tue, 25 Mar 2025 12:46:44 +0100 Subject: [PATCH 03/17] feat(core): expose preload and remove qprefetch --- .../qwik-city/src/runtime/src/client-navigate.ts | 9 +++------ packages/qwik-city/src/runtime/src/constants.ts | 2 -- packages/qwik/src/core/api.md | 3 +++ packages/qwik/src/core/internal.ts | 1 + packages/qwik/src/core/qrl/preload.ts | 13 ++++++------- packages/qwik/src/core/qrl/qrl.ts | 13 +------------ .../qwik/src/optimizer/src/plugins/bundle-graph.ts | 5 ++--- 7 files changed, 16 insertions(+), 30 deletions(-) diff --git a/packages/qwik-city/src/runtime/src/client-navigate.ts b/packages/qwik-city/src/runtime/src/client-navigate.ts index 20df78fc502..0a5becc227d 100644 --- a/packages/qwik-city/src/runtime/src/client-navigate.ts +++ b/packages/qwik-city/src/runtime/src/client-navigate.ts @@ -1,7 +1,6 @@ -import { isBrowser } from '@builder.io/qwik'; +import { isBrowser, _preload } from '@builder.io/qwik'; import type { NavigationType, ScrollState } from './types'; import { isSamePath, toPath } from './utils'; -import { PREFETCHED_NAVIGATE_PATHS } from './constants'; export const clientNavigate = ( win: Window, @@ -43,9 +42,7 @@ export const newScrollState = (): ScrollState => { export const prefetchSymbols = (path: string) => { if (isBrowser) { path = path.endsWith('/') ? path : path + '/'; - if (!PREFETCHED_NAVIGATE_PATHS.has(path)) { - PREFETCHED_NAVIGATE_PATHS.add(path); - document.dispatchEvent(new CustomEvent('qprefetch', { detail: { links: [path] } })); - } + path = path.length > 1 && path.startsWith('/') ? path.slice(1) : path; + _preload(path, true); } }; diff --git a/packages/qwik-city/src/runtime/src/constants.ts b/packages/qwik-city/src/runtime/src/constants.ts index c54e0a9a517..f5d9842f2cc 100644 --- a/packages/qwik-city/src/runtime/src/constants.ts +++ b/packages/qwik-city/src/runtime/src/constants.ts @@ -4,8 +4,6 @@ export const MODULE_CACHE = /*#__PURE__*/ new WeakMap(); export const CLIENT_DATA_CACHE = new Map>(); -export const PREFETCHED_NAVIGATE_PATHS = new Set(); - export const QACTION_KEY = 'qaction'; export const QFN_KEY = 'qfunc'; diff --git a/packages/qwik/src/core/api.md b/packages/qwik/src/core/api.md index 286a6322f10..280dcf1ecfe 100644 --- a/packages/qwik/src/core/api.md +++ b/packages/qwik/src/core/api.md @@ -624,6 +624,9 @@ export const PrefetchServiceWorker: (opts: { nonce?: string; }) => JSXNode_2<'script'>; +// @internal (undocumented) +export const _preload: (name: string, priority: boolean) => void; + // @public (undocumented) export interface ProgressHTMLAttributes extends Attrs<'progress', T> { } diff --git a/packages/qwik/src/core/internal.ts b/packages/qwik/src/core/internal.ts index cad738b5834..79f426f9fce 100644 --- a/packages/qwik/src/core/internal.ts +++ b/packages/qwik/src/core/internal.ts @@ -16,3 +16,4 @@ export { } from './use/use-core'; export { _jsxQ, _jsxC, _jsxS } from './render/jsx/jsx-runtime'; export { _fnSignal } from './qrl/inlined-fn'; +export { preload as _preload } from './qrl/preload'; diff --git a/packages/qwik/src/core/qrl/preload.ts b/packages/qwik/src/core/qrl/preload.ts index 1b81e05a584..54a33cbd991 100644 --- a/packages/qwik/src/core/qrl/preload.ts +++ b/packages/qwik/src/core/qrl/preload.ts @@ -32,11 +32,8 @@ type BundleImport = { $dynamicImports$: string[]; }; let bundles: Map | undefined; -type WantedBundle = { - name: string; - priority: boolean; -}; -const wantedBundles: Set = new Set(); + +const wantedBundles = new Map(); const parseBundleGraph = (text: string, base: string) => { try { @@ -65,7 +62,7 @@ const parseBundleGraph = (text: string, base: string) => { $dynamicImports$: dynamicImports, }); } - for (const { name, priority } of wantedBundles) { + for (const [name, priority] of wantedBundles) { preload(name, priority); } wantedBundles.clear(); @@ -75,6 +72,7 @@ const parseBundleGraph = (text: string, base: string) => { } }; +/** @internal */ export const loadBundleGraph = (element: Element) => { if (typeof window === 'undefined' || bundlesP) { return; @@ -147,9 +145,10 @@ const preloadBundle = (bundle: BundleImport, priority: boolean) => { bundle.$state$ = priority ? BundleImportState.Loading : BundleImportState.Low; }; +/** @internal */ export const preload = (name: string, priority: boolean) => { if (!bundles) { - wantedBundles.add({ name, priority }); + wantedBundles.set(name, priority || !!wantedBundles.get(name)); return; } const bundle = bundles.get(name); diff --git a/packages/qwik/src/core/qrl/qrl.ts b/packages/qwik/src/core/qrl/qrl.ts index fbb7b00217b..ff8abde1b9b 100644 --- a/packages/qwik/src/core/qrl/qrl.ts +++ b/packages/qwik/src/core/qrl/qrl.ts @@ -1,13 +1,6 @@ import { EMPTY_ARRAY } from '../util/flyweight'; import type { QRL } from './qrl.public'; -import { - assertQrl, - createQRL, - emitEvent, - getSymbolHash, - isSyncQrl, - type QRLInternal, -} from './qrl-class'; +import { assertQrl, createQRL, isSyncQrl, type QRLInternal } from './qrl-class'; import { isFunction, isString } from '../util/types'; import { qError, @@ -96,10 +89,6 @@ export const qrl = ( if (!announcedQRL.has(symbol)) { // Emit event announcedQRL.add(symbol); - emitEvent('qprefetch', { - symbols: [getSymbolHash(symbol)], - bundles: chunk && [chunk], - }); } // Unwrap subscribers diff --git a/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts b/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts index 3f59051ca76..a149872ef68 100644 --- a/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts +++ b/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts @@ -1,3 +1,4 @@ +import { getSymbolHash } from 'packages/qwik/src/core/qrl/qrl-class'; import type { QwikBundleGraph, QwikManifest } from '../types'; /** @@ -84,9 +85,7 @@ export function convertManifestToBundleGraph( if (!bundle) { console.warn(`Chunk ${chunkname} for symbol ${symbol} not found in the bundle graph.`); } else { - const idx = symbol.lastIndexOf('_'); - const hash = idx === -1 ? symbol : symbol.slice(idx + 1); - bundleGraph.push(hash, bundle.index); + bundleGraph.push(getSymbolHash(symbol), bundle.index); } } // Second pass to to update dependency pointers From 111ab2f61fec2c74c3e29a2a994c43c4a10e2de5 Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Tue, 25 Mar 2025 15:14:34 +0100 Subject: [PATCH 04/17] chore: deprecate and unregister service workers --- .changeset/yellow-frogs-repeat.md | 5 + .../docs/src/routes/api/qwik-city/api.json | 2 +- .../docs/src/routes/api/qwik-city/index.md | 7 +- packages/docs/src/routes/api/qwik/api.json | 4 +- packages/docs/src/routes/api/qwik/index.md | 20 +- .../docs/(qwik)/getting-started/index.mdx | 4 +- .../content-security-policy/index.mdx | 33 -- .../speculative-module-fetching/index.mdx | 145 +----- .../src/routes/docs/(qwikcity)/api/index.mdx | 1 - .../(qwikcity)/project-structure/index.mdx | 3 +- .../insights/src/routes/service-worker.ts | 6 +- packages/qwik-city/package.json | 2 +- .../generate-service-worker.ts | 184 ------- .../generate-service-worker.unit.ts | 114 ----- .../qwik-city/src/buildtime/vite/plugin.ts | 35 +- packages/qwik-city/src/runtime/src/api.md | 5 +- .../src/runtime/src/service-worker/api.md | 2 +- .../src/service-worker/cached-fetch.ts | 84 ---- .../src/service-worker/cached-fetch.unit.ts | 141 ------ .../runtime/src/service-worker/constants.ts | 9 - .../src/runtime/src/service-worker/index.ts | 26 +- .../runtime/src/service-worker/prefetch.ts | 146 ------ .../src/service-worker/prefetch.unit.ts | 200 -------- .../src/runtime/src/service-worker/setup.ts | 95 ---- .../src/runtime/src/service-worker/types.ts | 30 -- .../src/runtime/src/service-worker/utils.ts | 37 -- .../runtime/src/service-worker/utils.unit.ts | 83 ---- .../src/runtime/src/sw-component.tsx | 16 +- .../qwik-city/src/runtime/src/sw-register.ts | 63 +-- packages/qwik/src/core/api.md | 4 +- packages/qwik/src/core/components/prefetch.ts | 115 +---- .../src/core/components/prefetch.unit.tsx | 114 ----- .../qwik/src/optimizer/src/plugins/vite.ts | 11 - .../prefetch-service-worker/direct-fetch.ts | 162 ------ .../qwik/src/prefetch-service-worker/entry.ts | 3 - .../prefetch-service-worker/index.unit.tsx | 460 ------------------ .../process-message.ts | 162 ------ .../qwik/src/prefetch-service-worker/setup.ts | 48 -- .../qwik/src/prefetch-service-worker/state.ts | 82 ---- .../src/server/prefetch-implementation.ts | 27 +- packages/qwik/src/server/prefetch-utils.ts | 13 +- packages/qwik/src/server/render.ts | 9 - packages/qwik/src/server/render.unit.tsx | 23 - packages/qwik/src/server/types.ts | 17 +- scripts/build.ts | 3 - scripts/submodule-qwikprefetch.ts | 175 ------- .../apps/base/src/routes/service-worker.ts | 17 - starters/apps/empty/src/root.tsx | 10 +- .../apps/empty/src/routes/service-worker.ts | 18 - starters/apps/playground/src/root.tsx | 7 +- .../playground/src/routes/service-worker.ts | 18 - 51 files changed, 112 insertions(+), 2888 deletions(-) create mode 100644 .changeset/yellow-frogs-repeat.md delete mode 100644 packages/qwik-city/src/buildtime/runtime-generation/generate-service-worker.unit.ts delete mode 100644 packages/qwik-city/src/runtime/src/service-worker/cached-fetch.ts delete mode 100644 packages/qwik-city/src/runtime/src/service-worker/cached-fetch.unit.ts delete mode 100644 packages/qwik-city/src/runtime/src/service-worker/constants.ts delete mode 100644 packages/qwik-city/src/runtime/src/service-worker/prefetch.ts delete mode 100644 packages/qwik-city/src/runtime/src/service-worker/prefetch.unit.ts delete mode 100644 packages/qwik-city/src/runtime/src/service-worker/setup.ts delete mode 100644 packages/qwik-city/src/runtime/src/service-worker/types.ts delete mode 100644 packages/qwik-city/src/runtime/src/service-worker/utils.ts delete mode 100644 packages/qwik-city/src/runtime/src/service-worker/utils.unit.ts delete mode 100644 packages/qwik/src/core/components/prefetch.unit.tsx delete mode 100644 packages/qwik/src/prefetch-service-worker/direct-fetch.ts delete mode 100644 packages/qwik/src/prefetch-service-worker/entry.ts delete mode 100644 packages/qwik/src/prefetch-service-worker/index.unit.tsx delete mode 100644 packages/qwik/src/prefetch-service-worker/process-message.ts delete mode 100644 packages/qwik/src/prefetch-service-worker/setup.ts delete mode 100644 packages/qwik/src/prefetch-service-worker/state.ts delete mode 100644 packages/qwik/src/server/render.unit.tsx delete mode 100644 scripts/submodule-qwikprefetch.ts delete mode 100644 starters/apps/base/src/routes/service-worker.ts delete mode 100644 starters/apps/empty/src/routes/service-worker.ts delete mode 100644 starters/apps/playground/src/routes/service-worker.ts diff --git a/.changeset/yellow-frogs-repeat.md b/.changeset/yellow-frogs-repeat.md new file mode 100644 index 00000000000..cb59a0e4203 --- /dev/null +++ b/.changeset/yellow-frogs-repeat.md @@ -0,0 +1,5 @@ +--- +'@builder.io/qwik-city': minor +--- + +CHORE: the service workers have been deprecated and replaced with entries that unregister them. If you have it enabled in production, you can remove it after a while once you are sure all your users have the new version. diff --git a/packages/docs/src/routes/api/qwik-city/api.json b/packages/docs/src/routes/api/qwik-city/api.json index 6c42d66b5a9..7a66897870d 100644 --- a/packages/docs/src/routes/api/qwik-city/api.json +++ b/packages/docs/src/routes/api/qwik-city/api.json @@ -782,7 +782,7 @@ } ], "kind": "Function", - "content": "```typescript\nServiceWorkerRegister: (props: {\n nonce?: string;\n}) => import(\"@builder.io/qwik\").JSXNode<\"script\">\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nprops\n\n\n\n\n{ nonce?: string; }\n\n\n\n\n\n
\n**Returns:**\n\nimport(\"@builder.io/qwik\").JSXNode<\"script\">", + "content": "Loads the service workers that are defined in the routes. Any file named `service-worker.*` (all JS extensions are allowed) will be picked up, bundled into a separate file, and registered as a service worker.\n\n\n```typescript\nServiceWorkerRegister: (props: {\n nonce?: string;\n}) => JSXOutput\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nprops\n\n\n\n\n{ nonce?: string; }\n\n\n\n\n\n
\n**Returns:**\n\nJSXOutput", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/sw-component.tsx", "mdFile": "qwik-city.serviceworkerregister.md" }, diff --git a/packages/docs/src/routes/api/qwik-city/index.md b/packages/docs/src/routes/api/qwik-city/index.md index 51792d84a2a..6af6d3b358c 100644 --- a/packages/docs/src/routes/api/qwik-city/index.md +++ b/packages/docs/src/routes/api/qwik-city/index.md @@ -2269,9 +2269,10 @@ export type ServerQRL = QRL< ## ServiceWorkerRegister +Loads the service workers that are defined in the routes. Any file named `service-worker.*` (all JS extensions are allowed) will be picked up, bundled into a separate file, and registered as a service worker. + ```typescript -ServiceWorkerRegister: (props: { nonce?: string }) => - import("@builder.io/qwik").JSXNode<"script">; +ServiceWorkerRegister: (props: { nonce?: string }) => JSXOutput; ```
@@ -2301,7 +2302,7 @@ props
**Returns:** -import("@builder.io/qwik").JSXNode<"script"> +JSXOutput [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/sw-component.tsx) diff --git a/packages/docs/src/routes/api/qwik/api.json b/packages/docs/src/routes/api/qwik/api.json index ef3a05df838..bdb8463f87d 100644 --- a/packages/docs/src/routes/api/qwik/api.json +++ b/packages/docs/src/routes/api/qwik/api.json @@ -1760,7 +1760,7 @@ } ], "kind": "Function", - "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\nLoad the prefetch graph for the container.\n\nEach Qwik container needs to include its own prefetch graph.\n\n\n```typescript\nPrefetchGraph: (opts?: {\n base?: string;\n manifestHash?: string;\n manifestURL?: string;\n nonce?: string;\n}) => JSXOutput\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nopts\n\n\n\n\n{ base?: string; manifestHash?: string; manifestURL?: string; nonce?: string; }\n\n\n\n\n_(Optional)_ Options for the loading prefetch graph.\n\n- `base` - Base of the graph. For a default installation this will default to the q:base value `/build/`. But if more than one MFE is installed on the page, then each MFE needs to have its own base. - `manifestHash` - Hash of the manifest file to load. If not provided the hash will be extracted from the container attribute `q:manifest-hash` and assume the default build file `${base}/q-bundle-graph-${manifestHash}.json`. - `manifestURL` - URL of the manifest file to load if non-standard bundle graph location name.\n\n\n
\n**Returns:**\n\n[JSXOutput](#jsxoutput)", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n> Warning: This API is now obsolete.\n> \n> This is no longer needed as the preloading happens automatically in qrl-class. You can remove this component from your app.\n> \n\n\n```typescript\nPrefetchGraph: (opts?: {\n base?: string;\n manifestHash?: string;\n manifestURL?: string;\n nonce?: string;\n}) => JSXOutput\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nopts\n\n\n\n\n{ base?: string; manifestHash?: string; manifestURL?: string; nonce?: string; }\n\n\n\n\n_(Optional)_\n\n\n
\n**Returns:**\n\n[JSXOutput](#jsxoutput)", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts", "mdFile": "qwik.prefetchgraph.md" }, @@ -1774,7 +1774,7 @@ } ], "kind": "Function", - "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\nInstall a service worker which will prefetch the bundles.\n\nThere can only be one service worker per page. Because there can be many separate Qwik Containers on the page each container needs to load its prefetch graph using `PrefetchGraph` component.\n\n\n```typescript\nPrefetchServiceWorker: (opts: {\n base?: string;\n scope?: string;\n path?: string;\n verbose?: boolean;\n fetchBundleGraph?: boolean;\n nonce?: string;\n}) => JSXNode<'script'>\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nopts\n\n\n\n\n{ base?: string; scope?: string; path?: string; verbose?: boolean; fetchBundleGraph?: boolean; nonce?: string; }\n\n\n\n\nOptions for the prefetch service worker.\n\n- `base` - Base URL for the service worker `import.meta.env.BASE_URL` or `/`. Default is `import.meta.env.BASE_URL` - `scope` - Base URL for when the service-worker will activate. Default is `/` - `path` - Path to the service worker. Default is `qwik-prefetch-service-worker.js` unless you pass a path that starts with a `/` then the base is ignored. Default is `qwik-prefetch-service-worker.js` - `verbose` - Verbose logging for the service worker installation. Default is `false` - `nonce` - Optional nonce value for security purposes, defaults to `undefined`.\n\n\n
\n**Returns:**\n\n[JSXNode](#jsxnode)<'script'>", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n> Warning: This API is now obsolete.\n> \n> This is no longer needed as the preloading happens automatically in qrl-class.ts. Leave this in your app for a while so it uninstalls existing service workers, but don't use it for new projects.\n> \n\n\n```typescript\nPrefetchServiceWorker: (opts: {\n base?: string;\n scope?: string;\n path?: string;\n verbose?: boolean;\n fetchBundleGraph?: boolean;\n nonce?: string;\n}) => JSXNode<'script'>\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nopts\n\n\n\n\n{ base?: string; scope?: string; path?: string; verbose?: boolean; fetchBundleGraph?: boolean; nonce?: string; }\n\n\n\n\n\n
\n**Returns:**\n\n[JSXNode](#jsxnode)<'script'>", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts", "mdFile": "qwik.prefetchserviceworker.md" }, diff --git a/packages/docs/src/routes/api/qwik/index.md b/packages/docs/src/routes/api/qwik/index.md index 88380b14447..b049939a8c5 100644 --- a/packages/docs/src/routes/api/qwik/index.md +++ b/packages/docs/src/routes/api/qwik/index.md @@ -3559,9 +3559,9 @@ export interface ParamHTMLAttributes extends Attrs<'base', T, > This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. -Load the prefetch graph for the container. - -Each Qwik container needs to include its own prefetch graph. +> Warning: This API is now obsolete. +> +> This is no longer needed as the preloading happens automatically in qrl-class. You can remove this component from your app. ```typescript PrefetchGraph: (opts?: { @@ -3595,9 +3595,7 @@ opts -_(Optional)_ Options for the loading prefetch graph. - -- `base` - Base of the graph. For a default installation this will default to the q:base value `/build/`. But if more than one MFE is installed on the page, then each MFE needs to have its own base. - `manifestHash` - Hash of the manifest file to load. If not provided the hash will be extracted from the container attribute `q:manifest-hash` and assume the default build file `${base}/q-bundle-graph-${manifestHash}.json`. - `manifestURL` - URL of the manifest file to load if non-standard bundle graph location name. +_(Optional)_ @@ -3611,9 +3609,9 @@ _(Optional)_ Options for the loading prefetch graph. > This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. -Install a service worker which will prefetch the bundles. - -There can only be one service worker per page. Because there can be many separate Qwik Containers on the page each container needs to load its prefetch graph using `PrefetchGraph` component. +> Warning: This API is now obsolete. +> +> This is no longer needed as the preloading happens automatically in qrl-class.ts. Leave this in your app for a while so it uninstalls existing service workers, but don't use it for new projects. ```typescript PrefetchServiceWorker: (opts: { @@ -3649,10 +3647,6 @@ opts -Options for the prefetch service worker. - -- `base` - Base URL for the service worker `import.meta.env.BASE_URL` or `/`. Default is `import.meta.env.BASE_URL` - `scope` - Base URL for when the service-worker will activate. Default is `/` - `path` - Path to the service worker. Default is `qwik-prefetch-service-worker.js` unless you pass a path that starts with a `/` then the base is ignored. Default is `qwik-prefetch-service-worker.js` - `verbose` - Verbose logging for the service worker installation. Default is `false` - `nonce` - Optional nonce value for security purposes, defaults to `undefined`. - **Returns:** diff --git a/packages/docs/src/routes/docs/(qwik)/getting-started/index.mdx b/packages/docs/src/routes/docs/(qwik)/getting-started/index.mdx index 5235499e0b2..e6cda4ecf3a 100644 --- a/packages/docs/src/routes/docs/(qwik)/getting-started/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/getting-started/index.mdx @@ -614,7 +614,7 @@ bun run preview NOTE: - Your application should now have a production build running on localhost:4173. -- If you interact with the application now, the network tab of the dev tools should show that the bundles are instantly delivered from the [ServiceWorker cache](/docs/advanced/speculative-module-fetching/). +- If you interact with the application now, the network tab of the dev tools should show that the bundles have been [preloaded](/docs/advanced/speculative-module-fetching/). ## Review @@ -628,4 +628,4 @@ For more on just how much you can achieve with Qwik, check out the dedicated doc - [Form actions](/docs/(qwikcity)/action/index.mdx) && [zod validation](/docs/(qwikcity)/action/index.mdx#validation-and-type-safety) - [State management](/docs/(qwik)/components/state/index.mdx) - [Tasks](/docs/(qwik)/components/tasks/index.mdx#use-usetask-when-you-need-to) -- [ServiceWorker cache](/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx#pre-populating-the-cache-with-a-service-worker) +- [Preloading](/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx) diff --git a/packages/docs/src/routes/docs/(qwikcity)/advanced/content-security-policy/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/advanced/content-security-policy/index.mdx index e8062d4ee94..7f61da2335b 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/advanced/content-security-policy/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/advanced/content-security-policy/index.mdx @@ -69,39 +69,6 @@ export const onRequest: RequestHandler = event => { }; ``` -### Add it to the service worker as well - -```tsx {12,22} /nonce/ title="src/root.ts" -import { component$, useServerData } from "@builder.io/qwik"; -import { - QwikCityProvider, - RouterOutlet, - ServiceWorkerRegister, -} from "@builder.io/qwik-city"; -import { RouterHead } from "./components/router-head/router-head"; -import { isDev } from "@builder.io/qwik"; - -import "./global.css"; - -export default component$(() => { - const nonce = useServerData("nonce"); - return ( - - - - {!isDev && } - - - - - - {!isDev && } - - - ); -}); -``` - ### Custom scripts If you have custom script tags that you need to add the nonce to, you can use the `useServerData` hook to get the nonce from the server and add it to your script tags. diff --git a/packages/docs/src/routes/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx index 7bd9ed28371..09b00a0451a 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx @@ -13,7 +13,8 @@ contributors: - wtlin1228 - aendel - jemsco -updated_at: '2023-06-25T19:43:33Z' + - wmertens +updated_at: '2025-03-25T12:00:00Z' created_at: '2023-03-20T23:45:13Z' --- @@ -23,94 +24,11 @@ Qwik is able to load a page and become interactive extremely fast due to its abi Qwik's goal is to optimize loading by caching only the necessary parts of the application based on potential user interactions. It avoids loading unnecessary bundles by understanding which interactions are not possible. -- [Pre-populating the Cache with a Service Worker](#pre-populating-the-cache-with-a-service-worker) -- [Caching Request and Response Pairs](#caching-request-and-response-pairs) -- [Parallelizing Network Requests](#parallelizing-network-requests) - ### Pre-populating the Cache -Each page load will pre-populate the cache with bundles that _could_ be executed on the page by the user at that moment. For example, let's say that the page has a click listener on a button. When the page loads, the service worker's first job is to ensure the code for that click listener is already in the [cache](#cache-api). When the user clicks the button, Qwik makes a request to the event listener's function and any code dependencies to execute that function. The goal is for the code to already be in the [browser's cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) ready to execute. - -The initial page load prepares the cache for the next probable interaction and also downloads other necessary code incrementally in a separate thread. When a follow-up interaction happens, such as opening a modal or menu, Qwik will emit another event with additional code that could be used since the last interaction. Pre-populating the cache happens continuously as users interact with the application. - -### Pre-populate Cache Event - -The recommended strategy is to use a [service worker](#pre-populating-the-cache-with-a-service-worker) to populate the [browser's cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache). The Qwik framework itself should use the [prefetchEvent](../../../(qwik)/advanced/modules-prefetching/index.mdx#implementation) implementation, which is already the default. - -## Pre-populating the Cache with a Service Worker - -Traditionally, a service worker is used to cache most or all of the bundles that an application uses. [Service workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers) are commonly seen only as a way to make an application work offline. - -Qwik City uses service workers quite differently to provide a powerful caching strategy. Instead of downloading the entire application, the goal is to use the service worker to dynamically pre-populate the cache with what's _possible_ to execute. By _not_ downloading the entire application, resources are freed up, enabling users to request only the necessary parts they _could_ use to complete their current task on the screen. - -Additionally, the service worker will automatically add listeners for these events emitted from Qwik. - -### Background Task - -An advantage of using a service worker is that it's also an extension of a [worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API), which runs in a background thread. - -> Web Workers makes it possible to run a script operation in a background thread separate from the main execution thread of a web application. The advantage of this is that laborious processing can be performed in a separate thread, allowing the main (usually the UI) thread to run without being blocked/slowed down. - -By pre-populating the cache from within a service worker (which is a worker), we're able to essentially run the code in a background task, in order to not interfere with the main UI thread. By not interfering with the main thread, we can enhance the performance of the Qwik application for users. - -### Interactively Pre-populating the Cache - -Qwik itself should be configured to use the [prefetchEvent](../../../(qwik)/advanced/modules-prefetching/index.mdx#implementation) implementation. This is the default. When Qwik emits the event, the service worker registration actively forwards the event data to the installed and active service worker. - -Running in a background thread, the service worker then fetches the modules and adds them to the browser’s [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache). The main thread only needs to emit data about the required bundles, while the service worker’s sole focus is to cache those bundles. - -1. If the browser already has it cached? Great, do nothing! -2. If the browser hasn't already cached this bundle, then let's kick off the fetch request. - -> The service worker ensures that multiple requests for the same bundle [do not happen at the same time](#avoiding-duplicate-requests). - -## Caching Request and Response Pairs - -In many traditional frameworks, the preferred strategy is to use the html `` tag with a `rel` attribute set to `prefetch`, `preload` or `modulepreload`. However, due to [known issues](../../../(qwik)/advanced/modules-prefetching/index.mdx#link-rel) Qwik avoids using this approach as the default prefetching strategy, it can still be [configured](../../../(qwik)/advanced/modules-prefetching/index.mdx) if required. - -Instead, Qwik prefers to use a newer approach that takes full advantage of the browser's [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache), which is better supported compared to [modulepreload](../../../(qwik)/advanced/modules-prefetching/index.mdx#link-rel). - -### Cache API - -The [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache), often associated with service workers, is a way to store request and response pairs in order for an application to work offline. In addition to enabling applications to work without connectivity, the same Cache API provides an extremely powerful caching mechanism available to Qwik. - -Using the installed and activated [service worker](#pre-populating-the-cache-with-a-service-worker) to intercept requests, Qwik is able to handle specific requests for _known_ bundles. In contrast to the common way service workers are used, the default does not attempt to handle all requests, only known bundles generated by Qwik. The site's installed service worker can still be [customized by each site](#user-service-worker-code). - -An advantage of Qwik's optimizer is that it generates a `q-manifest.json` file. The `q-manifest.json` includes a detailed module graph of how bundles are associated and which symbols are within each bundle. This same module graph data is provided to the service worker allowing for every network request for known bundles to be handled by the cache. - -### Dynamic Imports and Caching - -When Qwik requests a module it uses a dynamic `import()`. For example, let's say a user interaction happened, requiring Qwik to execute a dynamic import for `/build/q-abc.js`. The code to do so would look something like this: - -```ts -const module = await import('/build/q-abc.js'); -``` - -What's important here is that Qwik itself has no knowledge of a prefetching or caching strategy. It's simply making a request for a URL. However, because we've installed a service worker, and the service worker is intercepting requests, it's able to inspect the URL and say, "look, this is a request for `/build/q-abc.js`! This is one of our bundles! Let's first check to see if we already have this in the cache before we do an actual network request." - -This is the power of the service worker and Cache API! In another thread, Qwik pre-populates the cache for modules the user may soon request. If these modules are already cached, then the browser doesn't need to do anything. +Each page load will pre-populate the cache with bundles that _could_ be executed on the page by the user at that moment. For example, let's say that the page has a click listener on a button. When the page loads, the Qwik's first job is to ensure the code for that click listener is already in the [browser's cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache). When the user clicks the button, Qwik makes a request to the event listener's function and any code dependencies to execute that function. This might include importing other modules. -## Parallelizing Network Requests - -In the [Caching Request and Response Pairs](#cache-api) docs we explained the powerful combination of the [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) and [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker) APIs. However, in Qwik, we can go further by ensuring that duplicate requests are not created for the same bundle and prevent network waterfalls, all from within the background thread. - -### Avoiding Duplicate Requests - -As an example, let's say an end-user currently has a very slow connection. When they first request the landing page, the device downloads the HTML and renders the content (an area where Qwik really shines). On this slow connection, it'd be a shame if users had to download several hundred more kilobytes just to [make their app work and become interactive](https://www.builder.io/blog/hydration-is-pure-overhead). - -However, because the app was built with Qwik, the end-user doesn't need to download the entire application for it to become interactive. Instead, the end-user already downloaded the SSR rendered HTML app, and any interactive parts, such as an "Add to cart" button, can be prefetched immediately. - -> Note that we're only prefetching the actual listener code, and _not_ the entire stack of the component tree render functions. - -In this extremely common real-world example of a device with a slow connection, the device immediately starts to pre-populate the cache for the possible interactions that are visible by the end-user. However, due to their slow connection, even though we started to cache the modules as soon as possible in a [background thread](#background-task), the fetch request itself could still be in flight. - -For demo purposes, let's say the fetching for this bundle takes two seconds. However, after one second of viewing the page, the user clicks on the button. - -In a traditional framework, there's a good chance that absolutely nothing would happen! The event listener can't be added to the button yet if the framework hasn't finished downloading, hydrating and re-rendering. This means that the user's interaction would just be lost. - -With Qwik's caching strategy, if a user clicks a button and we already initiated a request one second earlier, with only one second remaining until it's fully received, then the end-user only needs to wait for that one second. Remember, they're on a slow connection in this demo. Luckily the user already received the fully rendered landing page and are already looking at a completed page. Next, they're only pre-populating the cache with the parts of the app they could interact with, and their slow connection is dedicated to just those bundle(s). This is in contrast to their slow connection downloading all of the app, just to execute one listener. - -Qwik can intercept requests for known bundles. If a fetch is in flight in a background thread and a user requests the same bundle, it'll ensure that the second request is able to re-use the same bundle, which may already be done downloading. Trying to perform any of this with the [link](../../../(qwik)/advanced/modules-prefetching/index.mdx#link-rel) also shows why Qwik preferred to use the caching API and intercepts requests with a service worker as the default instead of using [link](../../../(qwik)/advanced/modules-prefetching/index.mdx#link-rel). +The initial page load prepares the cache for the next probable interaction and also downloads other necessary code incrementally. When a follow-up interaction happens, such as opening a modal or menu, Qwik will ask the browser to preload the code for that interaction. Pre-populating the cache happens continuously as users interact with the application. ### Reducing Network Waterfalls @@ -138,62 +56,23 @@ console.log('Module C'); In this example, when Module `A` is first requested, the browser has no idea that it should also start requesting Module `B` and `C`. It doesn't even know it needs to start requesting Module `B`, until AFTER Module `A` has finished downloading. It's a common problem in that the browser doesn't know ahead of time what it should start to request, until after each module has finished downloading. -However, because our service worker contains a module graph generated from the manifest, we do know all of the modules which _will_ be requested next. So when either user interaction or a prefetch for a bundle happens, the browser initiates the request for all of the bundles that _will_ be requested. This allows us to drastically reduce the time it takes to request all bundles. - -## User Service Worker Code - -The default service worker that is installed by Qwik City can still be controlled entirely by the application. For example, the source file `src/routes/service-worker.ts` becomes `/service-worker.js`, which is the script requested by the browser. Notice how its place within `src/routes` still follows the same directory-based routing pattern. - -Below is an example of a default `src/routes/service-worker.ts` source file: - -```ts -import { setupServiceWorker } from '@builder.io/qwik-city/service-worker'; - -setupServiceWorker(); +However, Qwik knows the module graph and controls QRL segment loading, we do know all of the modules which _will_ be requested next. So when Qwik sees that it will require a module, it will tell the browser to preload it and all of its dependencies. -addEventListener('install', () => self.skipWaiting()); +### Details -addEventListener('activate', (ev) => ev.waitUntil(self.clients.claim())); -``` +The Qwik build process generates a `q-manifest.json` file. The `q-manifest.json` includes a detailed module graph of how bundles are associated and which symbols are within each bundle. -The source code for `src/routes/service-worker.ts` can be modified which includes opting-in, or opting-out, of setting up Qwik City's service worker. +This is then further used to generate the `build/q-bundle-graph-xyz.json` file, which is a compact representation of the module graph, and is loaded by Qwik to preload QRL segment loading. -Notice that the `setupServiceWorker()` function is imported from `@builder.io/qwik-city/service-worker` and executed at the top of the source file. Developers have the flexibility to modify when and where this function is called according to their needs. For example, if a developer prefers to handle fetch requests first, they can add their fetch listener above the `setupServiceWorker()`. Or if they didn't want to use Qwik City's service worker at all, they would just remove `setupServiceWorker()` from the file. +Qwik-City also injects all routes with their dependencies into this bundlegraph file. -Additionally, the default `src/routes/service-worker.ts` file comes with an [install](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/install_event) and [activate](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/activate_event) event listeners, each added at the bottom of the file. The callbacks provided are the recommended callbacks. However, the developer can modify these callbacks depending on their own app's requirements. +When a container resumes, it will fetch the bundlegraph file (probably from the cache) and use it to prefetch modules needed for event handlers and routes. -Another important note is that Qwik City's request intercepting is _only_ for Qwik bundles, it does not attempt to handle any requests which are not a part of its build. +Static imports are preloaded with high priority, and dynamic imports are preloaded with low priority. The browser will decide when to download the modules. -So while Qwik City does provide a way to help prefetch and cache bundles, it does not take full control of the app's service worker. This still allows developers to add their service worker logic without conflicting with Qwik. +The preloading happens with the `_preload` function, which is an internal Qwik API. It works by traversing the bundlegraph and adding `` tags for each module. ## Disabled During Development Speculative module fetching only kicks in preview or on a production build. In development, the service worker is disabled which also disables speculative module fetching. This is because during development we want to always ensure the latest development code is being used, rather than what's been previously cached. - -### HTTP Cache vs. Service Worker Cache - -Speculative module fetching may not appear to be working partly due to the various levels of caching. For example, the browser itself may cache requests in its [HTTP cache](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching), and the service worker may cache requests in the [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache). Just emptying one of the caches may not be enough to see the effects of speculative module fetching. - -### Misleading Empty Cache and Hard Reload - -When developers run [Empty Cache and Hard Reload](https://developer.chrome.com/blog/hard-reload/), it's a bit misleading because it actually _only_ empties the browser's HTTP cache. It's not, however, emptying the service worker's cache. Even though the browser's HTTP cache is empty, the service worker still has the previous cached requests. - -Additionally, when "Empty Cache and Hard Reload" is used, the browser sends a `no-cache` cache-control header in the _request_ to the server. Because the request has a `no-cache` cache-control header, the service worker purposely does not use its own cache, and instead the browser performs the usual HTTP fetch again. - -### Emptying the Service Worker Cache - -The recommended way to test speculative module fetching is to: - -- **Unregister the service worker**: In Chrome DevTools, go to the Application tab, and under Service Workers, click the "Unregister" link for the for your site's service worker. -- **Delete the "QwikBuild" Cache Storage**: In Chrome DevTools, go to the Application tab, and under Cache Storage on the left side, right click "Delete" on the "QwikBuild" cache storage. -- **Do not hard reload**: Instead of hard reloading, which would send a no-cache cache-control to the service worker, just click the URL bar and hit enter. This will send a normal request as if you were a first time visitor. - -Note that this process is only for testing the speculative module fetching, and is not required for new builds. Each build will create a new service worker, and the old service worker will be automatically unregistered. - -### Debug Mode - -The service worker in Qwik core, which uses the `` and `` components in `root.tsx` has a debug mode. - -To see the service worker logs, add `window.qwikPrefetchSW.push(['verbose', '', []])` to the JavaScript console and press the `Enter` key. - diff --git a/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx index 9df224ecbe1..607d82bf937 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx @@ -300,7 +300,6 @@ export default component$(() => { - ); diff --git a/packages/docs/src/routes/docs/(qwikcity)/project-structure/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/project-structure/index.mdx index 76f55547850..67b7f08ae1b 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/project-structure/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/project-structure/index.mdx @@ -39,8 +39,7 @@ qwik-app-demo │ │ ├── flower.css │ │ └── index.tsx │ ├── index.tsx -│ ├── layout.tsx -│ └── service-worker.ts +│ └── layout.tsx ├── tsconfig.json └── vite.config.ts ``` diff --git a/packages/insights/src/routes/service-worker.ts b/packages/insights/src/routes/service-worker.ts index fb1bb3fd75e..965b4dd60f4 100644 --- a/packages/insights/src/routes/service-worker.ts +++ b/packages/insights/src/routes/service-worker.ts @@ -7,12 +7,8 @@ * Qwik uses a service worker to speed up your site and reduce latency, ie, not used in the traditional way of offline. * You can also use this file to add more functionality that runs in the service worker. */ -import { setupServiceWorker } from '@builder.io/qwik-city/service-worker'; - -setupServiceWorker(); +export declare const self: ServiceWorkerGlobalScope; addEventListener('install', () => self.skipWaiting()); addEventListener('activate', () => self.clients.claim()); - -declare const self: ServiceWorkerGlobalScope; diff --git a/packages/qwik-city/package.json b/packages/qwik-city/package.json index 105aa62e53b..a8626d470bc 100644 --- a/packages/qwik-city/package.json +++ b/packages/qwik-city/package.json @@ -155,7 +155,7 @@ "require": "./lib/vite/index.cjs" }, "./service-worker": { - "types": "./lib/service-worker.d.ts", + "types": "./service-worker.d.ts", "import": "./lib/service-worker.mjs", "require": "./lib/service-worker.cjs" } diff --git a/packages/qwik-city/src/buildtime/runtime-generation/generate-service-worker.ts b/packages/qwik-city/src/buildtime/runtime-generation/generate-service-worker.ts index e4b5eb1364c..1f9209a43ce 100644 --- a/packages/qwik-city/src/buildtime/runtime-generation/generate-service-worker.ts +++ b/packages/qwik-city/src/buildtime/runtime-generation/generate-service-worker.ts @@ -1,6 +1,3 @@ -import type { InsightManifest, QwikManifest } from '@builder.io/qwik/optimizer'; -import type { AppBundle } from '../../runtime/src/service-worker/types'; -import { removeExtension } from '../../utils/fs'; import type { BuildContext } from '../types'; export function generateServiceWorkerRegister(ctx: BuildContext, swRegister: string) { @@ -25,187 +22,6 @@ export function generateServiceWorkerRegister(ctx: BuildContext, swRegister: str return `export default ${JSON.stringify(swReg)};`; } -export function prependManifestToServiceWorker( - ctx: BuildContext, - manifest: QwikManifest, - prefetch: InsightManifest['prefetch'] | null, - swCode: string -) { - const key = `/* Qwik Service Worker */`; - if (swCode.includes(key)) { - // both SSR and SSG could have ran this code, - // just check if we already prepended the bundles - return null; - } - - // TODO: add dynamic imports that don't import routes - // (anything that doesn't contain _hw export) - - const appBundles: AppBundle[] = []; - const appBundlesCode = generateAppBundles(appBundles, manifest); - const libraryBundlesCode = generateLibraryBundles(appBundles, manifest); - const [linkBundlesCode] = generateLinkBundles(ctx, appBundles, manifest, prefetch); - - return [key, appBundlesCode, libraryBundlesCode, linkBundlesCode, swCode].join('\n'); -} - -export function generateAppBundles(appBundles: AppBundle[], manifest: QwikManifest) { - const sortedBundles = Object.keys(manifest.bundles).sort(); - for (const appBundleName of sortedBundles) { - const appBundle: AppBundle = [appBundleName, []]; - appBundles.push(appBundle); - - const symbolHashesInBundle: string[] = []; - - const manifestBundle = manifest.bundles[appBundleName]; - const staticDepsNames = Array.isArray(manifestBundle.imports) ? manifestBundle.imports : []; - - const dynamicDepsNames = []; - for (const dynamicDepName of manifestBundle.dynamicImports || []) { - const dynamicDep = manifest.bundles[dynamicDepName]; - if (!manifest.bundles[dynamicDepName]) { - continue; - } - if (dynamicDep.hasSymbols) { - dynamicDepsNames.push(dynamicDepName); - } - } - const depsNames = [...staticDepsNames, ...dynamicDepsNames]; - const depsNamesSet = new Set(depsNames); - - for (const depName of depsNames) { - clearTransitiveDeps(depsNamesSet, new Set(), depName); - } - - // set the imports based on the sorted index number - appBundle[1] = Array.from(depsNamesSet).map((dep) => sortedBundles.indexOf(dep)); - - if (manifestBundle.symbols) { - for (const manifestBundleSymbolName of manifestBundle.symbols) { - const symbol = manifest.symbols[manifestBundleSymbolName]; - if (symbol?.hash && !symbolHashesInBundle.includes(symbol.hash)) { - symbolHashesInBundle.push(symbol.hash); - } - } - } - - if (symbolHashesInBundle.length > 0) { - (appBundle as unknown as any)[2] = symbolHashesInBundle; - } - } - - function clearTransitiveDeps(deps: Set, seen: Set, depName: string) { - const childBundle = manifest.bundles[depName]; - - for (const childDepImport of childBundle.imports || []) { - if (deps.has(childDepImport)) { - deps.delete(childDepImport); - } - if (!seen.has(childDepImport)) { - seen.add(childDepImport); - clearTransitiveDeps(deps, seen, childDepImport); - } - } - } - - return `const appBundles=${JSON.stringify(appBundles)};`; -} - -function generateLibraryBundles(appBundles: AppBundle[], manifest: QwikManifest) { - const libraryBundleIds: number[] = []; - - for (const [bundleName, bundle] of Object.entries(manifest.bundles)) { - if (bundle.origins && bundle.origins.includes('@qwik-city-plan')) { - libraryBundleIds.push(getAppBundleIndex(appBundles, bundleName)); - break; - } - } - - return `const libraryBundleIds=${JSON.stringify(libraryBundleIds)};`; -} - -export function generateLinkBundles( - ctx: BuildContext, - appBundles: AppBundle[], - manifest: QwikManifest, - prefetch: InsightManifest['prefetch'] | null -) { - const linkBundles: string[] = []; - const symbolToBundle = new Map(); - const routeToBundles: Record = {}; - for (const bundleName in manifest.bundles || []) { - manifest.bundles[bundleName].symbols?.forEach((symbol) => { - const idx = symbol.lastIndexOf('_'); - symbolToBundle.set(idx === -1 ? symbol : symbol.substring(idx + 1), bundleName); - }); - } - - for (const r of ctx.routes) { - const linkBundleNames: string[] = []; - - const addFileBundles = (filePath: string) => { - for (const [bundleName, bundle] of Object.entries(manifest.bundles)) { - if (bundle.origins) { - for (const bundleOrigin of bundle.origins) { - const srcPath = removeExtension(filePath); - const bundleOriginPath = removeExtension(bundleOrigin); - - if (srcPath.endsWith(bundleOriginPath)) { - if (!linkBundleNames.includes(bundleName)) { - linkBundleNames.push(bundleName); - } - - if (bundle.dynamicImports) { - for (const dynamicImport of bundle.dynamicImports) { - if (!linkBundleNames.includes(dynamicImport)) { - linkBundleNames.push(dynamicImport); - } - } - } - } - } - } - } - }; - - for (const layout of r.layouts) { - addFileBundles(layout.filePath); - } - addFileBundles(r.filePath); - - if (prefetch) { - // process the symbols from insights prefetch - const symbolsForRoute = prefetch.find((p) => p.route === r.routeName); - symbolsForRoute?.symbols?.reverse().forEach((symbol) => { - const bundle = symbolToBundle.get(symbol); - if (bundle) { - const idx = linkBundleNames.indexOf(bundle); - if (idx !== -1) { - linkBundleNames.splice(idx, 1); - } - linkBundleNames.unshift(bundle); - } - }); - } - - linkBundles.push( - `[${r.pattern.toString()},${JSON.stringify( - linkBundleNames.map((bundleName) => getAppBundleIndex(appBundles, bundleName)) - )}]` - ); - routeToBundles[r.routeName] = linkBundleNames; - } - - return [`const linkBundles=[${linkBundles.join(',')}];`, routeToBundles] as [ - string, - typeof routeToBundles, - ]; -} - -function getAppBundleIndex(appBundles: AppBundle[], bundleName: string) { - return appBundles.findIndex((b) => b[0] === bundleName); -} - const SW_UNREGISTER = ` navigator.serviceWorker?.getRegistrations().then((regs) => { for (const reg of regs) { diff --git a/packages/qwik-city/src/buildtime/runtime-generation/generate-service-worker.unit.ts b/packages/qwik-city/src/buildtime/runtime-generation/generate-service-worker.unit.ts deleted file mode 100644 index 95d73da7a59..00000000000 --- a/packages/qwik-city/src/buildtime/runtime-generation/generate-service-worker.unit.ts +++ /dev/null @@ -1,114 +0,0 @@ -import type { - InsightManifest, - QwikBundle, - QwikManifest, - QwikSymbol, -} from '@builder.io/qwik/optimizer'; -import { assert, expect, test } from 'vitest'; -import type { AppBundle } from '../../runtime/src/service-worker/types'; -import type { BuildContext, BuildRoute } from '../types'; -import { generateAppBundles, generateLinkBundles } from './generate-service-worker'; - -test('incorporate qwik-insights', () => { - const routes: BuildRoute[] = [ - { - routeName: '/', - pattern: /\//, - filePath: './src/routes/index.tsx', - layouts: [], - } /* satisfies Partial */ as any, - { - routeName: '/routeA', - pattern: /\/routeA/, - filePath: './src/routes/routeA/index.tsx', - layouts: [], - } /* satisfies Partial */ as any, - ]; - const ctx: BuildContext = { routes } /* satisfies Partial */ as any; - const appBundles: AppBundle[] = [ - ['q-bundle-a.js', [12]], - ['q-bundle-b.js', [34]], - ['q-bundle-123.js', [123]], - ['q-bundle-234.js', [234]], - ['q-bundle-345.js', [345]], - ]; - const manifest: QwikManifest = { - bundles: { - 'q-bundle-a.js': { - origins: ['./src/routes/index.tsx'], - } /* satisfies Partial */ as any, - 'q-bundle-b.js': { - origins: ['./src/routes/routeA/index.tsx'], - } /* satisfies Partial */ as any, - 'q-bundle-123.js': { - symbols: ['s_123'], - } /* satisfies Partial */ as any, - 'q-bundle-234.js': { - symbols: ['s_234'], - } /* satisfies Partial */ as any, - 'q-bundle-345.js': { - symbols: ['s_345'], - } /* satisfies Partial */ as any, - }, - } /* satisfies Partial */ as any; - const prefetch: InsightManifest['prefetch'] = [ - { route: '/', symbols: ['123', '234'] }, - { route: '/routeA', symbols: ['345'] }, - ]; - const [_, routeToBundles] = generateLinkBundles(ctx, appBundles, manifest, prefetch); - assert.deepEqual(routeToBundles['/'], ['q-bundle-123.js', 'q-bundle-234.js', 'q-bundle-a.js']); - assert.deepEqual(routeToBundles['/routeA'], ['q-bundle-345.js', 'q-bundle-b.js']); -}); - -test('generateAppBundles', () => { - const fakeManifest = { - symbols: { - s_aaa123: { hash: 'aaa123' } as QwikSymbol, - s_bbb123: { hash: 'bbb123' } as QwikSymbol, - s_ccc123: { hash: 'ccc123' } as QwikSymbol, - s_ddd123: { hash: 'ddd123' } as QwikSymbol, - s_eee123: { hash: 'eee123' } as QwikSymbol, - } as Record, - bundles: { - 'a.js': { - size: 0, - imports: ['b.js', 'c.js'], - symbols: ['s_aaa123'], - }, - 'b.js': { - size: 0, - imports: ['c.js', 'd.js'], - dynamicImports: ['e.js'], - symbols: ['s_bbb123'], - }, - 'c.js': { - size: 0, - imports: ['d.js'], - symbols: ['s_ccc123'], - }, - 'd.js': { - size: 0, - imports: [], - symbols: ['s_ddd123'], - }, - 'e.js': { - size: 0, - imports: [], - symbols: ['s_eee123'], - }, - } as Record, - } as QwikManifest; - - const actualAppBundles = generateAppBundles([], fakeManifest); - - const expectedAppBundles = [ - ['a.js', [1], ['aaa123']], - ['b.js', [2], ['bbb123']], - ['c.js', [3], ['ccc123']], - ['d.js', [], ['ddd123']], - ['e.js', [], ['eee123']], - ]; - const expectedResult = `const appBundles=${JSON.stringify(expectedAppBundles)};`; - - expect(actualAppBundles).toEqual(expectedResult); -}); diff --git a/packages/qwik-city/src/buildtime/vite/plugin.ts b/packages/qwik-city/src/buildtime/vite/plugin.ts index 34574311a8e..39c436da82e 100644 --- a/packages/qwik-city/src/buildtime/vite/plugin.ts +++ b/packages/qwik-city/src/buildtime/vite/plugin.ts @@ -19,10 +19,7 @@ import { createMdxTransformer, type MdxTransform } from '../markdown/mdx'; import { transformMenu } from '../markdown/menu'; import { generateQwikCityEntries } from '../runtime-generation/generate-entries'; import { generateQwikCityPlan } from '../runtime-generation/generate-qwik-city-plan'; -import { - generateServiceWorkerRegister, - prependManifestToServiceWorker, -} from '../runtime-generation/generate-service-worker'; +import { generateServiceWorkerRegister } from '../runtime-generation/generate-service-worker'; import type { BuildContext } from '../types'; import { modifyBundleGraph } from './bundle-graph-modifier'; import { ssrDevMiddleware, staticDistMiddleware } from './dev-server'; @@ -270,38 +267,8 @@ function qwikCityPlugin(userOpts?: QwikCityVitePluginOptions): any { async handler() { if (ctx?.target === 'ssr' && !ctx?.isDevServer) { // ssr build - const manifest = qwikPlugin!.api.getManifest(); const clientOutDir = qwikPlugin!.api.getClientOutDir(); - if (manifest && clientOutDir) { - const basePathRelDir = api.getBasePathname().replace(/^\/|\/$/, ''); - const clientOutBaseDir = join(clientOutDir, basePathRelDir); - const insightsManifest = await qwikPlugin!.api.getInsightsManifest(clientOutDir); - - for (const swEntry of ctx.serviceWorkers) { - try { - const swClientDistPath = join(clientOutBaseDir, swEntry.chunkFileName); - const swCode = await fs.promises.readFile(swClientDistPath, 'utf-8'); - try { - const swCodeUpdate = prependManifestToServiceWorker( - ctx, - manifest, - insightsManifest?.prefetch || null, - swCode - ); - if (swCodeUpdate) { - await fs.promises.mkdir(clientOutDir, { recursive: true }); - await fs.promises.writeFile(swClientDistPath, swCodeUpdate); - } - } catch (e2) { - console.error(e2); - } - } catch (e) { - // safe to ignore if a service-worker.js not found - } - } - } - if (outDir && clientOutDir) { const assetsDir = qwikPlugin!.api.getAssetsDir(); const { staticPathsCode, notFoundPathsCode } = await postBuild( diff --git a/packages/qwik-city/src/runtime/src/api.md b/packages/qwik-city/src/runtime/src/api.md index 53864c93d81..2e8062c1de5 100644 --- a/packages/qwik-city/src/runtime/src/api.md +++ b/packages/qwik-city/src/runtime/src/api.md @@ -10,7 +10,6 @@ import { CookieOptions } from '@builder.io/qwik-city/middleware/request-handler' import { CookieValue } from '@builder.io/qwik-city/middleware/request-handler'; import { DeferReturn } from '@builder.io/qwik-city/middleware/request-handler'; import type { EnvGetter } from '@builder.io/qwik-city/middleware/request-handler'; -import { JSXNode } from '@builder.io/qwik'; import { JSXOutput } from '@builder.io/qwik'; import { QRL } from '@builder.io/qwik'; import { QRLEventHandlerMulti } from '@builder.io/qwik'; @@ -447,10 +446,10 @@ export type ServerQRL = QRL<((abort: AbortSignal, ...a // @public (undocumented) export const serverQrl: (qrl: QRL, options?: ServerConfig) => ServerQRL; -// @public (undocumented) +// @public export const ServiceWorkerRegister: (props: { nonce?: string; -}) => JSXNode<"script">; +}) => JSXOutput; // @public (undocumented) export interface StaticGenerate { diff --git a/packages/qwik-city/src/runtime/src/service-worker/api.md b/packages/qwik-city/src/runtime/src/service-worker/api.md index 22b2654ad3d..b0da6cf098a 100644 --- a/packages/qwik-city/src/runtime/src/service-worker/api.md +++ b/packages/qwik-city/src/runtime/src/service-worker/api.md @@ -4,7 +4,7 @@ ```ts -// @public (undocumented) +// @public @deprecated (undocumented) export const setupServiceWorker: () => void; // (No @packageDocumentation comment for this package) diff --git a/packages/qwik-city/src/runtime/src/service-worker/cached-fetch.ts b/packages/qwik-city/src/runtime/src/service-worker/cached-fetch.ts deleted file mode 100644 index ea66db546ce..00000000000 --- a/packages/qwik-city/src/runtime/src/service-worker/cached-fetch.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { AwaitingRequests, Fetch } from './types'; -import { useCache } from './utils'; - -export const cachedFetch = ( - cache: Cache, - fetch: Fetch, - awaitingRequests: AwaitingRequests, - request: Request -) => - new Promise((promiseResolve, promiseReject) => { - const url = request.url; - const awaitingRequestResolves = awaitingRequests.get(url); - - if (awaitingRequestResolves) { - // there's already an active request happening - // don't start a new request - awaitingRequestResolves.push([promiseResolve, promiseReject]); - } else { - // there isn't already an active request for this url - // start a new request - const resolve = (response: Response) => { - // the response has been resolved - const resolves = awaitingRequests.get(url); - if (resolves) { - awaitingRequests.delete(url); - // loop through each of the active requests - for (const [awaitingResolve] of resolves) { - // clone a new response for each of the active requests - awaitingResolve(response.clone()); - } - } else { - // somehow the array of awaiting requests doesn't exist - promiseResolve(response.clone()); - } - }; - - const reject = (msg: any) => { - const resolves = awaitingRequests.get(url); - if (resolves) { - awaitingRequests.delete(url); - for (const [_, awaitingReject] of resolves) { - awaitingReject(msg); - } - } else { - promiseReject(msg); - } - }; - - // create a new array of the request waiting to be resolved - awaitingRequests.set(url, [[promiseResolve, promiseReject]]); - - cache - .match(url) - .then((cachedResponse) => { - if (useCache(request, cachedResponse)) { - // cached response found and user did not specifically send - // a request header to NOT use the cache (wasn't a hard refresh) - resolve(cachedResponse!); - } else { - // no cached response found or user didn't want to use the cache - // do a full network request - return fetch(request).then(async (networkResponse) => { - if (networkResponse.ok) { - // network response was good, let's cache it - await cache.put(url, networkResponse.clone()); - } - resolve(networkResponse); - }); - } - }) - .catch((err) => { - // network error, probably offline - return cache.match(url).then((cachedResponse) => { - if (cachedResponse) { - // luckily we have a cached version, let's use it instead of an offline message - resolve(cachedResponse); - } else { - // darn, we've got no connectivity and no cached response - reject(err); - } - }); - }); - } - }); diff --git a/packages/qwik-city/src/runtime/src/service-worker/cached-fetch.unit.ts b/packages/qwik-city/src/runtime/src/service-worker/cached-fetch.unit.ts deleted file mode 100644 index 5b730f0b1eb..00000000000 --- a/packages/qwik-city/src/runtime/src/service-worker/cached-fetch.unit.ts +++ /dev/null @@ -1,141 +0,0 @@ -/* eslint-disable no-empty-pattern */ -import { assert, beforeEach, test } from 'vitest'; -import { cachedFetch } from './cached-fetch'; -import type { AwaitingRequests, Fetch } from './types'; - -function mockRequest(url: string): Request { - return { - url, - } as any; -} - -function mockResponse(url: string, body: string, ok = true): Response { - return { url, body, ok, clone: () => ({ body, ok }) } as any; -} - -interface TestContext { - cache: TestCache; - fetchRequests: number; - fetchSuccess: Map; - fetchError: Map; - fetch: Fetch; - addFetchSuccess: (response: Response) => void; - addFetchError: (url: string, e: Error) => void; - awaitingRequests: AwaitingRequests; -} - -interface TestCache extends Cache { - get: (url: string) => Response | undefined; -} - -let ctx: TestContext; - -beforeEach(() => { - const cacheStore = new Map(); - ctx = { - cache: { - match: async (url: string) => { - return cacheStore.get(url); - }, - put: async (url: string, response: Response) => { - cacheStore.set(url, response); - }, - get: (url: string) => cacheStore.get(url), - } as TestCache, - fetchRequests: 0, - fetchSuccess: new Map(), - fetchError: new Map(), - fetch: async (r: Request) => { - ctx.fetchRequests++; - const e = ctx.fetchError.get(r.url); - if (e) { - throw e; - } - return ctx.fetchSuccess.get(r.url)!; - }, - addFetchSuccess: (r) => ctx.fetchSuccess.set(r.url, r), - addFetchError: (url: string, e: Error) => { - ctx.fetchError.set(url, e); - }, - awaitingRequests: new Map(), - }; -}); - -test('new request, Failed to fetch', async () => { - const req = mockRequest('/a.js'); - ctx.addFetchError('/a.js', new Error('Failed to fetch')); - - try { - const promises: Promise[] = []; - for (let i = 0; i < 10; i++) { - promises.push(cachedFetch(ctx.cache, ctx.fetch, ctx.awaitingRequests, req)); - } - assert.deepEqual(ctx.awaitingRequests.size, 1); - assert.deepEqual(ctx.awaitingRequests.get('/a.js')?.length, 10); - await Promise.all(promises); - throw new Error('should have thrown'); - } catch (e: any) { - assert.deepEqual(e.message, 'Failed to fetch'); - assert.deepEqual(ctx.fetchRequests, 1); - assert.deepEqual(ctx.awaitingRequests.size, 0); - } -}); - -test('new request, Failed to fetch', async () => { - const req = mockRequest('/a.js'); - ctx.addFetchError('/a.js', new Error('Failed to fetch')); - - try { - await cachedFetch(ctx.cache, ctx.fetch, ctx.awaitingRequests, req); - throw new Error('should have thrown'); - } catch (e: any) { - assert.deepEqual(e.message, 'Failed to fetch'); - assert.deepEqual(ctx.fetchRequests, 1); - assert.deepEqual(ctx.awaitingRequests.size, 0); - } -}); - -test('new request, no existing cache, cache ok response', async () => { - const req = mockRequest('/a.js'); - ctx.addFetchSuccess(mockResponse('/a.js', 'a')); - - const promises: Promise[] = []; - for (let i = 0; i < 10; i++) { - promises.push(cachedFetch(ctx.cache, ctx.fetch, ctx.awaitingRequests, req)); - } - assert.deepEqual(ctx.awaitingRequests.size, 1); - assert.deepEqual(ctx.awaitingRequests.get('/a.js')?.length, 10); - const responses = await Promise.all(promises); - assert.deepEqual(responses.length, 10); - assert.deepEqual(responses[0].body, 'a'); - assert.deepEqual(responses[1].body, 'a'); - assert.deepEqual(ctx.cache.get('/a.js')?.body as any, 'a'); -}); - -test('new request, no existing cache, do not cache 404 response', async () => { - const req = mockRequest('/a.js'); - ctx.addFetchSuccess(mockResponse('/a.js', '404', false)); - - const promises: Promise[] = []; - for (let i = 0; i < 10; i++) { - promises.push(cachedFetch(ctx.cache, ctx.fetch, ctx.awaitingRequests, req)); - } - assert.deepEqual(ctx.awaitingRequests.size, 1); - assert.deepEqual(ctx.awaitingRequests.get('/a.js')?.length, 10); - const responses = await Promise.all(promises); - assert.deepEqual(responses.length, 10); - assert.deepEqual(responses[0].body, '404'); - assert.deepEqual(responses[1].body, '404'); - assert.deepEqual(ctx.cache.get('/a.js'), undefined); -}); - -test('new request, no cache', async () => { - const req = mockRequest('/abc.js'); - const fetchRes = mockResponse('/abc.js', 'abc'); - ctx.addFetchSuccess(fetchRes); - - const res = await cachedFetch(ctx.cache, ctx.fetch, ctx.awaitingRequests, req); - assert.deepEqual(res.body as any, 'abc'); - assert.deepEqual(ctx.fetchRequests, 1); - assert.deepEqual(ctx.awaitingRequests.size, 0); -}); diff --git a/packages/qwik-city/src/runtime/src/service-worker/constants.ts b/packages/qwik-city/src/runtime/src/service-worker/constants.ts deleted file mode 100644 index 21a6bed6974..00000000000 --- a/packages/qwik-city/src/runtime/src/service-worker/constants.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { AwaitingRequests } from './types'; - -export const qBuildCacheName = 'QwikBuild'; - -export const existingPrefetchUrls = new Set(); - -export const awaitingRequests: AwaitingRequests = new Map(); - -export const prefetchQueue: string[] = []; diff --git a/packages/qwik-city/src/runtime/src/service-worker/index.ts b/packages/qwik-city/src/runtime/src/service-worker/index.ts index 6f494c79fed..bfab0367053 100644 --- a/packages/qwik-city/src/runtime/src/service-worker/index.ts +++ b/packages/qwik-city/src/runtime/src/service-worker/index.ts @@ -1,13 +1,13 @@ -import type { AppBundle, LinkBundle } from './types'; -import { setupServiceWorkerScope } from './setup'; - -/** @public */ -export const setupServiceWorker = () => { - if (typeof self !== 'undefined' && typeof appBundles !== 'undefined') { - setupServiceWorkerScope(self as any, appBundles, libraryBundleIds, linkBundles); - } -}; - -declare const appBundles: AppBundle[]; -declare const libraryBundleIds: number[]; -declare const linkBundles: LinkBundle[]; +/** + * @deprecated This is no longer needed, Qwik now automatically embeds preloading logic into the + * application. + * + * If your service-worker.ts file contains no custom code, you should deploy to production until + * you're sure that all users picked up the new version, then you can remove it and also remove + * the `` component from your `Root.tsx`. + * + * If you do have custom service worker logic, you should keep the `service-worker.ts` file and + * `` component, but remove the `setupServiceWorker()` call. + * @public + */ +export const setupServiceWorker = () => {}; diff --git a/packages/qwik-city/src/runtime/src/service-worker/prefetch.ts b/packages/qwik-city/src/runtime/src/service-worker/prefetch.ts deleted file mode 100644 index d5ff970e5a3..00000000000 --- a/packages/qwik-city/src/runtime/src/service-worker/prefetch.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { cachedFetch } from './cached-fetch'; -import { awaitingRequests, existingPrefetchUrls, prefetchQueue } from './constants'; -import type { AppBundle, Fetch, LinkBundle } from './types'; -import { getAppBundleByName, getAppBundlesNamesFromIds } from './utils'; - -export const prefetchBundleNames = ( - appBundles: AppBundle[], - qBuildCache: Cache, - fetch: Fetch, - baseUrl: URL, - prefetchAppBundleNames: (string | null)[] | undefined | null, - highPriority = false -) => { - if (Array.isArray(prefetchAppBundleNames)) { - addBundlesToPrefetchQueue(prefetchAppBundleNames, appBundles, baseUrl, highPriority); - } - drainQueue(qBuildCache, fetch); -}; - -export function addBundlesToPrefetchQueue( - bundlesToPrefetch: (string | null)[], - appBundles: AppBundle[], - baseUrl: URL, - highPriority: boolean -) { - for (const prefetchAppBundleName of bundlesToPrefetch) { - try { - const appBundle = getAppBundleByName(appBundles, prefetchAppBundleName); - - if (appBundle) { - const importedBundleNames = getAppBundlesNamesFromIds(appBundles, appBundle[1]); - const url = new URL(prefetchAppBundleName!, baseUrl).href; - const queueIndex = prefetchQueue.indexOf(url); - - if (queueIndex > -1) { - // already in the queue - if (highPriority) { - // move to the front of the queue - prefetchQueue.splice(queueIndex, 1); - prefetchQueue.unshift(url); - } - } else { - if (highPriority) { - // add to the front of the queue - prefetchQueue.unshift(url); - } else { - // add to the end of the queue - prefetchQueue.push(url); - } - addBundlesToPrefetchQueue(importedBundleNames, appBundles, baseUrl, highPriority); - } - } - } catch (e) { - console.error(e); - } - } -} - -export function drainQueue(qBuildCache: Cache, fetch: Fetch) { - // do not prefetch more than 6 requests at a time to ensure - // the browser is able to handle a user request as soon as possible - while (prefetchQueue.length > 0 && awaitingRequests.size < 6) { - const url = prefetchQueue.shift()!; - - if (!existingPrefetchUrls.has(url!)) { - const request = new Request(url); - - existingPrefetchUrls.add(url!); - cachedFetch(qBuildCache, fetch, awaitingRequests, request) - .catch(() => { - existingPrefetchUrls.delete(url!); - }) - .finally(() => drainQueue(qBuildCache, fetch)); - } - } -} - -export const prefetchLinkBundles = ( - appBundles: AppBundle[], - libraryBundleIds: number[], - linkBundles: LinkBundle[], - qBuildCache: Cache, - fetch: Fetch, - baseUrl: URL, - linkPathnames: string[] -) => { - try { - prefetchBundleNames( - appBundles, - qBuildCache, - fetch, - baseUrl, - getAppBundlesNamesFromIds(appBundles, libraryBundleIds) - ); - } catch (e) { - console.error(e); - } - - for (const linkPathname of linkPathnames) { - try { - for (const linkBundle of linkBundles) { - const [route, linkBundleIds] = linkBundle; - console; - if (route.test(linkPathname)) { - prefetchBundleNames( - appBundles, - qBuildCache, - fetch, - baseUrl, - getAppBundlesNamesFromIds(appBundles, linkBundleIds) - ); - break; - } - } - } catch (e) { - console.error(e); - } - } -}; - -export const prefetchWaterfall = ( - appBundles: AppBundle[], - qBuildCache: Cache, - fetch: Fetch, - requestedBuildUrl: URL -) => { - try { - const { baseUrl, requestedBundleName } = splitUrlToBaseAndBundle(requestedBuildUrl); - - prefetchBundleNames(appBundles, qBuildCache, fetch, baseUrl, [requestedBundleName], true); - } catch (e) { - console.error(e); - } -}; - -function splitUrlToBaseAndBundle(fullUrl: URL) { - const segments = fullUrl.href.split('/'); - const requestedBundleName = segments[segments.length - 1]; - segments[segments.length - 1] = ''; - const baseUrl = new URL(segments.join('/')); - - return { - baseUrl, - requestedBundleName, - }; -} diff --git a/packages/qwik-city/src/runtime/src/service-worker/prefetch.unit.ts b/packages/qwik-city/src/runtime/src/service-worker/prefetch.unit.ts deleted file mode 100644 index f4696c91785..00000000000 --- a/packages/qwik-city/src/runtime/src/service-worker/prefetch.unit.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { describe } from 'node:test'; -import { afterEach, expect, test, vi } from 'vitest'; -import { cachedFetch } from './cached-fetch'; -import { awaitingRequests, existingPrefetchUrls, prefetchQueue } from './constants'; -import { addBundlesToPrefetchQueue, drainQueue } from './prefetch'; -import type { AppBundle, Fetch } from './types'; - -vi.mock('./cached-fetch'); - -function getStubWorkerCache() { - return { - add: (request: RequestInfo | URL) => Promise.resolve(), - addAll: (requests: RequestInfo[]) => Promise.resolve(), - delete: (request: RequestInfo | URL, options?: CacheQueryOptions) => Promise.resolve(true), - keys: (request?: RequestInfo | URL, options?: CacheQueryOptions) => Promise.resolve([]), - match: (request: RequestInfo | URL, options?: CacheQueryOptions) => Promise.resolve(undefined), - matchAll: (request?: RequestInfo | URL, options?: CacheQueryOptions) => Promise.resolve([]), - put: (request: RequestInfo | URL, response: Response) => Promise.resolve(), - }; -} - -function createFakeFetch(): Fetch { - return async function (request: Request) { - return new Response(); - }; -} - -afterEach(() => { - prefetchQueue.length = 0; - awaitingRequests.clear(); - existingPrefetchUrls.clear(); - vi.restoreAllMocks(); -}); - -const urlPrefix = 'http://localhost'; - -describe('addBundlesToPrefetchQueue', () => { - test(`GIVEN 3 modules with 2 imports each - WHEN attempting to prefetch the first 3 modules - THEN 9 modules should be added to the prefetching queue`, () => { - const fakeBaseUrl = new URL(urlPrefix); - const isHighPriority = false; - - const fakeAppBundles: AppBundle[] = [ - ['a.js', [1, 2]], - ['b.js', []], - ['c.js', []], - ['d.js', [4, 5]], - ['e.js', []], - ['f.js', []], - ['g.js', [7, 8]], - ['h.js', []], - ['i.js', []], - ]; - - const bundlesToPrefetch = ['a.js', 'd.js', 'g.js']; - - addBundlesToPrefetchQueue(bundlesToPrefetch, fakeAppBundles, fakeBaseUrl, isHighPriority); - - const expectedResult = [ - 'a.js', - 'b.js', - 'c.js', - 'd.js', - 'e.js', - 'f.js', - 'g.js', - 'h.js', - 'i.js', - ].map((bundle) => `${urlPrefix}/${bundle}`); - - expect(prefetchQueue).toEqual(expectedResult); - }); - test(`GIVEN 5 modules each importing the following one - WHEN attempting to prefetch the first module - THEN all 5 modules should be added to the prefetching queue`, () => { - const fakeBaseUrl = new URL(urlPrefix); - const isHighPriority = false; - - const fakeAppBundles: AppBundle[] = [ - ['a.js', [1]], - ['b.js', [2]], - ['c.js', [3]], - ['d.js', [4]], - ['e.js', []], - ]; - - const bundlesToPrefetch = ['a.js']; - - addBundlesToPrefetchQueue(bundlesToPrefetch, fakeAppBundles, fakeBaseUrl, isHighPriority); - - const expectedResult = ['a.js', 'b.js', 'c.js', 'd.js', 'e.js'].map( - (bundle) => `${urlPrefix}/${bundle}` - ); - - expect(prefetchQueue).toEqual(expectedResult); - }); - test(`GIVEN prefetch queue already has 2 modules - WHEN attempting to prefetch again the second module from the queue but with a high priority - THEN the second module should be added in front of the first`, () => { - const fakeBaseUrl = new URL(urlPrefix); - const isHighPriority = true; - - const fakeAppBundles: AppBundle[] = [ - ['a.js', []], - ['b.js', []], - ]; - - prefetchQueue.push(`${urlPrefix}/a.js`, `${urlPrefix}/b.js`); - - const bundlesToPrefetch = ['b.js']; - - addBundlesToPrefetchQueue(bundlesToPrefetch, fakeAppBundles, fakeBaseUrl, isHighPriority); - - const expectedResult = ['b.js', 'a.js'].map((bundle) => `${urlPrefix}/${bundle}`); - - expect(prefetchQueue).toEqual(expectedResult); - }); - test(`GIVEN prefetch queue already has module "a" - WHEN attempting to prefetch module "b" with a high priority - THEN module "b" should be added in front of "a"`, () => { - const fakeBaseUrl = new URL(urlPrefix); - const isHighPriority = true; - - const fakeAppBundles: AppBundle[] = [ - ['a.js', []], - ['b.js', []], - ]; - - prefetchQueue.push(`${urlPrefix}/a.js`); - - const bundlesToPrefetch = ['b.js']; - - addBundlesToPrefetchQueue(bundlesToPrefetch, fakeAppBundles, fakeBaseUrl, isHighPriority); - - const expectedResult = ['b.js', 'a.js'].map((bundle) => `${urlPrefix}/${bundle}`); - - expect(prefetchQueue).toEqual(expectedResult); - }); -}); - -describe('drainQueue', () => { - test(`GIVEN queue with 3 urls which are successfully fetched - THEN fetch should be called 3 times - and the requests should be added to the already cached requests`, async () => { - prefetchQueue.push(`${urlPrefix}/a.js`, `${urlPrefix}/b.js`, `${urlPrefix}/c.js`); - - vi.mocked(cachedFetch).mockResolvedValue(new Response()); - - drainQueue(getStubWorkerCache(), createFakeFetch()); - - expect(cachedFetch).toHaveBeenCalledTimes(3); - await vi.waitUntil(() => existingPrefetchUrls.size > 0); - - expect(existingPrefetchUrls.size).toBe(3); - }); - - test(`GIVEN queue with 4 urls which are successfully fetched - with one repeating url - THEN fetch should be called 3 times - and the requests should be added to the already cached requests`, async () => { - prefetchQueue.push( - `${urlPrefix}/a.js`, - `${urlPrefix}/b.js`, - `${urlPrefix}/c.js`, - `${urlPrefix}/a.js` - ); - - vi.mocked(cachedFetch).mockResolvedValue(new Response()); - - drainQueue(getStubWorkerCache(), createFakeFetch()); - - expect(cachedFetch).toHaveBeenCalledTimes(3); - await vi.waitUntil(() => existingPrefetchUrls.size > 0); - - expect(existingPrefetchUrls.size).toBe(3); - }); - - test(`GIVEN queue with 2 urls, one succeed and the second fails - THEN fetch should be called 2 times - and the "already cached requests" set should be set to 1`, async () => { - prefetchQueue.push(`${urlPrefix}/a.js`, `${urlPrefix}/b.js`); - - let requestCount = 0; - - vi.mocked(cachedFetch).mockImplementation(async (): Promise => { - if (requestCount === 1) { - throw new Error('Failed to fetch'); - } - requestCount++; - return new Response(); - }); - - drainQueue(getStubWorkerCache(), createFakeFetch()); - - expect(cachedFetch).toHaveBeenCalledTimes(2); - await vi.waitUntil(() => existingPrefetchUrls.size > 0); - expect(existingPrefetchUrls.size).toBe(1); - }); -}); diff --git a/packages/qwik-city/src/runtime/src/service-worker/setup.ts b/packages/qwik-city/src/runtime/src/service-worker/setup.ts deleted file mode 100644 index 56b86c4c28b..00000000000 --- a/packages/qwik-city/src/runtime/src/service-worker/setup.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { cachedFetch } from './cached-fetch'; -import { awaitingRequests, qBuildCacheName } from './constants'; -import { prefetchBundleNames, prefetchLinkBundles, prefetchWaterfall } from './prefetch'; -import type { AppBundle, LinkBundle, ServiceWorkerMessageEvent } from './types'; -import { computeAppSymbols, getCacheToDelete, isAppBundleRequest, resolveSymbols } from './utils'; - -export const setupServiceWorkerScope = ( - swScope: ServiceWorkerGlobalScope, - appBundles: AppBundle[], - libraryBundleIds: number[], - linkBundles: LinkBundle[] -) => { - const swFetch = swScope.fetch.bind(swScope); - const appSymbols = computeAppSymbols(appBundles); - - swScope.addEventListener('activate', (event) => { - (async () => { - try { - // Delete any other caches that are not the current SW cache name - event.waitUntil( - swScope.caches.keys().then((keys) => - Promise.all( - keys.map((key) => { - if (key !== qBuildCacheName) { - return caches.delete(key); - } - }) - ) - ) - ); - - // Delete old bundles - const qBuildCache = await swScope.caches.open(qBuildCacheName); - const cachedRequestKeys = await qBuildCache.keys(); - const cachedUrls = cachedRequestKeys.map((r) => r.url); - const cachedRequestsToDelete = getCacheToDelete(appBundles, cachedUrls); - await Promise.all(cachedRequestsToDelete.map((r) => qBuildCache.delete(r))); - } catch (e) { - console.error(e); - } - })(); - }); - - swScope.addEventListener('message', async ({ data }: ServiceWorkerMessageEvent) => { - if (data.type === 'qprefetch' && typeof data.base === 'string') { - const qBuildCache = await swScope.caches.open(qBuildCacheName); - const baseUrl = new URL(data.base, swScope.origin); - - if (Array.isArray(data.links)) { - prefetchLinkBundles( - appBundles, - libraryBundleIds, - linkBundles, - qBuildCache, - swFetch, - baseUrl, - data.links - ); - } - - if (Array.isArray(data.bundles)) { - prefetchBundleNames(appBundles, qBuildCache, swFetch, baseUrl, data.bundles); - } - - if (Array.isArray(data.symbols)) { - prefetchBundleNames( - appBundles, - qBuildCache, - swFetch, - baseUrl, - resolveSymbols(appSymbols, data.symbols) - ); - } - } - }); - - swScope.addEventListener('fetch', (event: FetchEvent) => { - const request = event.request; - - if (request.method === 'GET') { - const url = new URL(request.url); - - if (isAppBundleRequest(appBundles, url.pathname)) { - event.respondWith( - swScope.caches.open(qBuildCacheName).then((qBuildCache) => { - prefetchWaterfall(appBundles, qBuildCache, swFetch, url); - return cachedFetch(qBuildCache, swFetch, awaitingRequests, request); - }) - ); - } - } - }); -}; - -declare const self: ServiceWorkerGlobalScope; diff --git a/packages/qwik-city/src/runtime/src/service-worker/types.ts b/packages/qwik-city/src/runtime/src/service-worker/types.ts deleted file mode 100644 index e3969942a7e..00000000000 --- a/packages/qwik-city/src/runtime/src/service-worker/types.ts +++ /dev/null @@ -1,30 +0,0 @@ -export interface QPrefetchData { - links?: string[]; - bundles?: string[]; - symbols?: string[]; -} - -export interface QPrefetchMessage extends QPrefetchData { - type: 'qprefetch'; - base: string; -} - -export type ServiceWorkerMessage = QPrefetchMessage; - -export interface ServiceWorkerMessageEvent { - data: ServiceWorkerMessage; -} - -export type AppSymbols = Map; -export type AppBundle = - | [bundleName: string, importedBundleIds: number[]] - | [bundleName: string, importedBundleIds: number[], symbolHashesInBundle: string[]]; - -export type LinkBundle = [routePattern: RegExp, bundleIds: number[]]; - -export type Fetch = (r: Request) => Promise; - -export type AwaitingRequests = Map< - string, - [resolve: (response: Response | PromiseLike) => void, reject: (msg: any) => void][] ->; diff --git a/packages/qwik-city/src/runtime/src/service-worker/utils.ts b/packages/qwik-city/src/runtime/src/service-worker/utils.ts deleted file mode 100644 index 80388d09457..00000000000 --- a/packages/qwik-city/src/runtime/src/service-worker/utils.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { AppBundle, AppSymbols } from './types'; - -export const getCacheToDelete = (appBundles: AppBundle[], cachedUrls: string[]) => - cachedUrls.filter((url) => !appBundles.some((appBundle) => url.endsWith(appBundle[0]))); - -export const useCache = (request: Request, response: Response | undefined) => - !!response && !hasNoCacheHeader(response); - -const hasNoCacheHeader = (r: { headers: Headers }) => { - const cacheControl = r.headers.get('Cache-Control') || ''; - return cacheControl.includes('no-cache') || cacheControl.includes('max-age=0'); -}; - -export const isAppBundleRequest = (appBundles: AppBundle[], requestPathname: string) => - appBundles.some((b) => requestPathname.endsWith('/' + b[0])); - -export const getAppBundleByName = (appBundles: AppBundle[], appBundleName: string | null) => - appBundles.find((b) => b[0] === appBundleName); - -export const getAppBundlesNamesFromIds = (appBundles: AppBundle[], bundleIds: number[]) => - bundleIds.map((bundleId) => (appBundles[bundleId] ? appBundles[bundleId][0] : null)); - -export const resolveSymbols = (appSymbols: Map, symbolsHashes: string[]) => - symbolsHashes.map((s) => appSymbols.get(s)).filter((s) => s != null) as string[]; - -export const computeAppSymbols = (appBundles: AppBundle[]): AppSymbols => { - const appSymbols = new Map(); - for (const bundle of appBundles) { - const hashes = bundle[2]; - if (hashes) { - for (const hash of hashes) { - appSymbols.set(hash, bundle[0]); - } - } - } - return appSymbols; -}; diff --git a/packages/qwik-city/src/runtime/src/service-worker/utils.unit.ts b/packages/qwik-city/src/runtime/src/service-worker/utils.unit.ts deleted file mode 100644 index 5e2b14000ee..00000000000 --- a/packages/qwik-city/src/runtime/src/service-worker/utils.unit.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Request as NodeRequest, Response as NodeResponse } from 'undici'; -import type { AppBundle } from './types'; -import { getCacheToDelete, isAppBundleRequest, useCache } from './utils'; -import { assert, test } from 'vitest'; - -test('getCacheToDelete, delete bundles no longer possible', () => { - const appBundles: AppBundle[] = [ - ['q-abc.js', [], []], - ['q-def.js', [], []], - ]; - const cachedUrls = ['https://qwik.dev/build/q-abc.js', 'https://qwik.dev/build/q-xyz.js']; - const c = getCacheToDelete(appBundles, cachedUrls); - assert.deepEqual(c, ['https://qwik.dev/build/q-xyz.js']); -}); - -test('getCacheToDelete, none to delete', () => { - const appBundles: AppBundle[] = [ - ['q-abc.js', [], []], - ['q-def.js', [], []], - ]; - const cachedUrls = ['https://qwik.dev/build/q-abc.js']; - const c = getCacheToDelete(appBundles, cachedUrls); - assert.deepEqual(c, []); -}); - -test('isAppBundleRequest, in buildBundles', () => { - const appBundles: AppBundle[] = [ - ['q-abc.js', [], []], - ['q-def.js', [], []], - ]; - const pathname = '/build/q-abc.js'; - const c = isAppBundleRequest(appBundles, pathname); - assert.deepEqual(c, true); -}); - -test('isAppBundleRequest, not in buildBundles', () => { - const appBundles: AppBundle[] = [ - ['q-abc.js', [], []], - ['q-def.js', [], []], - ]; - const pathname = '/build/q-xyz.js'; - const c = isAppBundleRequest(appBundles, pathname); - assert.deepEqual(c, false); -}); - -test('do not useCache, no response', () => { - const request = mockRequest(); - const response = undefined; - const c = useCache(request, response); - assert.deepEqual(c, false); -}); - -test('do not useCache, response has max-age=0', () => { - const request = mockRequest(); - const response = mockResponse(); - response.headers.set('cache-control', 'max-age=0'); - const c = useCache(request, response); - assert.deepEqual(c, false); -}); - -test('do not useCache, response has no-cache', () => { - const request = mockRequest(); - const response = mockResponse(); - response.headers.set('cache-control', 'no-cache'); - const c = useCache(request, response); - assert.deepEqual(c, false); -}); - -test('useCache', () => { - const request = mockRequest(); - const response = mockResponse(); - const c = useCache(request, response); - assert.deepEqual(c, true); -}); - -export function mockRequest(url?: string): Request { - url = url || 'https://qwik.dev/'; - return new NodeRequest(url) as any; -} - -export function mockResponse(body?: any): Response { - return new NodeResponse(body) as any; -} diff --git a/packages/qwik-city/src/runtime/src/sw-component.tsx b/packages/qwik-city/src/runtime/src/sw-component.tsx index fedb3ffc525..2aa9bbd8c0c 100644 --- a/packages/qwik-city/src/runtime/src/sw-component.tsx +++ b/packages/qwik-city/src/runtime/src/sw-component.tsx @@ -1,6 +1,12 @@ -import { jsx } from '@builder.io/qwik'; import swRegister from '@qwik-city-sw-register'; - -/** @public */ -export const ServiceWorkerRegister = (props: { nonce?: string }) => - jsx('script', { dangerouslySetInnerHTML: swRegister, nonce: props.nonce }); +import type { JSXOutput } from '@builder.io/qwik'; +/** + * Loads the service workers that are defined in the routes. Any file named `service-worker.*` (all + * JS extensions are allowed) will be picked up, bundled into a separate file, and registered as a + * service worker. + * + * @public + */ +export const ServiceWorkerRegister = (props: { nonce?: string }): JSXOutput => ( + \n```\nBy default, the `prefetchEvent` implementation will be set to `null`.\n\n\n\n\n\n[workerFetchInsert?](#)\n\n\n\n\n\n\n\n'always' \\| 'no-link-support' \\| null\n\n\n\n\n_(Optional)_ `always`: Always include the worker fetch JS runtime.\n\n`no-link-support`: Only include the worker fetch JS runtime when the browser doesn't support `` prefetch/preload/modulepreload.\n\nDefaults to `null`.\n\n\n\n", + "content": "```typescript\nexport interface PrefetchImplementation \n```\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[linkFetchPriority?](#)\n\n\n\n\n\n\n\n'auto' \\| 'low' \\| 'high' \\| null\n\n\n\n\n_(Optional)_ Value of the `` attribute when link is used. Defaults to `null`.\n\n\n
\n\n[linkInsert?](#)\n\n\n\n\n\n\n\n'js-append' \\| 'html-append' \\| null\n\n\n\n\n_(Optional)_ `js-append`: Use JS runtime to create each `` and append to the head.\n\n`html-append`: Render each `` within html, appended at the end of the body.\n\nDefaults to `js-append`.\n\n\n
\n\n[linkRel?](#)\n\n\n\n\n\n\n\n'prefetch' \\| 'preload' \\| 'modulepreload' \\| null\n\n\n\n\n_(Optional)_ Value of the `` attribute when link is used. Defaults to `modulepreload`.\n\n\n
\n\n[prefetchEvent?](#)\n\n\n\n\n\n\n\n'always' \\| null\n\n\n\n\n_(Optional)_ Dispatch a `qprefetch` event with detail data containing the bundles that should be prefetched. The event dispatch script will be inlined into the document's HTML so any listeners of this event should already be ready to handle the event.\n\nThis implementation will inject a script similar to:\n\n```\n\n```\nBy default, the `prefetchEvent` implementation will be set to `null`.\n\n\n
\n\n[workerFetchInsert?](#)\n\n\n\n\n\n\n\n'always' \\| 'no-link-support' \\| null\n\n\n\n\n_(Optional)_ `always`: Always include the worker fetch JS runtime.\n\n`no-link-support`: Only include the worker fetch JS runtime when the browser doesn't support `` prefetch/preload/modulepreload.\n\nDefaults to `null`.\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/server/types.ts", "mdFile": "qwik.prefetchimplementation.md" }, @@ -152,7 +152,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface RenderOptions extends SerializeDocumentOptions \n```\n**Extends:** [SerializeDocumentOptions](#serializedocumentoptions)\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[base?](#)\n\n\n\n\n\n\n\nstring \\| ((options: [RenderOptions](#renderoptions)) => string)\n\n\n\n\n_(Optional)_ Specifies the root of the JS files of the client build. Setting a base, will cause the render of the `q:base` attribute in the `q:container` element.\n\n\n
\n\n[containerAttributes?](#)\n\n\n\n\n\n\n\nRecord<string, string>\n\n\n\n\n_(Optional)_\n\n\n
\n\n[containerTagName?](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n_(Optional)_ When set, the app is serialized into a fragment. And the returned html is not a complete document. Defaults to `html`\n\n\n
\n\n[locale?](#)\n\n\n\n\n\n\n\nstring \\| ((options: [RenderOptions](#renderoptions)) => string)\n\n\n\n\n_(Optional)_ Language to use when rendering the document.\n\n\n
\n\n[prefetchStrategy?](#)\n\n\n\n\n\n\n\n[PrefetchStrategy](#prefetchstrategy) \\| null\n\n\n\n\n_(Optional)_\n\n\n
\n\n[qwikLoader?](#)\n\n\n\n\n\n\n\n[QwikLoaderOptions](#qwikloaderoptions)\n\n\n\n\n_(Optional)_ Specifies if the Qwik Loader script is added to the document or not.\n\nDefaults to `{ include: true }`.\n\n\n
\n\n[qwikPrefetchServiceWorker?](#)\n\n\n\n\n\n\n\nQwikPrefetchServiceWorkerOptions\n\n\n\n\n_(Optional)_ Specifies if the Qwik Prefetch Service Worker script is added to the document or not.\n\nDefaults to `{ include: false }`. NOTE: This may be change in the future.\n\n\n
\n\n[serverData?](#)\n\n\n\n\n\n\n\nRecord<string, any>\n\n\n\n\n_(Optional)_\n\n\n
\n\n[snapshot?](#)\n\n\n\n\n\n\n\nboolean\n\n\n\n\n_(Optional)_ Defaults to `true`\n\n\n
", + "content": "```typescript\nexport interface RenderOptions extends SerializeDocumentOptions \n```\n**Extends:** [SerializeDocumentOptions](#serializedocumentoptions)\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[base?](#)\n\n\n\n\n\n\n\nstring \\| ((options: [RenderOptions](#renderoptions)) => string)\n\n\n\n\n_(Optional)_ Specifies the root of the JS files of the client build. Setting a base, will cause the render of the `q:base` attribute in the `q:container` element.\n\n\n
\n\n[containerAttributes?](#)\n\n\n\n\n\n\n\nRecord<string, string>\n\n\n\n\n_(Optional)_\n\n\n
\n\n[containerTagName?](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n_(Optional)_ When set, the app is serialized into a fragment. And the returned html is not a complete document. Defaults to `html`\n\n\n
\n\n[locale?](#)\n\n\n\n\n\n\n\nstring \\| ((options: [RenderOptions](#renderoptions)) => string)\n\n\n\n\n_(Optional)_ Language to use when rendering the document.\n\n\n
\n\n[prefetchStrategy?](#)\n\n\n\n\n\n\n\n[PrefetchStrategy](#prefetchstrategy) \\| null\n\n\n\n\n_(Optional)_\n\n\n
\n\n[qwikLoader?](#)\n\n\n\n\n\n\n\n[QwikLoaderOptions](#qwikloaderoptions)\n\n\n\n\n_(Optional)_ Specifies if the Qwik Loader script is added to the document or not.\n\nDefaults to `{ include: true }`.\n\n\n
\n\n[qwikPrefetchServiceWorker?](#)\n\n\n\n\n\n\n\nQwikPrefetchServiceWorkerOptions\n\n\n\n\n_(Optional)_\n\n\n
\n\n[serverData?](#)\n\n\n\n\n\n\n\nRecord<string, any>\n\n\n\n\n_(Optional)_\n\n\n
\n\n[snapshot?](#)\n\n\n\n\n\n\n\nboolean\n\n\n\n\n_(Optional)_ Defaults to `true`\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/server/types.ts", "mdFile": "qwik.renderoptions.md" }, diff --git a/packages/docs/src/routes/api/qwik-server/index.md b/packages/docs/src/routes/api/qwik-server/index.md index 12ad8ba55dc..904a8c646e2 100644 --- a/packages/docs/src/routes/api/qwik-server/index.md +++ b/packages/docs/src/routes/api/qwik-server/index.md @@ -258,11 +258,11 @@ _(Optional)_ Value of the `` attribute when link is us -_(Optional)_ `js-append`: Use JS runtime to create each `` and append to the body. +_(Optional)_ `js-append`: Use JS runtime to create each `` and append to the head. `html-append`: Render each `` within html, appended at the end of the body. -Defaults to `html-append`. +Defaults to `js-append`. @@ -647,9 +647,7 @@ QwikPrefetchServiceWorkerOptions -_(Optional)_ Specifies if the Qwik Prefetch Service Worker script is added to the document or not. - -Defaults to `{ include: false }`. NOTE: This may be change in the future. +_(Optional)_ diff --git a/packages/qwik/src/core/qrl/preload.ts b/packages/qwik/src/core/qrl/preload.ts index 54a33cbd991..e449a96575a 100644 --- a/packages/qwik/src/core/qrl/preload.ts +++ b/packages/qwik/src/core/qrl/preload.ts @@ -101,24 +101,27 @@ export const loadBundleGraph = (element: Element) => { }); }; -let canModulePreload: boolean | null = null; -const makePreloadLink = (bundle: BundleImport, priority: boolean) => { - const link = document.createElement('link'); - if (canModulePreload === null) { - if (link.relList.supports('modulepreload')) { - canModulePreload = true; - } else { - canModulePreload = false; +// we stringify this in prefetch-implementation.ts +export const makeMakePreloadLink = + /*@__PURE__*/ + (canModulePreload: boolean | null) => (url: string, priority: boolean) => { + const link = document.createElement('link'); + if (canModulePreload === null) { + if (link.relList.supports('modulepreload')) { + canModulePreload = true; + } else { + canModulePreload = false; + } } - } - link.rel = canModulePreload ? 'modulepreload' : 'preload'; - link.href = bundle.$url$!; - link.fetchPriority = priority ? 'high' : 'low'; - if (!canModulePreload) { - link.as = 'script'; - } - document.head.appendChild(link); -}; + link.rel = canModulePreload ? 'modulepreload' : 'preload'; + link.href = url; + link.fetchPriority = priority ? 'high' : 'low'; + if (!canModulePreload) { + link.as = 'script'; + } + document.head.appendChild(link); + }; +const makePreloadLink = makeMakePreloadLink(null); const prioritizeLink = (url: string) => { const link = document.querySelector(`link[href="${url}"]`) as HTMLLinkElement | null; @@ -135,9 +138,9 @@ const preloadBundle = (bundle: BundleImport, priority: boolean) => { } if (bundle.$url$) { if (bundle.$state$ === BundleImportState.None) { - makePreloadLink(bundle, priority); + makePreloadLink(bundle.$url$, priority); } else if (priority && bundle.$state$ === BundleImportState.Low) { - prioritizeLink(bundle.$url$!); + prioritizeLink(bundle.$url$); } else { return; } diff --git a/packages/qwik/src/server/api.md b/packages/qwik/src/server/api.md index 9f4bcb9c34b..e2cc92f48c0 100644 --- a/packages/qwik/src/server/api.md +++ b/packages/qwik/src/server/api.md @@ -47,6 +47,7 @@ export interface PrefetchImplementation { linkInsert?: 'js-append' | 'html-append' | null; linkRel?: 'prefetch' | 'preload' | 'modulepreload' | null; prefetchEvent?: 'always' | null; + // @deprecated workerFetchInsert?: 'always' | 'no-link-support' | null; } @@ -90,6 +91,8 @@ export interface RenderOptions extends SerializeDocumentOptions { prefetchStrategy?: PrefetchStrategy | null; qwikLoader?: QwikLoaderOptions; // Warning: (ae-forgotten-export) The symbol "QwikPrefetchServiceWorkerOptions" needs to be exported by the entry point index.d.ts + // + // @deprecated (undocumented) qwikPrefetchServiceWorker?: QwikPrefetchServiceWorkerOptions; // (undocumented) serverData?: Record; diff --git a/packages/qwik/src/server/prefetch-implementation.ts b/packages/qwik/src/server/prefetch-implementation.ts index e1b0777e101..8c4598f4fed 100644 --- a/packages/qwik/src/server/prefetch-implementation.ts +++ b/packages/qwik/src/server/prefetch-implementation.ts @@ -1,6 +1,7 @@ import { Fragment, jsx, type JSXNode } from '@builder.io/qwik'; import { flattenPrefetchResources, getMostReferenced, workerFetchScript } from './prefetch-utils'; import type { PrefetchImplementation, PrefetchResource, PrefetchStrategy } from './types'; +import { makeMakePreloadLink } from '../core/qrl/preload'; export function applyPrefetchImplementation( base: string, @@ -31,7 +32,7 @@ export function applyPrefetchImplementation( } if (prefetchImpl.linkInsert === 'js-append') { - linkJsImplementation(prefetchNodes, prefetchResources, prefetchImpl, nonce); + linkJsImplementation(base, manifestHash, nonce, prefetchNodes, prefetchResources, prefetchImpl); } else if (prefetchImpl.workerFetchInsert === 'always') { workerFetchImplementation(prefetchNodes, prefetchResources, nonce); } @@ -90,7 +91,7 @@ function linkHtmlImplementation( href: `${base}q-bundle-graph-${manifestHash}.json`, as: 'fetch', crossorigin: 'anonymous', - priority: prefetchImpl.linkFetchPriority || undefined, + fetchpriority: prefetchImpl.linkFetchPriority || undefined, }) ); } @@ -119,57 +120,48 @@ function linkHtmlImplementation( * TODO use idle event */ function linkJsImplementation( + base: string, + manifestHash: string | undefined, + nonce: string | undefined, prefetchNodes: JSXNode[], prefetchResources: PrefetchResource[], - prefetchImpl: Required, - nonce?: string + prefetchImpl: Required ) { - const rel = prefetchImpl.linkRel || 'modulepreload'; - const priority = prefetchImpl.linkFetchPriority; - let s = ``; - - if (prefetchImpl.workerFetchInsert === 'no-link-support') { - s += `let supportsLinkRel = true;`; - } - - s += `const u=${JSON.stringify(flattenPrefetchResources(prefetchResources).keys())};`; - s += `u.map((u,i)=>{`; - - s += `const l=document.createElement('link');`; - s += `l.setAttribute("href",u);`; - s += `l.setAttribute("rel","${rel}");`; - if (priority) { - s += `l.setAttribute("fetchpriority","${priority}");`; - } - - if (prefetchImpl.workerFetchInsert === 'no-link-support') { - s += `if(i===0){`; - s += `try{`; - s += `supportsLinkRel=l.relList.supports("${rel}");`; - s += `}catch(e){}`; - s += `}`; - } - - s += `document.body.appendChild(l);`; - - s += `});`; - - if (prefetchImpl.workerFetchInsert === 'no-link-support') { - s += `if(!supportsLinkRel){`; - s += workerFetchScript(); - s += `}`; + const injector = makeMakePreloadLink.toString(); + const urls = flattenPrefetchResources(prefetchResources); + const fetchPriority = prefetchImpl.linkFetchPriority; + const forceLow = fetchPriority === 'low'; + const prio = []; + const low = []; + for (const [url, priority] of urls) { + if (!priority || forceLow) { + low.push(url); + } else { + prio.push(url); + } } - if (prefetchImpl.workerFetchInsert === 'always') { - s += workerFetchScript(); - } + // Maybe this needs to be delayed + const script = ` + var _=(${injector})(null); + ${prio.length ? `${JSON.stringify(prio)}.forEach(u=>_(u,1));` : ''} + ${low.length ? `${JSON.stringify(low)}.forEach(u=>_(u,0));` : ''} + `.replaceAll(/^\s+|\s*\n/gm, ''); prefetchNodes.push( jsx('script', { type: 'module', 'q:type': 'link-js', - dangerouslySetInnerHTML: s, + dangerouslySetInnerHTML: script, nonce, + }), + jsx('link', { + rel: 'fetch', + id: `qwik-bg-${manifestHash}`, + href: `${base}q-bundle-graph-${manifestHash}.json`, + as: 'fetch', + crossorigin: 'anonymous', + fetchpriority: prefetchImpl.linkFetchPriority || undefined, }) ); } @@ -199,7 +191,7 @@ function normalizePrefetchImplementation( } const PrefetchImplementationDefault: Required = { - linkInsert: 'html-append', + linkInsert: 'js-append', linkRel: 'modulepreload', linkFetchPriority: null, workerFetchInsert: null, diff --git a/packages/qwik/src/server/types.ts b/packages/qwik/src/server/types.ts index ae69074e793..61e9ae2449b 100644 --- a/packages/qwik/src/server/types.ts +++ b/packages/qwik/src/server/types.ts @@ -22,11 +22,11 @@ export interface PrefetchStrategy { /** @public */ export interface PrefetchImplementation { /** - * `js-append`: Use JS runtime to create each `` and append to the body. + * `js-append`: Use JS runtime to create each `` and append to the head. * * `html-append`: Render each `` within html, appended at the end of the body. * - * Defaults to `html-append`. + * Defaults to `js-append`. */ linkInsert?: 'js-append' | 'html-append' | null; /** Value of the `` attribute when link is used. Defaults to `modulepreload`. */ @@ -40,6 +40,8 @@ export interface PrefetchImplementation { * `` prefetch/preload/modulepreload. * * Defaults to `null`. + * + * @deprecated Use `linkInsert` instead */ workerFetchInsert?: 'always' | 'no-link-support' | null; /** @@ -142,11 +144,7 @@ export interface RenderOptions extends SerializeDocumentOptions { */ qwikLoader?: QwikLoaderOptions; - /** - * Specifies if the Qwik Prefetch Service Worker script is added to the document or not. - * - * Defaults to `{ include: false }`. NOTE: This may be change in the future. - */ + /** @deprecated Use `prefetchStrategy` instead */ qwikPrefetchServiceWorker?: QwikPrefetchServiceWorkerOptions; prefetchStrategy?: PrefetchStrategy | null; diff --git a/starters/e2e/e2e.containers.spec.ts b/starters/e2e/e2e.containers.spec.ts index 18bb708da90..b6eecd1d469 100644 --- a/starters/e2e/e2e.containers.spec.ts +++ b/starters/e2e/e2e.containers.spec.ts @@ -43,11 +43,14 @@ test.describe("container", () => { const bundleLink = page.locator(`link#qwik-bg-${hash}`).first(); await expect(bundleLink).toHaveAttribute("href"); - const headPreload = page - .locator(`head > link[rel="modulepreload"]`) - .first(); - await expect(headPreload).not.toBeAttached(); - await container.locator("a").click(); - await expect(headPreload).toBeAttached(); + const headPreload = page.locator(`head > link[rel="modulepreload"]`); + const beforeCount = await headPreload.count(); + + const anchor = container.locator("a"); + await anchor.click(); + await expect(anchor).toHaveText("2 / 3"); + + const afterCount = await headPreload.count(); + expect(afterCount).toBeGreaterThan(beforeCount); }); }); From eb67c8babe64d7d06b8df463bdbc06f50c704fd1 Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Wed, 26 Mar 2025 00:17:43 +0100 Subject: [PATCH 07/17] chore: clear preload links when completed --- packages/qwik/src/core/qrl/preload.ts | 7 +++---- starters/e2e/e2e.containers.spec.ts | 11 +---------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/packages/qwik/src/core/qrl/preload.ts b/packages/qwik/src/core/qrl/preload.ts index e449a96575a..755c6afbf6b 100644 --- a/packages/qwik/src/core/qrl/preload.ts +++ b/packages/qwik/src/core/qrl/preload.ts @@ -103,7 +103,6 @@ export const loadBundleGraph = (element: Element) => { // we stringify this in prefetch-implementation.ts export const makeMakePreloadLink = - /*@__PURE__*/ (canModulePreload: boolean | null) => (url: string, priority: boolean) => { const link = document.createElement('link'); if (canModulePreload === null) { @@ -119,16 +118,16 @@ export const makeMakePreloadLink = if (!canModulePreload) { link.as = 'script'; } + link.onload = link.onerror = () => link.remove(); + document.head.appendChild(link); }; -const makePreloadLink = makeMakePreloadLink(null); +const makePreloadLink = /*@__PURE__*/ makeMakePreloadLink(null); const prioritizeLink = (url: string) => { const link = document.querySelector(`link[href="${url}"]`) as HTMLLinkElement | null; if (link) { link.fetchPriority = 'high'; - } else { - console.warn(`Preload link ${url} not found`); } }; diff --git a/starters/e2e/e2e.containers.spec.ts b/starters/e2e/e2e.containers.spec.ts index b6eecd1d469..01534d4a001 100644 --- a/starters/e2e/e2e.containers.spec.ts +++ b/starters/e2e/e2e.containers.spec.ts @@ -42,15 +42,6 @@ test.describe("container", () => { const hash = await container.getAttribute("q:manifest-hash"); const bundleLink = page.locator(`link#qwik-bg-${hash}`).first(); await expect(bundleLink).toHaveAttribute("href"); - - const headPreload = page.locator(`head > link[rel="modulepreload"]`); - const beforeCount = await headPreload.count(); - - const anchor = container.locator("a"); - await anchor.click(); - await expect(anchor).toHaveText("2 / 3"); - - const afterCount = await headPreload.count(); - expect(afterCount).toBeGreaterThan(beforeCount); + // We don't have a way to check if other modules are preloaded, because the link goes away }); }); From b19b956738eeed14a368af083ccb6a56eb008e2e Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Wed, 26 Mar 2025 00:30:48 +0100 Subject: [PATCH 08/17] fix(core dev): generate correct preload urls in dev this applies to `pnpm serve`, because normally dev mode doesn't generate preload urls --- packages/qwik/src/server/prefetch-strategy.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/qwik/src/server/prefetch-strategy.ts b/packages/qwik/src/server/prefetch-strategy.ts index a28d4982191..6b4a4ae1137 100644 --- a/packages/qwik/src/server/prefetch-strategy.ts +++ b/packages/qwik/src/server/prefetch-strategy.ts @@ -5,7 +5,6 @@ import type { SnapshotResult, } from './types'; import { getBuildBase } from './utils'; -import { qDev } from '../core/util/qdev'; import type { ResolvedManifest } from '@builder.io/qwik/optimizer'; import type { QRLInternal } from '../core/qrl/qrl-class'; @@ -80,7 +79,7 @@ function addBundle( bundleFileName: string, priority: boolean ) { - const url = qDev ? bundleFileName : buildBase + bundleFileName; + const url = buildBase + bundleFileName; let prefetchResource = urls.get(url); if (!prefetchResource) { prefetchResource = { From ea509f08ae8c13c693dad03cb74aa025f327ff15 Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Wed, 26 Mar 2025 11:35:19 +0100 Subject: [PATCH 09/17] refactor(bundle-graph): plugin API, tests, optimization --- .../src/routes/api/qwik-optimizer/api.json | 14 +- .../src/routes/api/qwik-optimizer/index.md | 21 +- ...graph-modifier.ts => get-route-imports.ts} | 24 +- ...fier.unit.ts => get-route-imports.unit.ts} | 68 +- .../qwik-city/src/buildtime/vite/plugin.ts | 6 +- packages/qwik/src/optimizer/src/api.md | 7 +- packages/qwik/src/optimizer/src/index.ts | 2 +- packages/qwik/src/optimizer/src/manifest.ts | 54 +- .../src/optimizer/src/plugins/bundle-graph.ts | 123 +-- .../src/plugins/bundle-graph.unit.ts | 171 +++++ .../src/plugins/fixture-output-bundles.json | 708 ++++++++++++++++++ .../qwik/src/optimizer/src/plugins/vite.ts | 11 +- 12 files changed, 1060 insertions(+), 149 deletions(-) rename packages/qwik-city/src/buildtime/vite/{bundle-graph-modifier.ts => get-route-imports.ts} (61%) rename packages/qwik-city/src/buildtime/vite/{bundle-graph-modifier.unit.ts => get-route-imports.unit.ts} (66%) create mode 100644 packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts create mode 100644 packages/qwik/src/optimizer/src/plugins/fixture-output-bundles.json diff --git a/packages/docs/src/routes/api/qwik-optimizer/api.json b/packages/docs/src/routes/api/qwik-optimizer/api.json index bb1fe034273..e83bf994f7e 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/api.json +++ b/packages/docs/src/routes/api/qwik-optimizer/api.json @@ -20,18 +20,18 @@ "mdFile": "qwik.path.basename.md" }, { - "name": "BundleGraphModifier", - "id": "bundlegraphmodifier", + "name": "BundleGraphAdder", + "id": "bundlegraphadder", "hierarchy": [ { - "name": "BundleGraphModifier", - "id": "bundlegraphmodifier" + "name": "BundleGraphAdder", + "id": "bundlegraphadder" } ], "kind": "TypeAlias", - "content": "A function that creates a modified version of the bundle graph. Used to inject routes and their dependencies into the bundle graph.\n\n\n```typescript\nexport type BundleGraphModifier = (graph: QwikBundleGraph, manifest: QwikManifest) => QwikBundleGraph;\n```\n**References:** [QwikBundleGraph](#qwikbundlegraph), [QwikManifest](#qwikmanifest)", + "content": "A function that returns a map of bundle names to their dependencies.\n\n\n```typescript\nexport type BundleGraphAdder = (manifest: QwikManifest) => Record;\n```\n**References:** [QwikManifest](#qwikmanifest)", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts", - "mdFile": "qwik.bundlegraphmodifier.md" + "mdFile": "qwik.bundlegraphadder.md" }, { "name": "ComponentEntryStrategy", @@ -546,7 +546,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface QwikVitePluginApi \n```\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[getAssetsDir](#)\n\n\n\n\n\n\n\n() => string \\| undefined\n\n\n\n\n\n
\n\n[getClientOutDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[getClientPublicOutDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[getInsightsManifest](#)\n\n\n\n\n\n\n\n(clientOutDir?: string \\| null) => Promise<[InsightManifest](#insightmanifest) \\| null>\n\n\n\n\n\n
\n\n[getManifest](#)\n\n\n\n\n\n\n\n() => [QwikManifest](#qwikmanifest) \\| null\n\n\n\n\n\n
\n\n[getOptimizer](#)\n\n\n\n\n\n\n\n() => [Optimizer](#optimizer) \\| null\n\n\n\n\n\n
\n\n[getOptions](#)\n\n\n\n\n\n\n\n() => NormalizedQwikPluginOptions\n\n\n\n\n\n
\n\n[getRootDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[registerBundleGraphModifier](#)\n\n\n\n\n\n\n\n(modifier: [BundleGraphModifier](#bundlegraphmodifier)) => void\n\n\n\n\n\n
", + "content": "```typescript\nexport interface QwikVitePluginApi \n```\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[getAssetsDir](#)\n\n\n\n\n\n\n\n() => string \\| undefined\n\n\n\n\n\n
\n\n[getClientOutDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[getClientPublicOutDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[getInsightsManifest](#)\n\n\n\n\n\n\n\n(clientOutDir?: string \\| null) => Promise<[InsightManifest](#insightmanifest) \\| null>\n\n\n\n\n\n
\n\n[getManifest](#)\n\n\n\n\n\n\n\n() => [QwikManifest](#qwikmanifest) \\| null\n\n\n\n\n\n
\n\n[getOptimizer](#)\n\n\n\n\n\n\n\n() => [Optimizer](#optimizer) \\| null\n\n\n\n\n\n
\n\n[getOptions](#)\n\n\n\n\n\n\n\n() => NormalizedQwikPluginOptions\n\n\n\n\n\n
\n\n[getRootDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[registerBundleGraphAdder](#)\n\n\n\n\n\n\n\n(adder: [BundleGraphAdder](#bundlegraphadder)) => void\n\n\n\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/plugins/vite.ts", "mdFile": "qwik.qwikvitepluginapi.md" }, diff --git a/packages/docs/src/routes/api/qwik-optimizer/index.md b/packages/docs/src/routes/api/qwik-optimizer/index.md index 29fbf41c061..5306dd4748d 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/index.md +++ b/packages/docs/src/routes/api/qwik-optimizer/index.md @@ -52,18 +52,21 @@ _(Optional)_ string -## BundleGraphModifier +## BundleGraphAdder -A function that creates a modified version of the bundle graph. Used to inject routes and their dependencies into the bundle graph. +A function that returns a map of bundle names to their dependencies. ```typescript -export type BundleGraphModifier = ( - graph: QwikBundleGraph, - manifest: QwikManifest, -) => QwikBundleGraph; +export type BundleGraphAdder = (manifest: QwikManifest) => Record< + string, + { + imports?: string[]; + dynamicImports?: string[]; + } +>; ``` -**References:** [QwikBundleGraph](#qwikbundlegraph), [QwikManifest](#qwikmanifest) +**References:** [QwikManifest](#qwikmanifest) [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts) @@ -2250,13 +2253,13 @@ Description -[registerBundleGraphModifier](#) +[registerBundleGraphAdder](#) -(modifier: [BundleGraphModifier](#bundlegraphmodifier)) => void +(adder: [BundleGraphAdder](#bundlegraphadder)) => void diff --git a/packages/qwik-city/src/buildtime/vite/bundle-graph-modifier.ts b/packages/qwik-city/src/buildtime/vite/get-route-imports.ts similarity index 61% rename from packages/qwik-city/src/buildtime/vite/bundle-graph-modifier.ts rename to packages/qwik-city/src/buildtime/vite/get-route-imports.ts index 53acb657f0d..e5534516484 100644 --- a/packages/qwik-city/src/buildtime/vite/bundle-graph-modifier.ts +++ b/packages/qwik-city/src/buildtime/vite/get-route-imports.ts @@ -1,14 +1,9 @@ -import type { QwikBundle, QwikBundleGraph, QwikManifest } from '@builder.io/qwik/optimizer'; +import type { QwikBundle, QwikManifest } from '@builder.io/qwik/optimizer'; import { removeExtension } from '../../utils/fs'; import type { BuildRoute } from '../types'; -export function modifyBundleGraph( - routes: BuildRoute[], - originalGraph: QwikBundleGraph, - manifest: QwikManifest -) { - const graph = [...originalGraph]; - +export function getRouteImports(routes: BuildRoute[], manifest: QwikManifest) { + const result: Record = {}; routes.forEach((route) => { const routePath = removeExtension(route.filePath); const layoutPaths = route.layouts @@ -16,21 +11,18 @@ export function modifyBundleGraph( : []; const routeAndLayoutPaths = [routePath, ...layoutPaths]; - const routeDeps = []; + const imports = []; for (const [bundleName, bundle] of Object.entries(manifest.bundles)) { if (isBundlePartOfRoute(bundle, routeAndLayoutPaths)) { - const bundleIndex = originalGraph.indexOf(bundleName); - if (bundleIndex !== -1) { - routeDeps.push(bundleIndex); - } + imports.push(bundleName); } } - if (routeDeps.length > 0) { - graph.push(route.routeName, ...routeDeps); + if (imports.length > 0) { + result[route.routeName] = { imports }; } }); - return graph; + return result; } function isBundlePartOfRoute(bundle: QwikBundle, routeAndLayoutPaths: string[]) { diff --git a/packages/qwik-city/src/buildtime/vite/bundle-graph-modifier.unit.ts b/packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts similarity index 66% rename from packages/qwik-city/src/buildtime/vite/bundle-graph-modifier.unit.ts rename to packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts index 76fa855bf4f..65d70e21390 100644 --- a/packages/qwik-city/src/buildtime/vite/bundle-graph-modifier.unit.ts +++ b/packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts @@ -1,11 +1,7 @@ -import { - type QwikBundle, - type QwikBundleGraph, - type QwikManifest, -} from '@builder.io/qwik/optimizer'; +import { type QwikBundle, type QwikManifest } from '@builder.io/qwik/optimizer'; import { describe, expect, test } from 'vitest'; import type { BuildLayout, BuildRoute } from '../types'; -import { modifyBundleGraph } from './bundle-graph-modifier'; +import { getRouteImports } from './get-route-imports'; describe('modifyBundleGraph', () => { test(`GIVEN 2 routes, one with a layout @@ -36,17 +32,6 @@ describe('modifyBundleGraph', () => { } as Record, } as QwikManifest; - const fakeBundleGraph: QwikBundleGraph = [ - 'fake-bundle1.js', - 2, - 'fake-bundle-static-dep.js', - -1, - 4, - 'fake-bundle-dynamic-dep.js', - 'fake-bundle-part-of-sub-route.js', - 'fake-bundle-part-of-layout.js', - ]; - const fakeRoutes: BuildRoute[] = [ { routeName: '/', @@ -63,18 +48,22 @@ describe('modifyBundleGraph', () => { }, ] as BuildRoute[]; - const actualResult = modifyBundleGraph(fakeRoutes, fakeBundleGraph, fakeManifest); - - const expectedResult: QwikBundleGraph = [ - ...fakeBundleGraph, - '/', - 0, // fake-bundle1.js - '/subroute', - 6, // fake-bundle-part-of-sub-route.js - 7, // fake-bundle-part-of-layout.js - ]; - - expect(actualResult).toEqual(expectedResult); + const actualResult = getRouteImports(fakeRoutes, fakeManifest); + expect(actualResult).toMatchInlineSnapshot(` + { + "/": { + "imports": [ + "fake-bundle1.js", + ], + }, + "/subroute": { + "imports": [ + "fake-bundle-part-of-sub-route.js", + "fake-bundle-part-of-layout.js", + ], + }, + } + `); }); test(`GIVEN a mismatch between the bundle graph and the manifest @@ -93,8 +82,6 @@ describe('modifyBundleGraph', () => { } as Record, } as QwikManifest; - const fakeBundleGraph: QwikBundleGraph = ['fake-bundle1.js']; - const fakeRoutes: BuildRoute[] = [ { routeName: '/', @@ -102,14 +89,17 @@ describe('modifyBundleGraph', () => { }, ] as BuildRoute[]; - const actualResult = modifyBundleGraph(fakeRoutes, fakeBundleGraph, fakeManifest); - - const expectedResult: QwikBundleGraph = [ - ...fakeBundleGraph, - '/', - 0, // fake-bundle1.js - ]; + const actualResult = getRouteImports(fakeRoutes, fakeManifest); - expect(actualResult).toEqual(expectedResult); + expect(actualResult).toMatchInlineSnapshot(` + { + "/": { + "imports": [ + "fake-bundle1.js", + "fake-bundle2.js", + ], + }, + } + `); }); }); diff --git a/packages/qwik-city/src/buildtime/vite/plugin.ts b/packages/qwik-city/src/buildtime/vite/plugin.ts index 39c436da82e..47ab42f0485 100644 --- a/packages/qwik-city/src/buildtime/vite/plugin.ts +++ b/packages/qwik-city/src/buildtime/vite/plugin.ts @@ -21,7 +21,7 @@ import { generateQwikCityEntries } from '../runtime-generation/generate-entries' import { generateQwikCityPlan } from '../runtime-generation/generate-qwik-city-plan'; import { generateServiceWorkerRegister } from '../runtime-generation/generate-service-worker'; import type { BuildContext } from '../types'; -import { modifyBundleGraph } from './bundle-graph-modifier'; +import { getRouteImports } from './get-route-imports'; import { ssrDevMiddleware, staticDistMiddleware } from './dev-server'; import { imagePlugin } from './image-jsx'; import type { QwikCityPluginApi, QwikCityVitePluginOptions } from './types'; @@ -236,8 +236,8 @@ function qwikCityPlugin(userOpts?: QwikCityVitePluginOptions): any { generateBundle(_, bundles) { // client bundles if (ctx?.target === 'client') { - qwikPlugin!.api.registerBundleGraphModifier((graph, manifest) => { - return modifyBundleGraph(ctx!.routes, graph, manifest); + qwikPlugin!.api.registerBundleGraphAdder((manifest) => { + return getRouteImports(ctx!.routes, manifest); }); const entries = [...ctx.entries, ...ctx.serviceWorkers].map((entry) => { diff --git a/packages/qwik/src/optimizer/src/api.md b/packages/qwik/src/optimizer/src/api.md index 0a096142a36..3354a133cf9 100644 --- a/packages/qwik/src/optimizer/src/api.md +++ b/packages/qwik/src/optimizer/src/api.md @@ -7,7 +7,10 @@ import type { Plugin as Plugin_2 } from 'vite'; // @public -export type BundleGraphModifier = (graph: QwikBundleGraph, manifest: QwikManifest) => QwikBundleGraph; +export type BundleGraphAdder = (manifest: QwikManifest) => Record; // @public (undocumented) export interface ComponentEntryStrategy { @@ -308,7 +311,7 @@ export interface QwikVitePluginApi { // (undocumented) getRootDir: () => string | null; // (undocumented) - registerBundleGraphModifier: (modifier: BundleGraphModifier) => void; + registerBundleGraphAdder: (adder: BundleGraphAdder) => void; } // Warning: (ae-forgotten-export) The symbol "QwikVitePluginCSROptions" needs to be exported by the entry point index.d.ts diff --git a/packages/qwik/src/optimizer/src/index.ts b/packages/qwik/src/optimizer/src/index.ts index 278ec62bf81..8ce6c46e580 100644 --- a/packages/qwik/src/optimizer/src/index.ts +++ b/packages/qwik/src/optimizer/src/index.ts @@ -48,7 +48,7 @@ export type { QwikVitePluginOptions, } from './plugins/vite'; -export type { BundleGraphModifier } from './plugins/bundle-graph'; +export type { BundleGraphAdder } from './plugins/bundle-graph'; export { qwikRollup } from './plugins/rollup'; export { qwikVite } from './plugins/vite'; diff --git a/packages/qwik/src/optimizer/src/manifest.ts b/packages/qwik/src/optimizer/src/manifest.ts index 54a2a7b5375..d0ac0680142 100644 --- a/packages/qwik/src/optimizer/src/manifest.ts +++ b/packages/qwik/src/optimizer/src/manifest.ts @@ -1,6 +1,6 @@ import type { OutputBundle } from 'rollup'; import { type NormalizedQwikPluginOptions } from './plugins/plugin'; -import type { GlobalInjections, SegmentAnalysis, Path, QwikBundle, QwikManifest } from './types'; +import type { GlobalInjections, Path, QwikBundle, QwikManifest, SegmentAnalysis } from './types'; // This is just the initial prioritization of the symbols and entries // at build time so there's less work during each SSR. However, SSR should @@ -279,7 +279,6 @@ export function generateManifestFromBundles( const bundle: QwikBundle = { size: outputBundle.code.length, - hasSymbols: false, }; let hasSymbols = false; @@ -347,27 +346,40 @@ export function generateManifestFromBundles( }; } // To inspect the bundles, uncomment the following lines - // and temporarily add the writeFileSync import from fs - // writeFileSync( - // 'output-bundles.json', - // JSON.stringify( - // Object.entries(outputBundles).map(([n, b]) => [ - // n, + // import('node:fs').then((fs) => + // fs.writeFileSync( + // 'output-bundles.json', + // JSON.stringify( // { - // ...b, - // code: '', - // map: '', - // source: '', - // modules: - // 'modules' in b - // ? Object.fromEntries( - // Object.entries(b.modules).map(([k, v]) => [k, { ...v, code: '' }]) - // ) - // : undefined, + // segments, + // bundles: Object.fromEntries( + // Object.entries(outputBundles).map(([n, b]) => [ + // n, + // { + // ...b, + // // code: 'code' in b ? `` : undefined, + // map: 'map' in b ? `` : undefined, + // source: 'source' in b ? `` : undefined, + // modules: + // 'modules' in b + // ? Object.fromEntries( + // Object.entries(b.modules).map(([k, v]) => [ + // k, + // { + // ...v, + // code: + // 'code' in v ? `` : undefined, + // }, + // ]) + // ) + // : undefined, + // }, + // ]) + // ), // }, - // ]), - // null, - // '\t' + // null, + // '\t' + // ).replaceAll(process.cwd(), '') // ) // ); diff --git a/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts b/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts index a149872ef68..5acfcd3e831 100644 --- a/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts +++ b/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts @@ -1,16 +1,14 @@ import { getSymbolHash } from 'packages/qwik/src/core/qrl/qrl-class'; -import type { QwikBundleGraph, QwikManifest } from '../types'; +import type { QwikBundle, QwikBundleGraph, QwikManifest } from '../types'; /** - * A function that creates a modified version of the bundle graph. Used to inject routes and their - * dependencies into the bundle graph. + * A function that returns a map of bundle names to their dependencies. * * @public */ -export type BundleGraphModifier = ( - graph: QwikBundleGraph, +export type BundleGraphAdder = ( manifest: QwikManifest -) => QwikBundleGraph; +) => Record; const dynamicTag = ''; /** @@ -28,16 +26,58 @@ const dynamicTag = ''; */ export function convertManifestToBundleGraph( manifest: QwikManifest, - bundleGraphModifiers?: Set + bundleGraphAdders?: Set ): QwikBundleGraph { - let bundleGraph: QwikBundleGraph = []; - const graph = manifest.bundles; - if (!graph) { + const bundleGraph: QwikBundleGraph = []; + if (!manifest.bundles) { return []; } - const names = Object.keys(graph).sort(); + // All known chunks + const graph = { ...manifest.bundles }; + // Symbols + Object.assign( + graph, + Object.fromEntries( + Object.entries(manifest.mapping).map(([symbol, chunkname]) => [ + getSymbolHash(symbol), + { imports: [chunkname] } as QwikBundle, + ]) + ) + ); + // Routes etc + if (bundleGraphAdders) { + for (const adder of bundleGraphAdders) { + const result = adder(manifest); + if (result) { + Object.assign(graph, result); + } + } + } + + // Remove unused bundles + const notUsed = new Set(Object.keys(graph)); + for (const bundleName of Object.keys(graph)) { + for (const dep of graph[bundleName].imports || []) { + notUsed.delete(dep); + } + for (const dep of graph[bundleName].dynamicImports || []) { + notUsed.delete(dep); + } + } + for (const bundleName of notUsed) { + const bundle = graph[bundleName]; + if (!bundle.imports?.length && !bundle.dynamicImports?.length) { + delete graph[bundleName]; + } + } + + const names = Object.keys(graph); const map = new Map }>(); - const clearTransitiveDeps = (parentDeps: Set, seen: Set, bundleName: string) => { + const clearTransitiveDeps = ( + parentDeps: Set, + bundleName: string, + seen: Set = new Set() + ) => { const bundle = graph[bundleName]; if (!bundle) { // external dependency @@ -49,46 +89,47 @@ export function convertManifestToBundleGraph( } if (!seen.has(dep)) { seen.add(dep); - clearTransitiveDeps(parentDeps, seen, dep); + clearTransitiveDeps(parentDeps, dep, seen); } } }; + const withoutExternalDependencies = (imports: string[] | undefined) => { + return imports?.filter((dep) => graph[dep]) || []; + }; + + /** + * First pass to collect minimal dependency lists and allocate space for dependencies. Minimal + * means that if one of your dependencies depends on another of your dependencies, there's no need + * to add that other dependency. + */ for (const bundleName of names) { const bundle = graph[bundleName]; - const index = bundleGraph.length; - const deps = new Set(bundle.imports); + const deps = new Set(withoutExternalDependencies(bundle.imports)); for (const depName of deps) { - if (!graph[depName]) { - // external dependency - continue; - } - clearTransitiveDeps(deps, new Set(), depName); + clearTransitiveDeps(deps, depName); + } + const dynDeps = new Set(withoutExternalDependencies(bundle.dynamicImports)); + for (const depName of dynDeps) { + clearTransitiveDeps(dynDeps, depName); } - const internalDynamicImports = bundle.dynamicImports?.filter((d) => graph[d]) || []; // If we have a lot of dynamic imports, we don't know which ones are needed, so we don't add any // This can happen with registry bundles like for routing - if (internalDynamicImports.length > 0 && internalDynamicImports.length < 10) { + if (dynDeps.size > 0 && dynDeps.size < 10) { deps.add(dynamicTag); - for (const depName of internalDynamicImports) { + for (const depName of dynDeps) { deps.add(depName); } } - map.set(bundleName, { index, deps }); + const index = bundleGraph.length; bundleGraph.push(bundleName); - while (index + deps.size >= bundleGraph.length) { + // allocate space for the dependency indices + for (let i = 0; i < deps.size; i++) { bundleGraph.push(null!); } + map.set(bundleName, { index, deps }); } - // Add the symbols to the bundle graph - for (const [symbol, chunkname] of Object.entries(manifest.mapping)) { - const bundle = map.get(chunkname); - if (!bundle) { - console.warn(`Chunk ${chunkname} for symbol ${symbol} not found in the bundle graph.`); - } else { - bundleGraph.push(getSymbolHash(symbol), bundle.index); - } - } - // Second pass to to update dependency pointers + + // Second pass to set the dependency indices for (const bundleName of names) { const bundle = map.get(bundleName); if (!bundle) { @@ -103,19 +144,11 @@ export function convertManifestToBundleGraph( bundleGraph[index++] = -1; continue; } - const dep = map.get(depName); - if (!dep) { - console.warn(`Dependency ${depName} of ${bundleName} not found in the bundle graph.`); - continue; - } + const dep = map.get(depName)!; const depIndex = dep.index; bundleGraph[index++] = depIndex; } } - if (bundleGraphModifiers && bundleGraphModifiers.size > 0) { - for (const modifier of bundleGraphModifiers) { - bundleGraph = modifier(bundleGraph, manifest); - } - } + return bundleGraph; } diff --git a/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts b/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts new file mode 100644 index 00000000000..94ec0ab36d8 --- /dev/null +++ b/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts @@ -0,0 +1,171 @@ +import { type QwikBundle, type QwikManifest } from '@builder.io/qwik/optimizer'; +import path from 'node:path'; +import { describe, expect, test } from 'vitest'; +import { generateManifestFromBundles } from '../manifest'; +import { convertManifestToBundleGraph } from './bundle-graph'; +// You can generate this by uncommenting the writing code in manifest.ts, building, running `pnpm serve`, and visiting the perf.prod app +import outputBundles from './fixture-output-bundles.json'; + +describe('convertManifestToBundleGraph', () => { + const fakeManifest = { + bundles: { + 'app.js': { + size: 0, + imports: ['static-dep.js', '@external-dep'], + }, + 'static-dep.js': { + size: 0, + dynamicImports: ['@other', 'transitive-dep.js', 'dynamic-dep.js'], + }, + 'dynamic-dep.js': { + size: 0, + imports: ['static-dep.js', 'transitive-dep.js', '@external-dep'], + }, + 'transitive-dep.js': { + size: 0, + }, + 'not-used.js': { + size: 0, + }, + 'has-a-symbol.js': { + size: 0, + symbols: ['sym2'], + }, + } as Record, + mapping: { sym1: 'app.js', sym2: 'has-a-symbol.js' }, + symbols: {}, + manifestHash: '123', + version: '1.0.0', + } as QwikManifest; + + test('trivial example', () => { + expect(convertManifestToBundleGraph(fakeManifest)).toMatchInlineSnapshot(` + [ + "app.js", + 2, + "static-dep.js", + -1, + 5, + "dynamic-dep.js", + 2, + 8, + "transitive-dep.js", + "has-a-symbol.js", + "sym1", + 0, + "sym2", + 9, + ] + `); + }); + test('adder', () => { + expect( + convertManifestToBundleGraph( + fakeManifest, + new Set([ + (_manifest) => { + return { + // Remove dynamic imports from dynamic-dep.js + 'dynamic-dep.js': { dynamicImports: [] }, + }; + }, + (_manifest) => { + return { + // Add a route + 'dashboard/': { + imports: ['static-dep.js'], + dynamicImports: ['transitive-dep.js'], + }, + }; + }, + ]) + ) + ).toMatchInlineSnapshot(` + [ + "app.js", + 2, + "static-dep.js", + -1, + 7, + 6, + "dynamic-dep.js", + "transitive-dep.js", + "has-a-symbol.js", + "sym1", + 0, + "sym2", + 8, + "dashboard/", + 2, + -1, + 7, + ] + `); + }); + + test(`works`, () => { + const manifest = generateManifestFromBundles( + path as any, + outputBundles.segments as any, + [], + outputBundles.bundles as any, + { rootDir: '/', outDir: '/' } as any, + console.error + ); + + expect(convertManifestToBundleGraph(manifest)).toMatchInlineSnapshot(` + [ + "app.js", + 30, + 31, + -1, + 19, + "app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js", + 0, + "app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js", + 0, + "app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js", + 30, + "app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js", + 30, + "app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js", + 30, + "app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js", + 0, + "app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js", + 30, + "app.tsx_App_component_jn5XSz7NZ88.js", + 30, + 31, + -1, + 5, + 7, + 9, + 11, + 13, + 15, + 17, + "core.js", + "preload-helper.js", + "root.js", + 0, + "jn5XSz7NZ88", + 19, + "5fYbrS6ABNA", + 11, + "DDeCLEw4BYU", + 17, + "WC1qsF4RHoU", + 5, + "cibDwmrlmRI", + 7, + "dHjr9JWbW34", + 9, + "rpMfjSetIeI", + 13, + "wEyctjlC58Q", + 15, + ] + `); + }); +}); diff --git a/packages/qwik/src/optimizer/src/plugins/fixture-output-bundles.json b/packages/qwik/src/optimizer/src/plugins/fixture-output-bundles.json new file mode 100644 index 00000000000..14217a1ab6a --- /dev/null +++ b/packages/qwik/src/optimizer/src/plugins/fixture-output-bundles.json @@ -0,0 +1,708 @@ +{ + "segments": [ + { + "origin": "components/app/app.tsx", + "name": "App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI", + "entry": null, + "displayName": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_2", + "hash": "cibDwmrlmRI", + "canonicalFilename": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI", + "path": "components/app", + "extension": "js", + "parent": "App_component_jn5XSz7NZ88", + "ctxKind": "eventHandler", + "ctxName": "onClick$", + "captures": true, + "loc": [2427, 2502] + }, + { + "origin": "components/app/app.tsx", + "name": "App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34", + "entry": null, + "displayName": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_3", + "hash": "dHjr9JWbW34", + "canonicalFilename": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34", + "path": "components/app", + "extension": "js", + "parent": "App_component_jn5XSz7NZ88", + "ctxKind": "eventHandler", + "ctxName": "onClick$", + "captures": true, + "loc": [2769, 3038] + }, + { + "origin": "components/app/app.tsx", + "name": "App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU", + "entry": null, + "displayName": "app.tsx_App_component_div_table_tbody_tr_td_a_onClick", + "hash": "DDeCLEw4BYU", + "canonicalFilename": "app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU", + "path": "components/app", + "extension": "js", + "parent": "App_component_jn5XSz7NZ88", + "ctxKind": "eventHandler", + "ctxName": "onClick$", + "captures": true, + "loc": [4339, 4357] + }, + { + "origin": "components/app/app.tsx", + "name": "App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU", + "entry": null, + "displayName": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_1", + "hash": "WC1qsF4RHoU", + "canonicalFilename": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU", + "path": "components/app", + "extension": "js", + "parent": "App_component_jn5XSz7NZ88", + "ctxKind": "eventHandler", + "ctxName": "onClick$", + "captures": true, + "loc": [2144, 2181] + }, + { + "origin": "components/app/app.tsx", + "name": "App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q", + "entry": null, + "displayName": "app.tsx_App_component_div_div_div_div_div_div_button_onClick", + "hash": "wEyctjlC58Q", + "canonicalFilename": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q", + "path": "components/app", + "extension": "js", + "parent": "App_component_jn5XSz7NZ88", + "ctxKind": "eventHandler", + "ctxName": "onClick$", + "captures": true, + "loc": [1859, 1895] + }, + { + "origin": "components/app/app.tsx", + "name": "App_component_jn5XSz7NZ88", + "entry": null, + "displayName": "app.tsx_App_component", + "hash": "jn5XSz7NZ88", + "canonicalFilename": "app.tsx_App_component_jn5XSz7NZ88", + "path": "components/app", + "extension": "js", + "parent": null, + "ctxKind": "function", + "ctxName": "component$", + "captures": false, + "loc": [1334, 5133] + }, + { + "origin": "components/app/app.tsx", + "name": "App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA", + "entry": null, + "displayName": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_4", + "hash": "5fYbrS6ABNA", + "canonicalFilename": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA", + "path": "components/app", + "extension": "js", + "parent": "App_component_jn5XSz7NZ88", + "ctxKind": "eventHandler", + "ctxName": "onClick$", + "captures": true, + "loc": [3289, 3312] + }, + { + "origin": "components/app/app.tsx", + "name": "App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI", + "entry": null, + "displayName": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_5", + "hash": "rpMfjSetIeI", + "canonicalFilename": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI", + "path": "components/app", + "extension": "js", + "parent": "App_component_jn5XSz7NZ88", + "ctxKind": "eventHandler", + "ctxName": "onClick$", + "captures": true, + "loc": [3550, 3842] + } + ], + "bundles": { + "build/root.js": { + "exports": ["default"], + "facadeModuleId": "/starters/apps/perf.prod/src/root.tsx", + "isDynamicEntry": false, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/starters/apps/perf.prod/src/global.css", + "/starters/apps/perf.prod/src/root.tsx" + ], + "name": "root", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/root.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["_", "a"], + "build/app.js": ["A"], + "build/preload-helper.js": [] + }, + "imports": ["build/core.js", "build/app.js", "build/preload-helper.js"], + "modules": { + "/starters/apps/perf.prod/src/global.css": { + "code": "", + "originalLength": 45, + "removedExports": [], + "renderedExports": [], + "renderedLength": 0 + }, + "/starters/apps/perf.prod/src/root.tsx": { + "code": "", + "originalLength": 356, + "removedExports": [], + "renderedExports": ["default"], + "renderedLength": 1007 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "import { _ as _jsxQ, a as _jsxC } from \"./core.js\";\nimport { A as App } from \"./app.js\";\nimport \"./preload-helper.js\";\nconst root = () => {\n return /* @__PURE__ */ _jsxQ(\"html\", null, null, [\n /* @__PURE__ */ _jsxQ(\"head\", null, null, [\n /* @__PURE__ */ _jsxQ(\"meta\", null, {\n charset: \"utf-8\"\n }, null, 3, null, {\n fileName: \"root.tsx\",\n lineNumber: 9,\n columnNumber: 9\n }),\n /* @__PURE__ */ _jsxQ(\"title\", null, null, \"Qwik Blank App\", 3, null, {\n fileName: \"root.tsx\",\n lineNumber: 10,\n columnNumber: 9\n }),\n /* @__PURE__ */ _jsxQ(\"meta\", null, {\n name: \"viewport\",\n content: \"width=device-width, initial-scale=1.0\"\n }, null, 3, null, {\n fileName: \"root.tsx\",\n lineNumber: 11,\n columnNumber: 9\n })\n ], 3, null, {\n fileName: \"root.tsx\",\n lineNumber: 8,\n columnNumber: 7\n }),\n /* @__PURE__ */ _jsxQ(\"body\", null, null, /* @__PURE__ */ _jsxC(App, null, 3, \"0t_0\"), 1, null, {\n fileName: \"root.tsx\",\n lineNumber: 13,\n columnNumber: 7\n })\n ], 1, \"0t_1\");\n};\nexport {\n root as default\n};\n", + "map": "", + "preliminaryFileName": "build/root.js", + "sourcemapFileName": null + }, + "build/core.js": { + "exports": ["_", "a", "b", "c", "q", "u"], + "facadeModuleId": null, + "isDynamicEntry": false, + "isEntry": false, + "isImplicitEntry": false, + "moduleIds": ["@builder.io/qwik/build", "/packages/qwik/dist/core.mjs"], + "name": "core", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/core.js", + "implicitlyLoadedBefore": [], + "importedBindings": {}, + "imports": [], + "modules": { + "@builder.io/qwik/build": { + "code": "", + "originalLength": 115, + "removedExports": ["isBrowser", "isDev"], + "renderedExports": ["isServer"], + "renderedLength": 49 + }, + "/packages/qwik/dist/core.mjs": { + "code": "", + "originalLength": 258665, + "removedExports": [ + "HTMLFragment", + "PrefetchGraph", + "PrefetchServiceWorker", + "RenderOnce", + "Resource", + "SSRComment", + "SSRHint", + "SSRRaw", + "SSRStream", + "SSRStreamBlock", + "_deserializeData", + "_fnSignal", + "_getContextElement", + "_getContextEvent", + "_hW", + "_jsxBranch", + "_jsxS", + "_noopQrl", + "_noopQrlDEV", + "_qrlSync", + "_regSymbol", + "_renderSSR", + "_restProps", + "_serializeData", + "_verifySerializable", + "_waitUntilRendered", + "_weakSerialize", + "_wrapProp", + "_wrapSignal", + "component$", + "createComputed$", + "createComputedQrl", + "createElement", + "createSignal", + "event$", + "eventQrl", + "getLocale", + "h", + "implicit$FirstArg", + "inlinedQrl", + "inlinedQrlDEV", + "isBrowser", + "isDev", + "jsx", + "jsxDEV", + "jsxs", + "render", + "setPlatform", + "sync$", + "useComputed$", + "useComputedQrl", + "useConstant", + "useContext", + "useContextProvider", + "useErrorBoundary", + "useId", + "useOn", + "useOnDocument", + "useOnWindow", + "useResource$", + "useResourceQrl", + "useServerData", + "useSignal", + "useStyles$", + "useStylesQrl", + "useStylesScoped$", + "useStylesScopedQrl", + "useTask$", + "useTaskQrl", + "useVisibleTask$", + "useVisibleTaskQrl", + "version", + "withLocale" + ], + "renderedExports": [ + "$", + "Fragment", + "SkipRender", + "Slot", + "_IMMUTABLE", + "_jsxC", + "_jsxQ", + "_pauseFromContexts", + "_preload", + "componentQrl", + "createContextId", + "getPlatform", + "isServer", + "isSignal", + "noSerialize", + "qrl", + "qrlDEV", + "untrack", + "unwrapStore", + "useLexicalScope", + "useStore" + ], + "renderedLength": 164160 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "removed for fixture", + "map": "", + "preliminaryFileName": "build/core.js", + "sourcemapFileName": null + }, + "build/app.js": { + "exports": ["A", "b"], + "facadeModuleId": null, + "isDynamicEntry": false, + "isEntry": false, + "isImplicitEntry": false, + "moduleIds": ["/starters/apps/perf.prod/src/components/app/app.tsx"], + "name": "app", + "type": "chunk", + "dynamicImports": ["build/app.tsx_App_component_jn5XSz7NZ88.js"], + "fileName": "build/app.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/preload-helper.js": ["_"], + "build/core.js": ["c", "q"] + }, + "imports": ["build/preload-helper.js", "build/core.js"], + "modules": { + "/starters/apps/perf.prod/src/components/app/app.tsx": { + "code": "", + "originalLength": 5135, + "removedExports": [], + "renderedExports": ["buildData", "App"], + "renderedLength": 1351 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "import { _ as __vitePreload } from \"./preload-helper.js\";\nimport { c as componentQrl, q as qrlDEV } from \"./core.js\";\nlet idCounter = 1;\nconst adjectives = [\n \"pretty\",\n \"large\",\n \"big\",\n \"small\",\n \"tall\",\n \"short\",\n \"long\",\n \"handsome\",\n \"plain\",\n \"quaint\",\n \"clean\",\n \"elegant\",\n \"easy\",\n \"angry\",\n \"crazy\",\n \"helpful\",\n \"mushy\",\n \"odd\",\n \"unsightly\",\n \"adorable\",\n \"important\",\n \"inexpensive\",\n \"cheap\",\n \"expensive\",\n \"fancy\"\n], colours = [\n \"red\",\n \"yellow\",\n \"blue\",\n \"green\",\n \"pink\",\n \"brown\",\n \"purple\",\n \"brown\",\n \"white\",\n \"black\",\n \"orange\"\n], nouns = [\n \"table\",\n \"chair\",\n \"house\",\n \"bbq\",\n \"desk\",\n \"car\",\n \"pony\",\n \"cookie\",\n \"sandwich\",\n \"burger\",\n \"pizza\",\n \"mouse\",\n \"keyboard\"\n];\nfunction _random(max) {\n return Math.round(Math.random() * 1e3) % max;\n}\nfunction buildData(count) {\n const data = new Array(count);\n for (let i = 0; i < count; i++) data[i] = {\n id: idCounter++,\n label: `${adjectives[_random(adjectives.length)]} ${colours[_random(colours.length)]} ${nouns[_random(nouns.length)]}`\n };\n return data;\n}\nconst App = /* @__PURE__ */ componentQrl(/* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_jn5XSz7NZ88.js\"), true ? [] : void 0), \"App_component_jn5XSz7NZ88\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 1334,\n hi: 5133,\n displayName: \"app.tsx_App_component\"\n}));\nexport {\n App as A,\n buildData as b\n};\n", + "map": "", + "preliminaryFileName": "build/app.js", + "sourcemapFileName": null + }, + "build/preload-helper.js": { + "exports": ["_"], + "facadeModuleId": null, + "isDynamicEntry": false, + "isEntry": false, + "isImplicitEntry": false, + "moduleIds": ["\u0000vite/preload-helper.js"], + "name": "preload-helper", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/preload-helper.js", + "implicitlyLoadedBefore": [], + "importedBindings": {}, + "imports": [], + "modules": { + "\u0000vite/preload-helper.js": { + "code": "", + "originalLength": 2281, + "removedExports": [], + "renderedExports": ["__vitePreload"], + "renderedLength": 1945 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "const scriptRel = function detectScriptRel() {\n const relList = typeof document !== \"undefined\" && document.createElement(\"link\").relList;\n return relList && relList.supports && relList.supports(\"modulepreload\") ? \"modulepreload\" : \"preload\";\n}();\nconst assetsURL = function(dep) {\n return \"/perf.prod/\" + dep;\n};\nconst seen = {};\nconst __vitePreload = function preload(baseModule, deps, importerUrl) {\n let promise = Promise.resolve();\n if (deps && deps.length > 0) {\n document.getElementsByTagName(\"link\");\n const cspNonceMeta = document.querySelector(\n \"meta[property=csp-nonce]\"\n );\n const cspNonce = (cspNonceMeta == null ? void 0 : cspNonceMeta.nonce) || (cspNonceMeta == null ? void 0 : cspNonceMeta.getAttribute(\"nonce\"));\n promise = Promise.all(\n deps.map((dep) => {\n dep = assetsURL(dep);\n if (dep in seen) return;\n seen[dep] = true;\n const isCss = dep.endsWith(\".css\");\n const cssSelector = isCss ? '[rel=\"stylesheet\"]' : \"\";\n if (document.querySelector(`link[href=\"${dep}\"]${cssSelector}`)) {\n return;\n }\n const link = document.createElement(\"link\");\n link.rel = isCss ? \"stylesheet\" : scriptRel;\n if (!isCss) {\n link.as = \"script\";\n link.crossOrigin = \"\";\n }\n link.href = dep;\n if (cspNonce) {\n link.setAttribute(\"nonce\", cspNonce);\n }\n document.head.appendChild(link);\n if (isCss) {\n return new Promise((res, rej) => {\n link.addEventListener(\"load\", res);\n link.addEventListener(\n \"error\",\n () => rej(new Error(`Unable to preload CSS for ${dep}`))\n );\n });\n }\n })\n );\n }\n return promise.then(() => baseModule()).catch((err) => {\n const e = new Event(\"vite:preloadError\", { cancelable: true });\n e.payload = err;\n window.dispatchEvent(e);\n if (!e.defaultPrevented) {\n throw err;\n }\n });\n};\nexport {\n __vitePreload as _\n};\n", + "map": "", + "preliminaryFileName": "build/preload-helper.js", + "sourcemapFileName": null + }, + "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js": { + "exports": ["App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI"], + "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js" + ], + "name": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u"], + "build/app.js": ["b"], + "build/preload-helper.js": [] + }, + "imports": ["build/core.js", "build/app.js", "build/preload-helper.js"], + "modules": { + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js": { + "code": "", + "originalLength": 281, + "removedExports": [], + "renderedExports": ["App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI"], + "renderedLength": 182 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "import { u as useLexicalScope } from \"./core.js\";\nimport { b as buildData } from \"./app.js\";\nimport \"./preload-helper.js\";\nconst App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI = () => {\n const [state] = useLexicalScope();\n return state.data = state.data.concat(buildData(1e3));\n};\nexport {\n App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI\n};\n", + "map": "", + "preliminaryFileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js", + "sourcemapFileName": null + }, + "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js": { + "exports": ["App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34"], + "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js" + ], + "name": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u"] + }, + "imports": ["build/core.js"], + "modules": { + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js": { + "code": "", + "originalLength": 272, + "removedExports": [], + "renderedExports": ["App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34"], + "renderedLength": 212 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "import { u as useLexicalScope } from \"./core.js\";\nconst App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34 = () => {\n const [state] = useLexicalScope();\n for (let i = 0, d = state.data, len = d.length; i < len; i += 10) d[i].label += \" !!!\";\n};\nexport {\n App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34\n};\n", + "map": "", + "preliminaryFileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js", + "sourcemapFileName": null + }, + "build/app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js": { + "exports": ["App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU"], + "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js" + ], + "name": "app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u"] + }, + "imports": ["build/core.js"], + "modules": { + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js": { + "code": "", + "originalLength": 198, + "removedExports": [], + "renderedExports": ["App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU"], + "renderedLength": 138 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "import { u as useLexicalScope } from \"./core.js\";\nconst App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU = () => {\n const [label] = useLexicalScope();\n return alert(label);\n};\nexport {\n App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU\n};\n", + "map": "", + "preliminaryFileName": "build/app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js", + "sourcemapFileName": null + }, + "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js": { + "exports": ["App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU"], + "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js" + ], + "name": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u"], + "build/app.js": ["b"], + "build/preload-helper.js": [] + }, + "imports": ["build/core.js", "build/app.js", "build/preload-helper.js"], + "modules": { + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js": { + "code": "", + "originalLength": 263, + "removedExports": [], + "renderedExports": ["App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU"], + "renderedLength": 164 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "import { u as useLexicalScope } from \"./core.js\";\nimport { b as buildData } from \"./app.js\";\nimport \"./preload-helper.js\";\nconst App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU = () => {\n const [state] = useLexicalScope();\n return state.data = buildData(1e4);\n};\nexport {\n App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU\n};\n", + "map": "", + "preliminaryFileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js", + "sourcemapFileName": null + }, + "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js": { + "exports": ["App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q"], + "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js" + ], + "name": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u"], + "build/app.js": ["b"], + "build/preload-helper.js": [] + }, + "imports": ["build/core.js", "build/app.js", "build/preload-helper.js"], + "modules": { + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js": { + "code": "", + "originalLength": 260, + "removedExports": [], + "renderedExports": ["App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q"], + "renderedLength": 161 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "import { u as useLexicalScope } from \"./core.js\";\nimport { b as buildData } from \"./app.js\";\nimport \"./preload-helper.js\";\nconst App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q = () => {\n const [state] = useLexicalScope();\n return state.data = buildData(1e3);\n};\nexport {\n App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q\n};\n", + "map": "", + "preliminaryFileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js", + "sourcemapFileName": null + }, + "build/app.tsx_App_component_jn5XSz7NZ88.js": { + "exports": ["App_component_jn5XSz7NZ88"], + "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_jn5XSz7NZ88.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_jn5XSz7NZ88.js" + ], + "name": "app.tsx_App_component_jn5XSz7NZ88", + "type": "chunk", + "dynamicImports": [ + "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js", + "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js", + "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js", + "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js", + "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js", + "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js", + "build/app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js" + ], + "fileName": "build/app.tsx_App_component_jn5XSz7NZ88.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/preload-helper.js": ["_"], + "build/core.js": ["b", "_", "q"] + }, + "imports": ["build/preload-helper.js", "build/core.js"], + "modules": { + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_jn5XSz7NZ88.js": { + "code": "", + "originalLength": 11168, + "removedExports": [], + "renderedExports": ["App_component_jn5XSz7NZ88"], + "renderedLength": 11377 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "import { _ as __vitePreload } from \"./preload-helper.js\";\nimport { b as useStore, _ as _jsxQ, q as qrlDEV } from \"./core.js\";\nconst App_component_jn5XSz7NZ88 = () => {\n const state = useStore({\n data: [],\n selected: null\n });\n return /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"container\"\n }, [\n /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"jumbotron\"\n }, /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"row\"\n }, [\n /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"col-md-6\"\n }, /* @__PURE__ */ _jsxQ(\"h1\", null, null, \"Qwik Keyed\", 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 88,\n columnNumber: 13\n }), 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 87,\n columnNumber: 11\n }),\n /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"col-md-6\"\n }, /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"row\"\n }, /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"col-sm-6 smallpad\"\n }, [\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n id: \"run\",\n class: \"btn btn-primary btn-block\",\n type: \"button\",\n onClick$: /* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js\"), true ? [] : void 0), \"App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 1859,\n hi: 1895,\n displayName: \"app.tsx_App_component_div_div_div_div_div_div_button_onClick\"\n }, [\n state\n ])\n }, \"Create 1,000 rows\", 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 93,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n id: \"runlots\",\n class: \"btn btn-primary btn-block\",\n type: \"button\",\n onClick$: /* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js\"), true ? [] : void 0), \"App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 2144,\n hi: 2181,\n displayName: \"app.tsx_App_component_div_div_div_div_div_div_button_onClick_1\"\n }, [\n state\n ])\n }, \"Create 10,000 rows\", 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 101,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n id: \"add\",\n class: \"btn btn-primary btn-block\",\n type: \"button\",\n onClick$: /* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js\"), true ? [] : void 0), \"App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 2427,\n hi: 2502,\n displayName: \"app.tsx_App_component_div_div_div_div_div_div_button_onClick_2\"\n }, [\n state\n ])\n }, \"Append 1,000 rows\", 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 109,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n id: \"update\",\n class: \"btn btn-primary btn-block\",\n type: \"button\",\n onClick$: /* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js\"), true ? [] : void 0), \"App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 2769,\n hi: 3038,\n displayName: \"app.tsx_App_component_div_div_div_div_div_div_button_onClick_3\"\n }, [\n state\n ])\n }, \"Update every 10th row\", 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 119,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n id: \"clear\",\n class: \"btn btn-primary btn-block\",\n type: \"button\",\n onClick$: /* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js\"), true ? [] : void 0), \"App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 3289,\n hi: 3312,\n displayName: \"app.tsx_App_component_div_div_div_div_div_div_button_onClick_4\"\n }, [\n state\n ])\n }, \"Clear\", 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 135,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n id: \"swaprows\",\n class: \"btn btn-primary btn-block\",\n type: \"button\",\n onClick$: /* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js\"), true ? [] : void 0), \"App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 3550,\n hi: 3842,\n displayName: \"app.tsx_App_component_div_div_div_div_div_div_button_onClick_5\"\n }, [\n state\n ])\n }, \"Swap Rows\", 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 143,\n columnNumber: 17\n })\n ], 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 92,\n columnNumber: 15\n }), 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 91,\n columnNumber: 13\n }), 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 90,\n columnNumber: 11\n })\n ], 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 86,\n columnNumber: 9\n }), 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 85,\n columnNumber: 7\n }),\n /* @__PURE__ */ _jsxQ(\"table\", null, {\n class: \"table table-hover table-striped test-data\"\n }, /* @__PURE__ */ _jsxQ(\"tbody\", null, null, state.data.map(({ id, label }) => {\n return /* @__PURE__ */ _jsxQ(\"tr\", {\n class: id === state.selected ? \"danger\" : \"\"\n }, null, [\n /* @__PURE__ */ _jsxQ(\"td\", null, {\n class: \"col-md-1\"\n }, id, 1, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 169,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"td\", null, {\n class: \"col-md-4\"\n }, /* @__PURE__ */ _jsxQ(\"a\", {\n onClick$: /* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js\"), true ? [] : void 0), \"App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 4339,\n hi: 4357,\n displayName: \"app.tsx_App_component_div_table_tbody_tr_td_a_onClick\"\n }, [\n label\n ])\n }, null, label, 0, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 171,\n columnNumber: 19\n }), 1, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 170,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"td\", null, {\n class: \"col-md-1\"\n }, /* @__PURE__ */ _jsxQ(\"a\", null, null, /* @__PURE__ */ _jsxQ(\"span\", null, {\n class: \"glyphicon glyphicon-remove\",\n ariaHidden: \"true\"\n }, null, 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 183,\n columnNumber: 21\n }), 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 174,\n columnNumber: 19\n }), 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 173,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"td\", null, {\n class: \"col-md-6\"\n }, null, 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 189,\n columnNumber: 17\n })\n ], 1, id, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 168,\n columnNumber: 15\n });\n }), 1, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 165,\n columnNumber: 9\n }), 1, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 164,\n columnNumber: 7\n }),\n /* @__PURE__ */ _jsxQ(\"span\", null, {\n class: \"preloadicon glyphicon glyphicon-remove\",\n ariaHidden: \"true\"\n }, null, 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 195,\n columnNumber: 7\n })\n ], 1, \"bK_0\");\n};\nexport {\n App_component_jn5XSz7NZ88\n};\n", + "map": "", + "preliminaryFileName": "build/app.tsx_App_component_jn5XSz7NZ88.js", + "sourcemapFileName": null + }, + "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js": { + "exports": ["App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA"], + "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js" + ], + "name": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u"] + }, + "imports": ["build/core.js"], + "modules": { + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js": { + "code": "", + "originalLength": 210, + "removedExports": [], + "renderedExports": ["App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA"], + "renderedLength": 150 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "import { u as useLexicalScope } from \"./core.js\";\nconst App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA = () => {\n const [state] = useLexicalScope();\n return state.data = [];\n};\nexport {\n App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA\n};\n", + "map": "", + "preliminaryFileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js", + "sourcemapFileName": null + }, + "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js": { + "exports": ["App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI"], + "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js" + ], + "name": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u"] + }, + "imports": ["build/core.js"], + "modules": { + "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js": { + "code": "", + "originalLength": 343, + "removedExports": [], + "renderedExports": ["App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI"], + "renderedLength": 283 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "import { u as useLexicalScope } from \"./core.js\";\nconst App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI = () => {\n const [state] = useLexicalScope();\n const d = state.data.slice();\n if (d.length > 998) {\n const tmp = d[1];\n d[1] = d[998];\n d[998] = tmp;\n state.data = d;\n }\n};\nexport {\n App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI\n};\n", + "map": "", + "preliminaryFileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js", + "sourcemapFileName": null + }, + "assets/97WVOY9i-style.css": { + "fileName": "assets/97WVOY9i-style.css", + "name": "style.css", + "needsCodeReference": false, + "source": "", + "type": "asset" + }, + "q-manifest.json": { + "fileName": "q-manifest.json", + "needsCodeReference": false, + "source": "", + "type": "asset" + }, + "build/q-bundle-graph-uiknax.json": { + "fileName": "build/q-bundle-graph-uiknax.json", + "needsCodeReference": false, + "source": "", + "type": "asset" + } + } +} diff --git a/packages/qwik/src/optimizer/src/plugins/vite.ts b/packages/qwik/src/optimizer/src/plugins/vite.ts index 99bc0626907..b5ffe88b5fd 100644 --- a/packages/qwik/src/optimizer/src/plugins/vite.ts +++ b/packages/qwik/src/optimizer/src/plugins/vite.ts @@ -12,7 +12,7 @@ import type { TransformModule, } from '../types'; import { versions } from '../versions'; -import { convertManifestToBundleGraph, type BundleGraphModifier } from './bundle-graph'; +import { convertManifestToBundleGraph, type BundleGraphAdder } from './bundle-graph'; import { getImageSizeServer } from './image-size-server'; import { CLIENT_OUT_DIR, @@ -93,7 +93,7 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any { return null; } - const bundleGraphModifiers = new Set(); + const bundleGraphAdders = new Set(); const api: QwikVitePluginApi = { getOptimizer: () => qwikPlugin.getOptimizer(), @@ -104,8 +104,7 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any { getClientOutDir: () => clientOutDir, getClientPublicOutDir: () => clientPublicOutDir, getAssetsDir: () => viteAssetsDir, - registerBundleGraphModifier: (modifier: BundleGraphModifier) => - bundleGraphModifiers.add(modifier), + registerBundleGraphAdder: (adder: BundleGraphAdder) => bundleGraphAdders.add(adder), }; // We provide two plugins to Vite. The first plugin is the main plugin that handles all the @@ -604,7 +603,7 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any { const useAssetsDir = !!assetsDir && assetsDir !== '_astro'; const sys = qwikPlugin.getSys(); - const bundleGraph = convertManifestToBundleGraph(manifest, bundleGraphModifiers); + const bundleGraph = convertManifestToBundleGraph(manifest, bundleGraphAdders); this.emitFile({ type: 'asset', @@ -1103,7 +1102,7 @@ export interface QwikVitePluginApi { getClientOutDir: () => string | null; getClientPublicOutDir: () => string | null; getAssetsDir: () => string | undefined; - registerBundleGraphModifier: (modifier: BundleGraphModifier) => void; + registerBundleGraphAdder: (adder: BundleGraphAdder) => void; } /** From 1f5e4ea0721bf7c9fcb24a483566140e8216c6e7 Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Wed, 26 Mar 2025 13:41:44 +0100 Subject: [PATCH 10/17] perf(qwikcity): remove qwikcity plan dyn imports from bundlegraph --- .../src/buildtime/vite/get-route-imports.ts | 12 +++++++++++- .../src/buildtime/vite/get-route-imports.unit.ts | 8 ++++++++ packages/qwik-city/src/buildtime/vite/plugin.ts | 2 +- .../qwik/src/optimizer/src/plugins/bundle-graph.ts | 5 ++--- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/packages/qwik-city/src/buildtime/vite/get-route-imports.ts b/packages/qwik-city/src/buildtime/vite/get-route-imports.ts index e5534516484..4117712cd49 100644 --- a/packages/qwik-city/src/buildtime/vite/get-route-imports.ts +++ b/packages/qwik-city/src/buildtime/vite/get-route-imports.ts @@ -1,9 +1,10 @@ import type { QwikBundle, QwikManifest } from '@builder.io/qwik/optimizer'; import { removeExtension } from '../../utils/fs'; import type { BuildRoute } from '../types'; +import { QWIK_CITY_PLAN_ID } from './plugin'; export function getRouteImports(routes: BuildRoute[], manifest: QwikManifest) { - const result: Record = {}; + const result: Record = {}; routes.forEach((route) => { const routePath = removeExtension(route.filePath); const layoutPaths = route.layouts @@ -22,6 +23,15 @@ export function getRouteImports(routes: BuildRoute[], manifest: QwikManifest) { result[route.routeName] = { imports }; } }); + for (const bundleName of Object.keys(manifest.bundles)) { + const bundle = manifest.bundles[bundleName]; + if (bundle.origins?.some((s) => s.endsWith(QWIK_CITY_PLAN_ID))) { + // Don't consider the city plan for preloading + // we keep imports because something might be bundled with it + result[bundleName] = { imports: bundle.imports, dynamicImports: [] }; + break; + } + } return result; } diff --git a/packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts b/packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts index 65d70e21390..5bc82a20559 100644 --- a/packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts +++ b/packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts @@ -29,6 +29,10 @@ describe('modifyBundleGraph', () => { size: 0, origins: ['src/routes/layout.tsx'], }, + 'q-city-plan.js': { + size: 0, + origins: ['@qwik-city-plan'], + }, } as Record, } as QwikManifest; @@ -62,6 +66,10 @@ describe('modifyBundleGraph', () => { "fake-bundle-part-of-layout.js", ], }, + "q-city-plan.js": { + "dynamicImports": [], + "imports": undefined, + }, } `); }); diff --git a/packages/qwik-city/src/buildtime/vite/plugin.ts b/packages/qwik-city/src/buildtime/vite/plugin.ts index 47ab42f0485..d5d6630ca54 100644 --- a/packages/qwik-city/src/buildtime/vite/plugin.ts +++ b/packages/qwik-city/src/buildtime/vite/plugin.ts @@ -315,7 +315,7 @@ function qwikCityPlugin(userOpts?: QwikCityVitePluginOptions): any { } const QWIK_SERIALIZER = '@qwik-serializer'; -const QWIK_CITY_PLAN_ID = '@qwik-city-plan'; +export const QWIK_CITY_PLAN_ID = '@qwik-city-plan'; const QWIK_CITY_ENTRIES_ID = '@qwik-city-entries'; const QWIK_CITY = '@builder.io/qwik-city'; const QWIK_CITY_SW_REGISTER = '@qwik-city-sw-register'; diff --git a/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts b/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts index 5acfcd3e831..f55236ddc8e 100644 --- a/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts +++ b/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts @@ -112,9 +112,8 @@ export function convertManifestToBundleGraph( for (const depName of dynDeps) { clearTransitiveDeps(dynDeps, depName); } - // If we have a lot of dynamic imports, we don't know which ones are needed, so we don't add any - // This can happen with registry bundles like for routing - if (dynDeps.size > 0 && dynDeps.size < 10) { + if (dynDeps.size > 0) { + // We rely on the Set keeping the items in order, everything after this is dynamic deps.add(dynamicTag); for (const depName of dynDeps) { deps.add(depName); From 537d95ab504ad1828208260eac8c18cd5f234058 Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Wed, 26 Mar 2025 16:22:20 +0100 Subject: [PATCH 11/17] feat(insights): add route hints to bundlegraph, refactor --- .../src/routes/api/qwik-optimizer/api.json | 16 +-- .../src/routes/api/qwik-optimizer/index.md | 79 ------------- .../qwik-city/src/buildtime/vite/plugin.ts | 7 +- packages/qwik-labs/src-vite/insights/index.ts | 104 ++++++++++++++---- packages/qwik/src/optimizer/src/api.md | 15 --- packages/qwik/src/optimizer/src/index.ts | 1 - .../qwik/src/optimizer/src/plugins/plugin.ts | 3 - .../qwik/src/optimizer/src/plugins/vite.ts | 49 --------- packages/qwik/src/optimizer/src/types.ts | 7 -- 9 files changed, 89 insertions(+), 192 deletions(-) diff --git a/packages/docs/src/routes/api/qwik-optimizer/api.json b/packages/docs/src/routes/api/qwik-optimizer/api.json index e83bf994f7e..1ae5bab91a8 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/api.json +++ b/packages/docs/src/routes/api/qwik-optimizer/api.json @@ -196,20 +196,6 @@ "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts", "mdFile": "qwik.inlineentrystrategy.md" }, - { - "name": "InsightManifest", - "id": "insightmanifest", - "hierarchy": [ - { - "name": "InsightManifest", - "id": "insightmanifest" - } - ], - "kind": "Interface", - "content": "```typescript\nexport interface InsightManifest \n```\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[manual](#)\n\n\n\n\n\n\n\nRecord<string, string>\n\n\n\n\n\n
\n\n[prefetch](#)\n\n\n\n\n\n\n\n{ route: string; symbols: string\\[\\]; }\\[\\]\n\n\n\n\n\n
\n\n[type](#)\n\n\n\n\n\n\n\n'smart'\n\n\n\n\n\n
", - "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts", - "mdFile": "qwik.insightmanifest.md" - }, { "name": "isAbsolute", "id": "path-isabsolute", @@ -546,7 +532,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface QwikVitePluginApi \n```\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[getAssetsDir](#)\n\n\n\n\n\n\n\n() => string \\| undefined\n\n\n\n\n\n
\n\n[getClientOutDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[getClientPublicOutDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[getInsightsManifest](#)\n\n\n\n\n\n\n\n(clientOutDir?: string \\| null) => Promise<[InsightManifest](#insightmanifest) \\| null>\n\n\n\n\n\n
\n\n[getManifest](#)\n\n\n\n\n\n\n\n() => [QwikManifest](#qwikmanifest) \\| null\n\n\n\n\n\n
\n\n[getOptimizer](#)\n\n\n\n\n\n\n\n() => [Optimizer](#optimizer) \\| null\n\n\n\n\n\n
\n\n[getOptions](#)\n\n\n\n\n\n\n\n() => NormalizedQwikPluginOptions\n\n\n\n\n\n
\n\n[getRootDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[registerBundleGraphAdder](#)\n\n\n\n\n\n\n\n(adder: [BundleGraphAdder](#bundlegraphadder)) => void\n\n\n\n\n\n
", + "content": "```typescript\nexport interface QwikVitePluginApi \n```\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[getAssetsDir](#)\n\n\n\n\n\n\n\n() => string \\| undefined\n\n\n\n\n\n
\n\n[getClientOutDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[getClientPublicOutDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[getManifest](#)\n\n\n\n\n\n\n\n() => [QwikManifest](#qwikmanifest) \\| null\n\n\n\n\n\n
\n\n[getOptimizer](#)\n\n\n\n\n\n\n\n() => [Optimizer](#optimizer) \\| null\n\n\n\n\n\n
\n\n[getOptions](#)\n\n\n\n\n\n\n\n() => NormalizedQwikPluginOptions\n\n\n\n\n\n
\n\n[getRootDir](#)\n\n\n\n\n\n\n\n() => string \\| null\n\n\n\n\n\n
\n\n[registerBundleGraphAdder](#)\n\n\n\n\n\n\n\n(adder: [BundleGraphAdder](#bundlegraphadder)) => void\n\n\n\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/plugins/vite.ts", "mdFile": "qwik.qwikvitepluginapi.md" }, diff --git a/packages/docs/src/routes/api/qwik-optimizer/index.md b/packages/docs/src/routes/api/qwik-optimizer/index.md index 5306dd4748d..ff40e16814c 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/index.md +++ b/packages/docs/src/routes/api/qwik-optimizer/index.md @@ -594,72 +594,6 @@ Description [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts) -## InsightManifest - -```typescript -export interface InsightManifest -``` - - - - - -
- -Property - - - -Modifiers - - - -Type - - - -Description - -
- -[manual](#) - - - - - -Record<string, string> - - - -
- -[prefetch](#) - - - - - -{ route: string; symbols: string[]; }[] - - - -
- -[type](#) - - - - - -'smart' - - - -
- -[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts) - ## isAbsolute ```typescript @@ -2188,19 +2122,6 @@ Description -[getInsightsManifest](#) - - - - - -(clientOutDir?: string \| null) => Promise<[InsightManifest](#insightmanifest) \| null> - - - - - - [getManifest](#) diff --git a/packages/qwik-city/src/buildtime/vite/plugin.ts b/packages/qwik-city/src/buildtime/vite/plugin.ts index d5d6630ca54..2312a0ffa19 100644 --- a/packages/qwik-city/src/buildtime/vite/plugin.ts +++ b/packages/qwik-city/src/buildtime/vite/plugin.ts @@ -95,6 +95,9 @@ function qwikCityPlugin(userOpts?: QwikCityVitePluginOptions): any { if (!qwikPlugin) { throw new Error('Missing vite-plugin-qwik'); } + qwikPlugin.api.registerBundleGraphAdder?.((manifest) => { + return getRouteImports(ctx!.routes, manifest); + }); // @ts-ignore `format` removed in Vite 5 if (config.ssr?.format === 'cjs') { @@ -236,10 +239,6 @@ function qwikCityPlugin(userOpts?: QwikCityVitePluginOptions): any { generateBundle(_, bundles) { // client bundles if (ctx?.target === 'client') { - qwikPlugin!.api.registerBundleGraphAdder((manifest) => { - return getRouteImports(ctx!.routes, manifest); - }); - const entries = [...ctx.entries, ...ctx.serviceWorkers].map((entry) => { return { chunkFileName: entry.chunkFileName, diff --git a/packages/qwik-labs/src-vite/insights/index.ts b/packages/qwik-labs/src-vite/insights/index.ts index ca1cfbfed6c..7010e558bbf 100644 --- a/packages/qwik-labs/src-vite/insights/index.ts +++ b/packages/qwik-labs/src-vite/insights/index.ts @@ -1,8 +1,7 @@ -import { type QwikVitePluginOptions } from '@builder.io/qwik/optimizer'; -import { existsSync, mkdirSync } from 'fs'; -import { readFile, writeFile } from 'fs/promises'; -import { join } from 'node:path'; -import { resolve } from 'path'; +import { type QwikVitePlugin } from '@builder.io/qwik/optimizer'; +import { existsSync, mkdirSync } from 'node:fs'; +import { readFile, writeFile } from 'node:fs/promises'; +import { join, resolve } from 'node:path'; import { type PluginOption } from 'vite'; const logWarn = (message?: any, ...rest) => { @@ -15,6 +14,12 @@ const log = (message?: any) => { console.log('\x1b[35m%s\x1b[0m', `qwikInsight(): ${message}`); }; +/** @public */ +export interface InsightManifest { + manual: Record; + prefetch: { route: string; symbols: string[] }[]; +} + export async function qwikInsights(qwikInsightsOpts: { publicApiKey: string; baseUrl?: string; @@ -22,28 +27,89 @@ export async function qwikInsights(qwikInsightsOpts: { }): Promise { const { publicApiKey, baseUrl = 'https://insights.qwik.dev', outDir = '' } = qwikInsightsOpts; let isProd = false; + let jsonDir: string; + let jsonFile: string; + let data: InsightManifest | null = null; + let qwikVitePlugin: QwikVitePlugin | null = null; + + async function loadQwikInsights(): Promise { + if (data) { + return data; + } + if (existsSync(jsonFile)) { + log('Reading Qwik Insight data from: ' + jsonFile); + return (data = JSON.parse(await readFile(jsonFile, 'utf-8')) as InsightManifest); + } + return null; + } + const vitePlugin: PluginOption = { name: 'vite-plugin-qwik-insights', enforce: 'pre', apply: 'build', async config(viteConfig) { + jsonDir = resolve(viteConfig.root || '.', outDir); + jsonFile = join(jsonDir, 'q-insights.json'); isProd = viteConfig.mode !== 'ssr'; - if (isProd) { - const qManifest: QwikVitePluginOptions['entryStrategy'] = { type: 'smart' }; - try { - const response = await fetch(`${baseUrl}/api/v1/${publicApiKey}/bundles/strategy/`); - const strategy = await response.json(); - Object.assign(qManifest, strategy); - const path = resolve(viteConfig.root || '.', outDir); - const pathJson = join(path, 'q-insights.json'); - mkdirSync(path, { recursive: true }); - log('Fetched latest Qwik Insight data into: ' + pathJson); - await writeFile(pathJson, JSON.stringify(qManifest)); - } catch (e) { - logWarn('Failed to fetch manifest from Insights DB', e); + }, + configResolved: { + // we want to register the bundle graph adder last so we overwrite existing routes + order: 'post', + async handler(config) { + qwikVitePlugin = config.plugins.find( + (p) => p.name === 'vite-plugin-qwik' + ) as QwikVitePlugin; + if (!qwikVitePlugin) { + throw new Error('Missing vite-plugin-qwik'); } - } + const opts = qwikVitePlugin.api.getOptions(); + if (opts.entryStrategy.type !== 'smart') { + return; + } + if (isProd) { + try { + const qManifest: InsightManifest = { manual: {}, prefetch: [] }; + const response = await fetch(`${baseUrl}/api/v1/${publicApiKey}/bundles/strategy/`); + const strategy = await response.json(); + Object.assign(qManifest, strategy); + data = qManifest; + mkdirSync(jsonDir, { recursive: true }); + log('Fetched latest Qwik Insight data into: ' + jsonFile); + await writeFile(jsonFile, JSON.stringify(qManifest)); + } catch (e) { + logWarn('Failed to fetch manifest from Insights DB', e); + await loadQwikInsights(); + } + } else { + await loadQwikInsights(); + } + + if (data) { + opts.entryStrategy.manual = { ...data.manual, ...opts.entryStrategy.manual }; + + qwikVitePlugin.api.registerBundleGraphAdder(() => { + const result: Record< + string, + { imports?: string[] | undefined; dynamicImports?: string[] | undefined } + > = {}; + for (const item of data?.prefetch || []) { + if (item.symbols) { + let route = item.route; + if (route.startsWith('/')) { + route = route.slice(1); + } + if (!route.endsWith('/')) { + route += '/'; + } + result[route] = { imports: item.symbols }; + } + } + return result; + }); + } + }, }, + closeBundle: async () => { const path = resolve(outDir, 'q-manifest.json'); if (isProd && existsSync(path)) { diff --git a/packages/qwik/src/optimizer/src/api.md b/packages/qwik/src/optimizer/src/api.md index 3354a133cf9..3b6ec095b14 100644 --- a/packages/qwik/src/optimizer/src/api.md +++ b/packages/qwik/src/optimizer/src/api.md @@ -75,19 +75,6 @@ export interface InlineEntryStrategy { type: 'inline'; } -// @public (undocumented) -export interface InsightManifest { - // (undocumented) - manual: Record; - // (undocumented) - prefetch: { - route: string; - symbols: string[]; - }[]; - // (undocumented) - type: 'smart'; -} - // @public (undocumented) export type MinifyMode = 'simplify' | 'none'; @@ -299,8 +286,6 @@ export interface QwikVitePluginApi { // (undocumented) getClientPublicOutDir: () => string | null; // (undocumented) - getInsightsManifest: (clientOutDir?: string | null) => Promise; - // (undocumented) getManifest: () => QwikManifest | null; // (undocumented) getOptimizer: () => Optimizer | null; diff --git a/packages/qwik/src/optimizer/src/index.ts b/packages/qwik/src/optimizer/src/index.ts index 8ce6c46e580..aba102ec9e8 100644 --- a/packages/qwik/src/optimizer/src/index.ts +++ b/packages/qwik/src/optimizer/src/index.ts @@ -10,7 +10,6 @@ export type { SegmentAnalysis as HookAnalysis, SegmentEntryStrategy as HookEntryStrategy, InlineEntryStrategy, - InsightManifest, MinifyMode, Optimizer, OptimizerOptions, diff --git a/packages/qwik/src/optimizer/src/plugins/plugin.ts b/packages/qwik/src/optimizer/src/plugins/plugin.ts index 560af231caf..0868608e8c9 100644 --- a/packages/qwik/src/optimizer/src/plugins/plugin.ts +++ b/packages/qwik/src/optimizer/src/plugins/plugin.ts @@ -8,7 +8,6 @@ import type { EntryStrategy, GlobalInjections, SegmentAnalysis, - InsightManifest, Optimizer, OptimizerOptions, OptimizerSystem, @@ -111,7 +110,6 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) { srcInputs: null as any, sourcemap: !!optimizerOptions.sourcemap, manifestInput: null, - insightsManifest: null, manifestOutput: null, transformedModuleOutput: null, scope: null, @@ -1011,7 +1009,6 @@ export interface QwikPluginOptions { vendorRoots?: string[]; manifestOutput?: ((manifest: QwikManifest) => Promise | void) | null; manifestInput?: QwikManifest | null; - insightsManifest?: InsightManifest | null; input?: string[] | string | { [entry: string]: string }; outDir?: string; assetsDir?: string; diff --git a/packages/qwik/src/optimizer/src/plugins/vite.ts b/packages/qwik/src/optimizer/src/plugins/vite.ts index b5ffe88b5fd..52ef84e9503 100644 --- a/packages/qwik/src/optimizer/src/plugins/vite.ts +++ b/packages/qwik/src/optimizer/src/plugins/vite.ts @@ -3,11 +3,9 @@ import { QWIK_LOADER_DEFAULT_DEBUG, QWIK_LOADER_DEFAULT_MINIFIED } from '../scri import type { EntryStrategy, GlobalInjections, - InsightManifest, Optimizer, OptimizerOptions, OptimizerSystem, - Path, QwikManifest, TransformModule, } from '../types'; @@ -76,30 +74,12 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any { const injections: GlobalInjections[] = []; const qwikPlugin = createQwikPlugin(qwikViteOpts.optimizerOptions); - async function loadQwikInsights(clientOutDir = ''): Promise { - const sys = qwikPlugin.getSys(); - const cwdRelativePath = absolutePathAwareJoin( - sys.path, - rootDir || '.', - clientOutDir, - 'q-insights.json' - ); - const path = absolutePathAwareJoin(sys.path, process.cwd(), cwdRelativePath); - const fs: typeof import('fs') = await sys.dynamicImport('node:fs'); - if (fs.existsSync(path)) { - qwikPlugin.log('Reading Qwik Insight data from: ' + cwdRelativePath); - return JSON.parse(await fs.promises.readFile(path, 'utf-8')) as InsightManifest; - } - return null; - } - const bundleGraphAdders = new Set(); const api: QwikVitePluginApi = { getOptimizer: () => qwikPlugin.getOptimizer(), getOptions: () => qwikPlugin.getOptions(), getManifest: () => manifestInput, - getInsightsManifest: (clientOutDir = '') => loadQwikInsights(clientOutDir!), getRootDir: () => qwikPlugin.getOptions().rootDir, getClientOutDir: () => clientOutDir, getClientPublicOutDir: () => clientPublicOutDir, @@ -436,19 +416,6 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any { basePathname += '/'; } } - const sys = qwikPlugin.getSys(); - if (sys.env === 'node' && !qwikViteOpts.entryStrategy) { - try { - const entryStrategy = await loadQwikInsights( - !qwikViteOpts.csr ? qwikViteOpts.client?.outDir : undefined - ); - if (entryStrategy) { - qwikViteOpts.entryStrategy = entryStrategy; - } - } catch { - // ok to ignore - } - } const useSourcemap = !!config.build.sourcemap; if (useSourcemap && qwikViteOpts.optimizerOptions?.sourcemap === undefined) { qwikPlugin.setSourceMapSupport(true); @@ -1097,7 +1064,6 @@ export interface QwikVitePluginApi { getOptimizer: () => Optimizer | null; getOptions: () => NormalizedQwikPluginOptions; getManifest: () => QwikManifest | null; - getInsightsManifest: (clientOutDir?: string | null) => Promise; getRootDir: () => string | null; getClientOutDir: () => string | null; getClientPublicOutDir: () => string | null; @@ -1120,18 +1086,3 @@ export interface QwikViteDevResponse { _qwikEnvData?: Record; _qwikRenderResolve?: () => void; } - -/** - * Joins path segments together and normalizes the resulting path, taking into account absolute - * paths. - */ -function absolutePathAwareJoin(path: Path, ...segments: string[]): string { - for (let i = segments.length - 1; i >= 0; --i) { - const segment = segments[i]; - if (segment.startsWith(path.sep) || segment.indexOf(path.delimiter) !== -1) { - segments.splice(0, i); - break; - } - } - return path.join(...segments); -} diff --git a/packages/qwik/src/optimizer/src/types.ts b/packages/qwik/src/optimizer/src/types.ts index 2efa40aa325..03eaf00edd0 100644 --- a/packages/qwik/src/optimizer/src/types.ts +++ b/packages/qwik/src/optimizer/src/types.ts @@ -321,10 +321,3 @@ export interface ResolvedManifest { mapper: SymbolMapper; manifest: QwikManifest; } - -/** @public */ -export interface InsightManifest { - type: 'smart'; - manual: Record; - prefetch: { route: string; symbols: string[] }[]; -} From bb6be68db63ceeb0f69c834afdaf1742e5cd3936 Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Thu, 27 Mar 2025 11:04:12 +0100 Subject: [PATCH 12/17] perf(bundle-graph): only include segments for dynamic imports If the dev wants to include other dynamic imports, they can just make a qrl() --- .../src/routes/api/qwik-optimizer/api.json | 6 +- .../src/routes/api/qwik-optimizer/index.md | 29 +--- packages/qwik/src/optimizer/src/api.md | 11 +- packages/qwik/src/optimizer/src/manifest.ts | 13 +- .../src/optimizer/src/plugins/bundle-graph.ts | 41 +++-- .../src/plugins/bundle-graph.unit.ts | 150 +++++++++++------- .../src/optimizer/src/plugins/vite.unit.ts | 41 +---- packages/qwik/src/optimizer/src/types.ts | 10 +- 8 files changed, 141 insertions(+), 160 deletions(-) diff --git a/packages/docs/src/routes/api/qwik-optimizer/api.json b/packages/docs/src/routes/api/qwik-optimizer/api.json index 1ae5bab91a8..f213d302ae0 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/api.json +++ b/packages/docs/src/routes/api/qwik-optimizer/api.json @@ -406,7 +406,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface QwikBundle \n```\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[dynamicImports?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_\n\n\n
\n\n[hasSymbols?](#)\n\n\n\n\n\n\n\nboolean\n\n\n\n\n_(Optional)_\n\n\n
\n\n[imports?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_\n\n\n
\n\n[origins?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_\n\n\n
\n\n[size](#)\n\n\n\n\n\n\n\nnumber\n\n\n\n\n\n
\n\n[symbols?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_\n\n\n
", + "content": "```typescript\nexport interface QwikBundle \n```\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[dynamicImports?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_ Dynamic imports\n\n\n
\n\n[imports?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_ Direct imports\n\n\n
\n\n[origins?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_ Source files of the bundle\n\n\n
\n\n[size](#)\n\n\n\n\n\n\n\nnumber\n\n\n\n\nSize of the bundle\n\n\n
\n\n[symbols?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_ Symbols in the bundle\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts", "mdFile": "qwik.qwikbundle.md" }, @@ -476,7 +476,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface QwikSymbol \n```\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[canonicalFilename](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[captures](#)\n\n\n\n\n\n\n\nboolean\n\n\n\n\n\n
\n\n[ctxKind](#)\n\n\n\n\n\n\n\n'function' \\| 'event'\n\n\n\n\n\n
\n\n[ctxName](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[displayName](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[hash](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[loc](#)\n\n\n\n\n\n\n\n\\[number, number\\]\n\n\n\n\n\n
\n\n[origin](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[parent](#)\n\n\n\n\n\n\n\nstring \\| null\n\n\n\n\n\n
", + "content": "```typescript\nexport interface QwikSymbol \n```\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[canonicalFilename](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[captures](#)\n\n\n\n\n\n\n\nboolean\n\n\n\n\n\n
\n\n[ctxKind](#)\n\n\n\n\n\n\n\n'function' \\| 'eventHandler'\n\n\n\n\n\n
\n\n[ctxName](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[displayName](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[hash](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[loc](#)\n\n\n\n\n\n\n\n\\[number, number\\]\n\n\n\n\n\n
\n\n[origin](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[parent](#)\n\n\n\n\n\n\n\nstring \\| null\n\n\n\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts", "mdFile": "qwik.qwiksymbol.md" }, @@ -608,7 +608,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface SegmentAnalysis \n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[canonicalFilename](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[captures](#)\n\n\n\n\n\n\n\nboolean\n\n\n\n\n\n
\n\n[ctxKind](#)\n\n\n\n\n\n\n\n'event' \\| 'function'\n\n\n\n\n\n
\n\n[ctxName](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[displayName](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[entry](#)\n\n\n\n\n\n\n\nstring \\| null\n\n\n\n\n\n
\n\n[extension](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[hash](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[loc](#)\n\n\n\n\n\n\n\n\\[number, number\\]\n\n\n\n\n\n
\n\n[name](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[origin](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[parent](#)\n\n\n\n\n\n\n\nstring \\| null\n\n\n\n\n\n
", + "content": "```typescript\nexport interface SegmentAnalysis \n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[canonicalFilename](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[captures](#)\n\n\n\n\n\n\n\nboolean\n\n\n\n\n\n
\n\n[ctxKind](#)\n\n\n\n\n\n\n\n'eventHandler' \\| 'function'\n\n\n\n\n\n
\n\n[ctxName](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[displayName](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[entry](#)\n\n\n\n\n\n\n\nstring \\| null\n\n\n\n\n\n
\n\n[extension](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[hash](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[loc](#)\n\n\n\n\n\n\n\n\\[number, number\\]\n\n\n\n\n\n
\n\n[name](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[origin](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
\n\n[parent](#)\n\n\n\n\n\n\n\nstring \\| null\n\n\n\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts", "mdFile": "qwik.segmentanalysis.md" }, diff --git a/packages/docs/src/routes/api/qwik-optimizer/index.md b/packages/docs/src/routes/api/qwik-optimizer/index.md index ff40e16814c..efef293945a 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/index.md +++ b/packages/docs/src/routes/api/qwik-optimizer/index.md @@ -1267,22 +1267,7 @@ string[] -_(Optional)_ - - - - -[hasSymbols?](#) - - - - - -boolean - - - -_(Optional)_ +_(Optional)_ Dynamic imports @@ -1297,7 +1282,7 @@ string[] -_(Optional)_ +_(Optional)_ Direct imports @@ -1312,7 +1297,7 @@ string[] -_(Optional)_ +_(Optional)_ Source files of the bundle @@ -1327,6 +1312,8 @@ number +Size of the bundle + @@ -1340,7 +1327,7 @@ string[] -_(Optional)_ +_(Optional)_ Symbols in the bundle @@ -1859,7 +1846,7 @@ boolean -'function' \| 'event' +'function' \| 'eventHandler' @@ -2390,7 +2377,7 @@ boolean -'event' \| 'function' +'eventHandler' \| 'function' diff --git a/packages/qwik/src/optimizer/src/api.md b/packages/qwik/src/optimizer/src/api.md index 3b6ec095b14..233569c9ae4 100644 --- a/packages/qwik/src/optimizer/src/api.md +++ b/packages/qwik/src/optimizer/src/api.md @@ -167,17 +167,10 @@ export type QwikBuildTarget = 'client' | 'ssr' | 'lib' | 'test'; // @public (undocumented) export interface QwikBundle { - // (undocumented) dynamicImports?: string[]; - // (undocumented) - hasSymbols?: boolean; - // (undocumented) imports?: string[]; - // (undocumented) origins?: string[]; - // (undocumented) size: number; - // (undocumented) symbols?: string[]; } @@ -244,7 +237,7 @@ export interface QwikSymbol { // (undocumented) captures: boolean; // (undocumented) - ctxKind: 'function' | 'event'; + ctxKind: 'function' | 'eventHandler'; // (undocumented) ctxName: string; // (undocumented) @@ -320,7 +313,7 @@ interface SegmentAnalysis { // (undocumented) captures: boolean; // (undocumented) - ctxKind: 'event' | 'function'; + ctxKind: 'eventHandler' | 'function'; // (undocumented) ctxName: string; // (undocumented) diff --git a/packages/qwik/src/optimizer/src/manifest.ts b/packages/qwik/src/optimizer/src/manifest.ts index d0ac0680142..68074e38498 100644 --- a/packages/qwik/src/optimizer/src/manifest.ts +++ b/packages/qwik/src/optimizer/src/manifest.ts @@ -15,15 +15,15 @@ function prioritizeSymbolNames(manifest: QwikManifest) { const b = symbols[symbolNameB]; // events should sort highest - if (a.ctxKind === 'event' && b.ctxKind !== 'event') { + if (a.ctxKind === 'eventHandler' && b.ctxKind !== 'eventHandler') { return -1; } - if (a.ctxKind !== 'event' && b.ctxKind === 'event') { + if (a.ctxKind !== 'eventHandler' && b.ctxKind === 'eventHandler') { return 1; } - if (a.ctxKind === 'event' && b.ctxKind === 'event') { - // both are an event + if (a.ctxKind === 'eventHandler' && b.ctxKind === 'eventHandler') { + // both are an event handler const aIndex = EVENT_PRIORITY.indexOf(a.ctxName.toLowerCase()); const bIndex = EVENT_PRIORITY.indexOf(b.ctxName.toLowerCase()); @@ -281,20 +281,15 @@ export function generateManifestFromBundles( size: outputBundle.code.length, }; - let hasSymbols = false; for (const symbol of outputBundle.exports) { if (qrlNames.has(symbol)) { // When not minifying we see both the entry and the segment file // The segment file will only have 1 export, we want the entry if (!manifest.mapping[symbol] || outputBundle.exports.length !== 1) { - hasSymbols = true; manifest.mapping[symbol] = bundleFileName; } } } - if (hasSymbols) { - bundle.hasSymbols = true; - } const bundleImports = outputBundle.imports // Tree shaking might remove imports diff --git a/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts b/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts index f55236ddc8e..818f247d158 100644 --- a/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts +++ b/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts @@ -54,13 +54,30 @@ export function convertManifestToBundleGraph( } } + // Filter out external and non-segment dynamic imports + for (const bundleName of Object.keys(graph)) { + const bundle = graph[bundleName]; + const imports = bundle.imports?.filter((dep) => graph[dep]) || []; + + // We only include dynamic imports that have qrl segments + // If the dev wants to include other dynamic imports, they can just make a qrl() + const dynamicImports = bundle.dynamicImports?.filter((dep) => graph[dep]?.hasSegments) || []; + + // Overwrite so we don't mutate + graph[bundleName] = { + imports, + dynamicImports, + size: bundle.size, + }; + } + // Remove unused bundles const notUsed = new Set(Object.keys(graph)); for (const bundleName of Object.keys(graph)) { - for (const dep of graph[bundleName].imports || []) { + for (const dep of graph[bundleName].imports!) { notUsed.delete(dep); } - for (const dep of graph[bundleName].dynamicImports || []) { + for (const dep of graph[bundleName].dynamicImports!) { notUsed.delete(dep); } } @@ -79,11 +96,7 @@ export function convertManifestToBundleGraph( seen: Set = new Set() ) => { const bundle = graph[bundleName]; - if (!bundle) { - // external dependency - return; - } - for (const dep of bundle.imports || []) { + for (const dep of bundle.imports!) { if (parentDeps.has(dep)) { parentDeps.delete(dep); } @@ -93,9 +106,6 @@ export function convertManifestToBundleGraph( } } }; - const withoutExternalDependencies = (imports: string[] | undefined) => { - return imports?.filter((dep) => graph[dep]) || []; - }; /** * First pass to collect minimal dependency lists and allocate space for dependencies. Minimal @@ -104,11 +114,12 @@ export function convertManifestToBundleGraph( */ for (const bundleName of names) { const bundle = graph[bundleName]; - const deps = new Set(withoutExternalDependencies(bundle.imports)); + // external dependencies are not included in `graph` + const deps = new Set(bundle.imports!); for (const depName of deps) { clearTransitiveDeps(deps, depName); } - const dynDeps = new Set(withoutExternalDependencies(bundle.dynamicImports)); + const dynDeps = new Set(bundle.dynamicImports!); for (const depName of dynDeps) { clearTransitiveDeps(dynDeps, depName); } @@ -130,11 +141,7 @@ export function convertManifestToBundleGraph( // Second pass to set the dependency indices for (const bundleName of names) { - const bundle = map.get(bundleName); - if (!bundle) { - console.warn(`Bundle ${bundleName} not found in the bundle graph.`); - continue; - } + const bundle = map.get(bundleName)!; // eslint-disable-next-line prefer-const let { index, deps } = bundle; index++; diff --git a/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts b/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts index 94ec0ab36d8..f611a8d7245 100644 --- a/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts +++ b/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts @@ -15,21 +15,27 @@ describe('convertManifestToBundleGraph', () => { }, 'static-dep.js': { size: 0, - dynamicImports: ['@other', 'transitive-dep.js', 'dynamic-dep.js'], + dynamicImports: ['@other', 'transitive-dep.js', 'dynamic-dep.js', 'no-symbols.js'], }, 'dynamic-dep.js': { size: 0, imports: ['static-dep.js', 'transitive-dep.js', '@external-dep'], + dynamicImports: ['has-a-symbol.js', 'no-symbols.js'], + hasSegments: true, }, 'transitive-dep.js': { size: 0, + hasSegments: true, }, 'not-used.js': { size: 0, }, 'has-a-symbol.js': { size: 0, - symbols: ['sym2'], + hasSegments: true, + }, + 'no-symbols.js': { + size: 0, }, } as Record, mapping: { sym1: 'app.js', sym2: 'has-a-symbol.js' }, @@ -39,34 +45,71 @@ describe('convertManifestToBundleGraph', () => { } as QwikManifest; test('trivial example', () => { - expect(convertManifestToBundleGraph(fakeManifest)).toMatchInlineSnapshot(` - [ - "app.js", - 2, - "static-dep.js", - -1, - 5, - "dynamic-dep.js", - 2, - 8, - "transitive-dep.js", - "has-a-symbol.js", - "sym1", - 0, - "sym2", - 9, - ] - `); + expect(convertManifestToBundleGraph(fakeManifest)).toEqual([ + 'app.js', // 0 + 2, + 'static-dep.js', // 2 + -1, + 5, + // doesn't list 8 because it's also statically imported by dynamic-dep.js + 'dynamic-dep.js', // 5 + 2, + 10, + -1, + 11, + 'transitive-dep.js', // 10 + 'has-a-symbol.js', // 11 + 'sym1', // 13 + 0, + 'sym2', // 15 + 11, + ]); + }); + + test('empty', () => { + expect(convertManifestToBundleGraph({} as any)).toEqual([]); + }); + + test('simple file set', () => { + const manifest = { + bundles: { + 'a.js': { + size: 0, + imports: ['b.js'], + dynamicImports: ['c.js'], + }, + 'b.js': { + size: 0, + dynamicImports: ['c.js'], + }, + 'c.js': { + size: 0, + hasSegments: true, + }, + } as Record, + mapping: {}, + } as QwikManifest; + expect(convertManifestToBundleGraph(manifest)).toEqual([ + 'a.js', // 0 + 4, + -1, + 7, + 'b.js', // 4 + -1, + 7, + 'c.js', // 7 + ]); }); + test('adder', () => { expect( convertManifestToBundleGraph( fakeManifest, new Set([ - (_manifest) => { + (manifest) => { return { // Remove dynamic imports from dynamic-dep.js - 'dynamic-dep.js': { dynamicImports: [] }, + 'dynamic-dep.js': { ...manifest.bundles['dynamic-dep.js'], dynamicImports: [] }, }; }, (_manifest) => { @@ -80,27 +123,24 @@ describe('convertManifestToBundleGraph', () => { }, ]) ) - ).toMatchInlineSnapshot(` - [ - "app.js", - 2, - "static-dep.js", - -1, - 7, - 6, - "dynamic-dep.js", - "transitive-dep.js", - "has-a-symbol.js", - "sym1", - 0, - "sym2", - 8, - "dashboard/", - 2, - -1, - 7, - ] - `); + ).toEqual([ + 'app.js', // 0 + 2, + 'static-dep.js', // 2 + -1, + 5, + 'dynamic-dep.js', // 5 + 2, + 8, + 'transitive-dep.js', // 8 + 'has-a-symbol.js', // 9 + 'sym1', // 11 + 0, + 'sym2', // 13 + 9, + 'dashboard/', // 15 + 2, + ]); }); test(`works`, () => { @@ -116,8 +156,8 @@ describe('convertManifestToBundleGraph', () => { expect(convertManifestToBundleGraph(manifest)).toMatchInlineSnapshot(` [ "app.js", - 30, - 31, + 22, + 23, -1, 19, "app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js", @@ -125,26 +165,18 @@ describe('convertManifestToBundleGraph', () => { "app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js", 0, "app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js", - 30, + 22, "app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js", - 30, + 22, "app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js", - 30, + 22, "app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js", 0, "app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js", - 30, + 22, "app.tsx_App_component_jn5XSz7NZ88.js", - 30, - 31, - -1, - 5, - 7, - 9, - 11, - 13, - 15, - 17, + 22, + 23, "core.js", "preload-helper.js", "root.js", diff --git a/packages/qwik/src/optimizer/src/plugins/vite.unit.ts b/packages/qwik/src/optimizer/src/plugins/vite.unit.ts index a68dec779d7..5009766e3d1 100644 --- a/packages/qwik/src/optimizer/src/plugins/vite.unit.ts +++ b/packages/qwik/src/optimizer/src/plugins/vite.unit.ts @@ -1,9 +1,8 @@ import path, { resolve } from 'node:path'; import type { Rollup } from 'vite'; -import { assert, expect, suite, test } from 'vitest'; +import { assert, test } from 'vitest'; import { normalizePath } from '../../../testing/util'; -import type { OptimizerOptions, QwikBundle, QwikManifest } from '../types'; -import { convertManifestToBundleGraph } from './bundle-graph'; +import type { OptimizerOptions } from '../types'; import { qwikVite, type QwikVitePlugin, type QwikVitePluginOptions } from './vite'; const cwd = process.cwd(); @@ -490,39 +489,3 @@ test('command: build, --mode lib with multiple outputs', async () => { assert.deepEqual(build.emptyOutDir, undefined); assert.deepEqual(opts.resolveQwikBuild, true); }); - -suite('convertManifestToBundleGraph', () => { - test('empty', () => { - expect(convertManifestToBundleGraph({} as any)).toEqual([]); - }); - - test('simple file set', () => { - const manifest = { - bundles: { - 'a.js': { - size: 0, - imports: ['b.js'], - dynamicImports: ['c.js'], - }, - 'b.js': { - size: 0, - dynamicImports: ['c.js'], - }, - 'c.js': { - size: 0, - }, - } as Record, - mapping: {}, - } as QwikManifest; - expect(convertManifestToBundleGraph(manifest)).toEqual([ - 'a.js', - 4, - -1, - 7, - 'b.js', - -1, - 7, - 'c.js', - ]); - }); -}); diff --git a/packages/qwik/src/optimizer/src/types.ts b/packages/qwik/src/optimizer/src/types.ts index 03eaf00edd0..40d909192ab 100644 --- a/packages/qwik/src/optimizer/src/types.ts +++ b/packages/qwik/src/optimizer/src/types.ts @@ -107,7 +107,7 @@ export interface SegmentAnalysis { canonicalFilename: string; extension: string; parent: string | null; - ctxKind: 'event' | 'function'; + ctxKind: 'eventHandler' | 'function'; ctxName: string; captures: boolean; loc: [number, number]; @@ -260,7 +260,7 @@ export interface QwikSymbol { displayName: string; hash: string; canonicalFilename: string; - ctxKind: 'function' | 'event'; + ctxKind: 'function' | 'eventHandler'; ctxName: string; captures: boolean; parent: string | null; @@ -269,11 +269,15 @@ export interface QwikSymbol { /** @public */ export interface QwikBundle { + /** Size of the bundle */ size: number; - hasSymbols?: boolean; + /** Symbols in the bundle */ symbols?: string[]; + /** Direct imports */ imports?: string[]; + /** Dynamic imports */ dynamicImports?: string[]; + /** Source files of the bundle */ origins?: string[]; } From bc0caa4d5c01155cd600d9d608be4d4e5338ca35 Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Mon, 31 Mar 2025 13:59:37 +0200 Subject: [PATCH 13/17] feat(preloader): add @builder.io/qwik/preloader separate shared chunk that does preloading and is also imported by qwik --- packages/docs/src/repl/bundled.tsx | 2 + packages/docs/src/repl/worker/repl-plugins.ts | 9 + .../src/routes/api/qwik-optimizer/api.json | 2 +- .../src/routes/api/qwik-optimizer/index.md | 15 + .../src/buildtime/vite/get-route-imports.ts | 8 +- .../buildtime/vite/get-route-imports.unit.ts | 6 +- .../src/runtime/src/client-navigate.ts | 6 +- packages/qwik-labs/src-vite/insights/index.ts | 14 +- packages/qwik/package.json | 3 + packages/qwik/src/core/api.md | 3 - packages/qwik/src/core/internal.ts | 1 - packages/qwik/src/core/preloader.ts | 317 ++++++++++++++++++ packages/qwik/src/core/qrl/preload.ts | 175 ---------- packages/qwik/src/core/qrl/qrl-class.ts | 7 +- packages/qwik/src/optimizer/src/api.md | 1 + packages/qwik/src/optimizer/src/manifest.ts | 6 +- .../src/optimizer/src/plugins/bundle-graph.ts | 12 +- .../src/plugins/bundle-graph.unit.ts | 30 +- .../qwik/src/optimizer/src/plugins/plugin.ts | 21 ++ packages/qwik/src/optimizer/src/types.ts | 2 + .../src/server/prefetch-implementation.ts | 123 ++++--- packages/qwik/src/server/prefetch-strategy.ts | 7 +- packages/qwik/src/server/preloading.md | 106 ++++++ packages/qwik/src/server/render.ts | 2 +- packages/qwik/tsconfig.json | 1 + scripts/build.ts | 4 +- scripts/submodule-core.ts | 9 +- scripts/submodule-preloader.ts | 72 ++++ scripts/util.ts | 5 +- starters/apps/perf.prod/vite.config.mts | 12 + 30 files changed, 705 insertions(+), 276 deletions(-) create mode 100644 packages/qwik/src/core/preloader.ts delete mode 100644 packages/qwik/src/core/qrl/preload.ts create mode 100644 packages/qwik/src/server/preloading.md create mode 100644 scripts/submodule-preloader.ts create mode 100644 starters/apps/perf.prod/vite.config.mts diff --git a/packages/docs/src/repl/bundled.tsx b/packages/docs/src/repl/bundled.tsx index 6ce706ac5f1..5c4626a8935 100644 --- a/packages/docs/src/repl/bundled.tsx +++ b/packages/docs/src/repl/bundled.tsx @@ -14,6 +14,7 @@ import qCoreDts from '../../node_modules/@builder.io/qwik/dist/core.d.ts?raw-sou import qCoreMinMjs from '../../node_modules/@builder.io/qwik/dist/core.min.mjs?raw-source'; import qCoreMjs from '../../node_modules/@builder.io/qwik/dist/core.mjs?raw-source'; import qOptimizerCjs from '../../node_modules/@builder.io/qwik/dist/optimizer.cjs?raw-source'; +import qPreloaderMjs from '../../node_modules/@builder.io/qwik/dist/preloader.mjs?raw-source'; import qServerCjs from '../../node_modules/@builder.io/qwik/dist/server.cjs?raw-source'; import qServerDts from '../../node_modules/@builder.io/qwik/dist/server.d.ts?raw-source'; import qWasmCjs from '../../node_modules/@builder.io/qwik/bindings/qwik.wasm.cjs?raw-source'; @@ -55,6 +56,7 @@ export const bundled: PkgUrls = { '/dist/optimizer.cjs': qOptimizerCjs, '/dist/server.cjs': qServerCjs, '/dist/server.d.ts': qServerDts, + '/dist/preloader.mjs': qPreloaderMjs, '/bindings/qwik.wasm.cjs': qWasmCjs, '/bindings/qwik_wasm_bg.wasm': qWasmBinUrl, }, diff --git a/packages/docs/src/repl/worker/repl-plugins.ts b/packages/docs/src/repl/worker/repl-plugins.ts index 2e9cd574f9a..3866f685e84 100644 --- a/packages/docs/src/repl/worker/repl-plugins.ts +++ b/packages/docs/src/repl/worker/repl-plugins.ts @@ -29,6 +29,9 @@ export const replResolver = (options: ReplInputOptions, buildMode: 'client' | 's if (id === '@builder.io/qwik/server') { return '\0qwikServer'; } + if (id === '@builder.io/qwik/preloader') { + return '\0qwikPreloader'; + } // Simple relative file resolution if (id.startsWith('./')) { const extensions = ['', '.tsx', '.ts']; @@ -69,6 +72,12 @@ export const replResolver = (options: ReplInputOptions, buildMode: 'client' | 's } throw new Error(`Unable to load Qwik core`); } + if (id === '\0qwikPreloader') { + const rsp = await depResponse('@builder.io/qwik', '/preloader.mjs'); + if (rsp) { + return rsp.text(); + } + } // We're the fallback, we know all the files if (/\.[jt]sx?$/.test(id)) { diff --git a/packages/docs/src/routes/api/qwik-optimizer/api.json b/packages/docs/src/routes/api/qwik-optimizer/api.json index f213d302ae0..19dfabf8136 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/api.json +++ b/packages/docs/src/routes/api/qwik-optimizer/api.json @@ -434,7 +434,7 @@ } ], "kind": "Interface", - "content": "The metadata of the build. One of its uses is storing where QRL symbols are located.\n\n\n```typescript\nexport interface QwikManifest \n```\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[bundles](#)\n\n\n\n\n\n\n\n{ \\[fileName: string\\]: [QwikBundle](#qwikbundle); }\n\n\n\n\nAll code bundles, used to know the import graph\n\n\n
\n\n[injections?](#)\n\n\n\n\n\n\n\n[GlobalInjections](#globalinjections)\\[\\]\n\n\n\n\n_(Optional)_ CSS etc to inject in the document head\n\n\n
\n\n[manifestHash](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\nContent hash of the manifest, if this changes, the code changed\n\n\n
\n\n[mapping](#)\n\n\n\n\n\n\n\n{ \\[symbolName: string\\]: string; }\n\n\n\n\nWhere QRLs are located\n\n\n
\n\n[options?](#)\n\n\n\n\n\n\n\n{ target?: string; buildMode?: string; entryStrategy?: { \\[key: string\\]: any; }; }\n\n\n\n\n_(Optional)_\n\n\n
\n\n[platform?](#)\n\n\n\n\n\n\n\n{ \\[name: string\\]: string; }\n\n\n\n\n_(Optional)_\n\n\n
\n\n[symbols](#)\n\n\n\n\n\n\n\n{ \\[symbolName: string\\]: [QwikSymbol](#qwiksymbol); }\n\n\n\n\nQRL symbols\n\n\n
\n\n[version](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
", + "content": "The metadata of the build. One of its uses is storing where QRL symbols are located.\n\n\n```typescript\nexport interface QwikManifest \n```\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[bundles](#)\n\n\n\n\n\n\n\n{ \\[fileName: string\\]: [QwikBundle](#qwikbundle); }\n\n\n\n\nAll code bundles, used to know the import graph\n\n\n
\n\n[injections?](#)\n\n\n\n\n\n\n\n[GlobalInjections](#globalinjections)\\[\\]\n\n\n\n\n_(Optional)_ CSS etc to inject in the document head\n\n\n
\n\n[manifestHash](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\nContent hash of the manifest, if this changes, the code changed\n\n\n
\n\n[mapping](#)\n\n\n\n\n\n\n\n{ \\[symbolName: string\\]: string; }\n\n\n\n\nWhere QRLs are located\n\n\n
\n\n[options?](#)\n\n\n\n\n\n\n\n{ target?: string; buildMode?: string; entryStrategy?: { \\[key: string\\]: any; }; }\n\n\n\n\n_(Optional)_\n\n\n
\n\n[platform?](#)\n\n\n\n\n\n\n\n{ \\[name: string\\]: string; }\n\n\n\n\n_(Optional)_\n\n\n
\n\n[preloader?](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n_(Optional)_ The preloader bundle\n\n\n
\n\n[symbols](#)\n\n\n\n\n\n\n\n{ \\[symbolName: string\\]: [QwikSymbol](#qwiksymbol); }\n\n\n\n\nQRL symbols\n\n\n
\n\n[version](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts", "mdFile": "qwik.qwikmanifest.md" }, diff --git a/packages/docs/src/routes/api/qwik-optimizer/index.md b/packages/docs/src/routes/api/qwik-optimizer/index.md index efef293945a..b156b9a1384 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/index.md +++ b/packages/docs/src/routes/api/qwik-optimizer/index.md @@ -1463,6 +1463,21 @@ _(Optional)_ +[preloader?](#) + + + + + +string + + + +_(Optional)_ The preloader bundle + + + + [symbols](#) diff --git a/packages/qwik-city/src/buildtime/vite/get-route-imports.ts b/packages/qwik-city/src/buildtime/vite/get-route-imports.ts index 4117712cd49..146915bb8ad 100644 --- a/packages/qwik-city/src/buildtime/vite/get-route-imports.ts +++ b/packages/qwik-city/src/buildtime/vite/get-route-imports.ts @@ -12,15 +12,15 @@ export function getRouteImports(routes: BuildRoute[], manifest: QwikManifest) { : []; const routeAndLayoutPaths = [routePath, ...layoutPaths]; - const imports = []; + const bundles = []; for (const [bundleName, bundle] of Object.entries(manifest.bundles)) { if (isBundlePartOfRoute(bundle, routeAndLayoutPaths)) { - imports.push(bundleName); + bundles.push(bundleName); } } - if (imports.length > 0) { - result[route.routeName] = { imports }; + if (bundles.length > 0) { + result[route.routeName] = { dynamicImports: bundles }; } }); for (const bundleName of Object.keys(manifest.bundles)) { diff --git a/packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts b/packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts index 5bc82a20559..b1634eaa8cf 100644 --- a/packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts +++ b/packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts @@ -56,12 +56,12 @@ describe('modifyBundleGraph', () => { expect(actualResult).toMatchInlineSnapshot(` { "/": { - "imports": [ + "dynamicImports": [ "fake-bundle1.js", ], }, "/subroute": { - "imports": [ + "dynamicImports": [ "fake-bundle-part-of-sub-route.js", "fake-bundle-part-of-layout.js", ], @@ -102,7 +102,7 @@ describe('modifyBundleGraph', () => { expect(actualResult).toMatchInlineSnapshot(` { "/": { - "imports": [ + "dynamicImports": [ "fake-bundle1.js", "fake-bundle2.js", ], diff --git a/packages/qwik-city/src/runtime/src/client-navigate.ts b/packages/qwik-city/src/runtime/src/client-navigate.ts index 0a5becc227d..06b3284acef 100644 --- a/packages/qwik-city/src/runtime/src/client-navigate.ts +++ b/packages/qwik-city/src/runtime/src/client-navigate.ts @@ -1,4 +1,6 @@ -import { isBrowser, _preload } from '@builder.io/qwik'; +import { isBrowser } from '@builder.io/qwik'; +// @ts-expect-error we don't have types for the preloader yet +import { p as preload } from '@builder.io/qwik/preloader'; import type { NavigationType, ScrollState } from './types'; import { isSamePath, toPath } from './utils'; @@ -43,6 +45,6 @@ export const prefetchSymbols = (path: string) => { if (isBrowser) { path = path.endsWith('/') ? path : path + '/'; path = path.length > 1 && path.startsWith('/') ? path.slice(1) : path; - _preload(path, true); + preload(path); } }; diff --git a/packages/qwik-labs/src-vite/insights/index.ts b/packages/qwik-labs/src-vite/insights/index.ts index 7010e558bbf..cfaaf9668e0 100644 --- a/packages/qwik-labs/src-vite/insights/index.ts +++ b/packages/qwik-labs/src-vite/insights/index.ts @@ -1,4 +1,4 @@ -import { type QwikVitePlugin } from '@builder.io/qwik/optimizer'; +import { type QwikVitePlugin, type SmartEntryStrategy } from '@builder.io/qwik/optimizer'; import { existsSync, mkdirSync } from 'node:fs'; import { readFile, writeFile } from 'node:fs/promises'; import { join, resolve } from 'node:path'; @@ -63,9 +63,6 @@ export async function qwikInsights(qwikInsightsOpts: { throw new Error('Missing vite-plugin-qwik'); } const opts = qwikVitePlugin.api.getOptions(); - if (opts.entryStrategy.type !== 'smart') { - return; - } if (isProd) { try { const qManifest: InsightManifest = { manual: {}, prefetch: [] }; @@ -85,9 +82,12 @@ export async function qwikInsights(qwikInsightsOpts: { } if (data) { - opts.entryStrategy.manual = { ...data.manual, ...opts.entryStrategy.manual }; + (opts.entryStrategy as SmartEntryStrategy).manual = { + ...data.manual, + ...(opts.entryStrategy as SmartEntryStrategy).manual, + }; - qwikVitePlugin.api.registerBundleGraphAdder(() => { + qwikVitePlugin.api.registerBundleGraphAdder((manifest) => { const result: Record< string, { imports?: string[] | undefined; dynamicImports?: string[] | undefined } @@ -101,7 +101,7 @@ export async function qwikInsights(qwikInsightsOpts: { if (!route.endsWith('/')) { route += '/'; } - result[route] = { imports: item.symbols }; + result[route] = { ...manifest.bundles[route], imports: item.symbols }; } } return result; diff --git a/packages/qwik/package.json b/packages/qwik/package.json index 3327450b9ae..70ae9f8ddd7 100644 --- a/packages/qwik/package.json +++ b/packages/qwik/package.json @@ -109,6 +109,9 @@ "import": "./dist/optimizer.mjs", "require": "./dist/optimizer.cjs" }, + "./preloader": { + "import": "./dist/preloader.mjs" + }, "./server.cjs": "./dist/server.cjs", "./server.mjs": "./dist/server.mjs", "./server": { diff --git a/packages/qwik/src/core/api.md b/packages/qwik/src/core/api.md index 5fca5adeb32..b3bae791d39 100644 --- a/packages/qwik/src/core/api.md +++ b/packages/qwik/src/core/api.md @@ -624,9 +624,6 @@ export const PrefetchServiceWorker: (opts: { nonce?: string; }) => JSXNode_2<'script'>; -// @internal (undocumented) -export const _preload: (name: string, priority: boolean) => void; - // @public (undocumented) export interface ProgressHTMLAttributes extends Attrs<'progress', T> { } diff --git a/packages/qwik/src/core/internal.ts b/packages/qwik/src/core/internal.ts index 79f426f9fce..cad738b5834 100644 --- a/packages/qwik/src/core/internal.ts +++ b/packages/qwik/src/core/internal.ts @@ -16,4 +16,3 @@ export { } from './use/use-core'; export { _jsxQ, _jsxC, _jsxS } from './render/jsx/jsx-runtime'; export { _fnSignal } from './qrl/inlined-fn'; -export { preload as _preload } from './qrl/preload'; diff --git a/packages/qwik/src/core/preloader.ts b/packages/qwik/src/core/preloader.ts new file mode 100644 index 00000000000..53ba3e08a64 --- /dev/null +++ b/packages/qwik/src/core/preloader.ts @@ -0,0 +1,317 @@ +/* eslint-disable no-console */ +/** + * Note: this file gets built separately from the rest of the core module, and is then kept separate + * in the dist directory via manualChunks. This way it can run before the rest of the core module is + * loaded, but core can still use it. + * + * Here we handle preloading of bundles. + * + * Given a symbol hash (in fact any string), we can find all the bundles that it depends on, via the + * bundle graph. We then generate preload link tags for each of those bundles. + * + * There are several parts to this: + * + * - Load the bundle graph from the preload link tag that was injected during SSR + * - Given a string, find all the bundles that it depends on + * - Generate the preload link tags if needed + * + * In practice, we queue incoming requests and when we process + */ +/** + * Todo + * + * - High and low priority sets + * - Num preloads active at a time + * - If no modulepreload support, do 1 fetch at a time + */ + +import { isBrowser } from '@builder.io/qwik/build'; +import type { QwikBundleGraph } from '../optimizer/src/types'; + +const DEBUG = true; + +const enum BundleImportState { + None, + QueuedLow, + QueuedHigh, + /** Preload link was made */ + Loading, + LoadingHigh, + /** All imports are >=Loading */ + Loaded, +} +type BundleImport = { + $name$: string; + $url$: string | null; + $state$: BundleImportState; + $imports$: string[]; + $dynamicImports$: string[]; + $priority$: boolean; + $created$: number; + $waited$: number; + $loaded$: number; + $didLoadHigh$: boolean; +}; +const bundles = new Map(); +let gotBundleGraph = false; +const high: BundleImport[] = []; +const low: BundleImport[] = []; + +let highCount = 0; +let lowCount = 0; +const loadStart = Date.now(); + +const log = (...args: any[]) => { + console.log( + `PL ${Date.now() - loadStart}> hi ${highCount}/${high.length} lo ${lowCount}/${low.length}`, + ...args + ); +}; +let base: string | undefined; + +// minification helpers +const doc = isBrowser ? document : undefined!; +const modulePreloadStr = 'modulepreload'; +const preloadStr = 'preload'; + +const checkLoaded = (bundle: BundleImport) => { + if (bundle.$state$ === BundleImportState.Loaded) { + return true; + } + if ( + bundle.$state$ === BundleImportState.Loading && + bundle.$imports$.every((dep) => bundles.get(dep)!.$state$ >= BundleImportState.Loading) + ) { + bundle.$state$ = BundleImportState.Loaded; + return true; + } +}; + +/** + * This is called when a bundle is queued or finished loading. + * + * Because Chrome doesn't treat new modulepreloads as higher priority, we only make 5 links + * available at a time, so that when a new high priority bundle comes in, it is soon preloaded. + * + * We make sure to first empty the high priority items, first-in-last-out. + */ +const trigger = () => { + // high is confirmed needed so we go as wide as possible + while (high.length) { + const bundle = high.pop()!; + preloadOne(bundle!, true); + } + /** + * The low priority bundles are opportunistic, and we want to give the browser some breathing room + * for other resources, so we cycle between 4 and 10 outstanding modulepreloads. + */ + if (highCount + lowCount < 5) { + while (highCount + lowCount < 10 && low.length) { + const bundle = low.pop()!; + preloadOne(bundle!); + } + } + if (DEBUG && !high.length && !low.length) { + const loaded = [...bundles.values()].filter((b) => b.$state$ >= BundleImportState.Loading); + const waitTime = loaded.reduce((acc, b) => acc + b.$waited$, 0); + const loadTime = loaded.reduce((acc, b) => acc + b.$loaded$, 0); + log(`done ${loaded.length} total: ${waitTime}ms waited, ${loadTime}ms loaded`); + } +}; + +const rel = + isBrowser && doc.createElement('link').relList.supports(modulePreloadStr) + ? modulePreloadStr + : preloadStr; +/** + * Note, we considered using `preload` for low priority bundles, but those don't get preparsed and + * that slows down interaction + */ +const preloadOne = (bundle: BundleImport, priority?: boolean) => { + if (checkLoaded(bundle)) { + return; + } + if ((priority && !bundle.$didLoadHigh$) || bundle.$state$ < BundleImportState.Loading) { + const start = Date.now(); + bundle.$waited$ = start - bundle.$created$; + bundle.$state$ = priority ? BundleImportState.LoadingHigh : BundleImportState.Loading; + bundle.$didLoadHigh$ = priority!; + if (bundle.$url$) { + DEBUG && + log(`load ${priority ? 'high' : 'low'} after ${`${bundle.$waited$}ms`}`, bundle.$name$); + const link = doc.createElement('link'); + link.href = bundle.$url$!; + link.rel = priority ? rel : 'preload'; + if (priority) { + link.rel = rel; + highCount++; + } else { + link.rel = 'preload'; + link.as = 'script'; + link.fetchPriority = 'low'; + lowCount++; + } + link.as = 'script'; + link.onload = link.onerror = () => { + const end = Date.now(); + bundle.$loaded$ = end - start; + DEBUG && log(`DONE ${bundle.$priority$ ? 'high' : 'low'} ${end - start}ms`, bundle.$name$); + link.remove(); + if (priority) { + highCount--; + } else { + lowCount--; + } + preload(bundle.$dynamicImports$); + trigger(); + }; + + doc.head.appendChild(link); + } + } + + // (re)queue dependencies + preload(bundle.$imports$, priority); + if (priority) { + preload(bundle.$dynamicImports$); + } +}; + +const makeBundle = (path: string, imports: string[], dynamicImports: string[]) => { + const url = path.endsWith('.js') ? new URL(`${base}${path}`, doc.baseURI).toString() : null; + return { + $name$: path, + $url$: url, + $state$: BundleImportState.None, + $imports$: imports, + $dynamicImports$: dynamicImports, + $priority$: false, + $created$: Date.now(), + $waited$: 0, + $loaded$: 0, + $didLoadHigh$: false, + }; +}; + +const parseBundleGraph = (text: string) => { + DEBUG && log(`parseBundleGraph ${text.length >> 10}kB`); + const graph = JSON.parse(text) as QwikBundleGraph; + let i = 0; + // All existing loading bundles need imports processed + const toProcess = [...bundles.values()] + .filter((bundle) => { + return bundle.$state$ >= BundleImportState.Loading; + }) + .reverse(); + while (i < graph.length) { + const name = graph[i++] as string; + const imports: string[] = []; + const dynamicImports: string[] = []; + let idx: number | string; + let collection = imports; + while (((idx = graph[i]), typeof idx === 'number')) { + if (idx === -1) { + collection = dynamicImports; + } else { + collection.push(graph[idx] as string); + } + i++; + } + if (bundles.has(name)) { + const bundle = bundles.get(name)!; + bundle.$imports$ = imports; + bundle.$dynamicImports$ = dynamicImports; + if (bundle.$state$ === BundleImportState.Loaded) { + bundle.$state$ = BundleImportState.Loading; + } + } else { + bundles.set(name, makeBundle(name, imports, dynamicImports)); + } + } + DEBUG && + log(`parseBundleGraph done ${bundles.size} bundles, will process ${toProcess.length} bundles`); + gotBundleGraph = true; + for (const bundle of toProcess) { + preload(bundle.$imports$, true); + preload(bundle.$dynamicImports$); + } +}; + +const handleBundle = (name: string, collection: BundleImport[], priority: boolean) => { + let bundle = bundles.get(name); + if (!bundle) { + if (gotBundleGraph) { + return; + } + bundle = makeBundle(name, [], []); + bundles.set(name, bundle); + } + if (checkLoaded(bundle)) { + return; + } + if (bundle.$state$ < BundleImportState.QueuedHigh) { + if (priority) { + bundle.$priority$ = true; + bundle.$state$ = BundleImportState.QueuedHigh; + collection.push(bundle); + } else { + bundle.$state$ = BundleImportState.QueuedLow; + collection.unshift(bundle); + } + return true; + } +}; + +let allOk = true; +/** + * Preload a bundle or bundles. Requires calling loadBundleGraph first. + * + * @internal + */ +const preload = (name: string | string[], priority?: boolean) => { + if (!isBrowser || !base || !name.length) { + return; + } + if (!allOk) { + return; + } + const queue = priority ? high : low; + let didQueue = false; + if (Array.isArray(name)) { + // We must process in reverse order to ensure first bundles are handled first + for (let i = name.length - 1; i >= 0; i--) { + didQueue = handleBundle(name[i], queue, priority!) || didQueue; + } + } else { + didQueue = handleBundle(name, queue, priority!)!; + } + if (didQueue) { + DEBUG && log(`queue ${priority ? 'high' : 'low'}`, name); + trigger(); + if (low.length > 5000) { + // just a precaution, should never happen + allOk = false; + } + } +}; + +/** + * Lazily load the bundle graph and then import dependencies of bundles that were loaded already. + * + * @internal + */ +const loadBundleGraph = (basePath: string, manifestHash: string) => { + if (!isBrowser || base) { + return; + } + base = basePath; + // TODO check TTI, maybe inject fetch link with timeout so we don't do the fetch directly + fetch(`${basePath}q-bundle-graph-${manifestHash}.json`) + .then((res) => res.text()) + .then((text) => parseBundleGraph(text)) + // We warn because it's not critical, and in the CI tests Windows serves up a HTML file instead the bundle graph sometimes, which breaks the tests that don't expect error logs + .catch(console.warn); +}; + +// Short names for minification +export { loadBundleGraph as l, preload as p }; diff --git a/packages/qwik/src/core/qrl/preload.ts b/packages/qwik/src/core/qrl/preload.ts deleted file mode 100644 index 755c6afbf6b..00000000000 --- a/packages/qwik/src/core/qrl/preload.ts +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Here we handle preloading of chunks. - * - * Given a symbol hash (in fact any string), we can find all the chunks that it depends on, via the - * bundle graph. We then generate preload link tags for each of those chunks. - * - * The priority is set to high for direct imports and low for indirect imports. - * - * There are several parts to this: - * - * - Load the bundle graph from the preload link tag that was injected during SSR - * - Given a string, find all the chunks that it depends on - * - Generate the preload link tags if needed - */ - -import { isDev } from '@builder.io/qwik/build'; -import type { QwikBundleGraph } from '../../optimizer/src/types'; -import { QBaseAttr, QManifestHash } from '../util/markers'; - -import { QContainerSelector } from '../util/markers'; - -let bundlesP: Promise | undefined; -enum BundleImportState { - None, - Low, - Loading, -} -type BundleImport = { - $url$: string | null; - $state$: BundleImportState; - $imports$: string[]; - $dynamicImports$: string[]; -}; -let bundles: Map | undefined; - -const wantedBundles = new Map(); - -const parseBundleGraph = (text: string, base: string) => { - try { - const graph = JSON.parse(text) as QwikBundleGraph; - bundles ||= new Map(); - let i = 0; - while (i < graph.length) { - const name = graph[i++] as string; - const url = name.endsWith('.js') ? `${base}${name}` : null; - const imports: string[] = []; - const dynamicImports: string[] = []; - let idx: number | string; - let collection = imports; - while (((idx = graph[i]), typeof idx === 'number')) { - if (idx === -1) { - collection = dynamicImports; - } else { - collection.push(graph[idx] as string); - } - i++; - } - bundles.set(name, { - $url$: url, - $state$: BundleImportState.None, - $imports$: imports, - $dynamicImports$: dynamicImports, - }); - } - for (const [name, priority] of wantedBundles) { - preload(name, priority); - } - wantedBundles.clear(); - } catch (e) { - console.error('Error parsing bundle graph', e, text); - throw e; - } -}; - -/** @internal */ -export const loadBundleGraph = (element: Element) => { - if (typeof window === 'undefined' || bundlesP) { - return; - } - const container = element.closest(QContainerSelector); - if (!container) { - return; - } - const hash = container.getAttribute(QManifestHash); - const base = container.getAttribute(QBaseAttr) || '/'; - const link = hash && (container.querySelector(`link#qwik-bg-${hash}`) as HTMLLinkElement | null); - if (!link) { - bundlesP = Promise.reject('No preload link found'); - // prevent uncaught promise rejection - bundlesP.catch(() => {}); - return; - } - bundlesP = fetch(link.href) - .then((res) => res.text()) - .then((text) => parseBundleGraph(text, base)) - .catch((e) => { - console.error('Error loading bundle graph, retrying later', e); - setTimeout(() => { - bundlesP = undefined; - }, 60000); - }); -}; - -// we stringify this in prefetch-implementation.ts -export const makeMakePreloadLink = - (canModulePreload: boolean | null) => (url: string, priority: boolean) => { - const link = document.createElement('link'); - if (canModulePreload === null) { - if (link.relList.supports('modulepreload')) { - canModulePreload = true; - } else { - canModulePreload = false; - } - } - link.rel = canModulePreload ? 'modulepreload' : 'preload'; - link.href = url; - link.fetchPriority = priority ? 'high' : 'low'; - if (!canModulePreload) { - link.as = 'script'; - } - link.onload = link.onerror = () => link.remove(); - - document.head.appendChild(link); - }; -const makePreloadLink = /*@__PURE__*/ makeMakePreloadLink(null); - -const prioritizeLink = (url: string) => { - const link = document.querySelector(`link[href="${url}"]`) as HTMLLinkElement | null; - if (link) { - link.fetchPriority = 'high'; - } -}; - -const preloadBundle = (bundle: BundleImport, priority: boolean) => { - if (bundle.$state$ >= BundleImportState.Loading) { - return; - } - if (bundle.$url$) { - if (bundle.$state$ === BundleImportState.None) { - makePreloadLink(bundle.$url$, priority); - } else if (priority && bundle.$state$ === BundleImportState.Low) { - prioritizeLink(bundle.$url$); - } else { - return; - } - } - bundle.$state$ = priority ? BundleImportState.Loading : BundleImportState.Low; -}; - -/** @internal */ -export const preload = (name: string, priority: boolean) => { - if (!bundles) { - wantedBundles.set(name, priority || !!wantedBundles.get(name)); - return; - } - const bundle = bundles.get(name); - if (!bundle) { - isDev && console.warn(`Bundle ${name} not found`); - return; - } - const isReprioritize = priority && bundle.$state$ === BundleImportState.Low; - if (bundle.$state$ !== BundleImportState.None && !isReprioritize) { - // prevent loops - return; - } - preloadBundle(bundle, priority); - for (const importName of bundle.$imports$) { - preload(importName, priority); - } - if (!isReprioritize) { - for (const importName of bundle.$dynamicImports$) { - preload(importName, false); - } - } -}; diff --git a/packages/qwik/src/core/qrl/qrl-class.ts b/packages/qwik/src/core/qrl/qrl-class.ts index 7314baee78e..36eca9b26f4 100644 --- a/packages/qwik/src/core/qrl/qrl-class.ts +++ b/packages/qwik/src/core/qrl/qrl-class.ts @@ -15,7 +15,8 @@ import { getQFuncs, QInstance } from '../util/markers'; import { isPromise, maybeThen } from '../util/promises'; import { qDev, qSerialize, qTest, seal } from '../util/qdev'; import { isArray, isFunction, type ValueOrPromise } from '../util/types'; -import { loadBundleGraph, preload } from './preload'; +// @ts-expect-error we don't have types for the preloader +import { l as loadBundleGraph, p as preload } from '@builder.io/qwik/preloader'; import type { QRLDev } from './qrl'; import type { QRL, QrlArgs, QrlReturn } from './qrl.public'; @@ -126,6 +127,8 @@ export const createQRL = ( }; const resolve = async (containerEl?: Element): Promise => { + // Give it another bump + preload(getSymbolHash(symbol), true); if (symbolRef !== null) { // Resolving (Promise) or already resolved (value) return symbolRef; @@ -226,7 +229,7 @@ export const createQRL = ( if (qDev) { seal(qrl); } - preload(hash, true); + preload(hash); return qrl; }; diff --git a/packages/qwik/src/optimizer/src/api.md b/packages/qwik/src/optimizer/src/api.md index 233569c9ae4..714efe23254 100644 --- a/packages/qwik/src/optimizer/src/api.md +++ b/packages/qwik/src/optimizer/src/api.md @@ -199,6 +199,7 @@ export interface QwikManifest { platform?: { [name: string]: string; }; + preloader?: string; symbols: { [symbolName: string]: QwikSymbol; }; diff --git a/packages/qwik/src/optimizer/src/manifest.ts b/packages/qwik/src/optimizer/src/manifest.ts index 68074e38498..7d80916f73a 100644 --- a/packages/qwik/src/optimizer/src/manifest.ts +++ b/packages/qwik/src/optimizer/src/manifest.ts @@ -1,5 +1,5 @@ import type { OutputBundle } from 'rollup'; -import { type NormalizedQwikPluginOptions } from './plugins/plugin'; +import { QWIK_PRELOADER_REAL_ID, type NormalizedQwikPluginOptions } from './plugins/plugin'; import type { GlobalInjections, Path, QwikBundle, QwikManifest, SegmentAnalysis } from './types'; // This is just the initial prioritization of the symbols and entries @@ -249,6 +249,7 @@ export function generateManifestFromBundles( symbols: {}, mapping: {}, bundles: {}, + preloader: '', injections, version: '1', options: { @@ -315,6 +316,9 @@ export function generateManifestFromBundles( .map((m) => path.relative(opts.rootDir, m)); if (modulePaths.length > 0) { bundle.origins = modulePaths; + if (modulePaths.some((m) => m.endsWith(QWIK_PRELOADER_REAL_ID))) { + manifest.preloader = bundleFileName; + } } manifest.bundles[bundleFileName] = bundle; diff --git a/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts b/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts index 818f247d158..8c6a6a3b719 100644 --- a/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts +++ b/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts @@ -58,16 +58,16 @@ export function convertManifestToBundleGraph( for (const bundleName of Object.keys(graph)) { const bundle = graph[bundleName]; const imports = bundle.imports?.filter((dep) => graph[dep]) || []; + const dynamicImports = bundle.dynamicImports?.filter((dep) => graph[dep]?.symbols) || []; - // We only include dynamic imports that have qrl segments - // If the dev wants to include other dynamic imports, they can just make a qrl() - const dynamicImports = bundle.dynamicImports?.filter((dep) => graph[dep]?.hasSegments) || []; - - // Overwrite so we don't mutate + /** + * Overwrite so we don't mutate the given objects. Be sure to copy all properties we use during + * and after the conversion. + */ graph[bundleName] = { + ...bundle, imports, dynamicImports, - size: bundle.size, }; } diff --git a/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts b/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts index f611a8d7245..e835731a8c7 100644 --- a/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts +++ b/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts @@ -21,24 +21,24 @@ describe('convertManifestToBundleGraph', () => { size: 0, imports: ['static-dep.js', 'transitive-dep.js', '@external-dep'], dynamicImports: ['has-a-symbol.js', 'no-symbols.js'], - hasSegments: true, + symbols: ['sym1'], }, 'transitive-dep.js': { size: 0, - hasSegments: true, + symbols: ['sym4'], }, 'not-used.js': { size: 0, }, 'has-a-symbol.js': { size: 0, - hasSegments: true, + symbols: ['sym2'], }, 'no-symbols.js': { size: 0, }, } as Record, - mapping: { sym1: 'app.js', sym2: 'has-a-symbol.js' }, + mapping: { sym1: 'dynamic-dep.js', sym2: 'has-a-symbol.js' }, symbols: {}, manifestHash: '123', version: '1.0.0', @@ -59,9 +59,9 @@ describe('convertManifestToBundleGraph', () => { 11, 'transitive-dep.js', // 10 'has-a-symbol.js', // 11 - 'sym1', // 13 - 0, - 'sym2', // 15 + 'sym1', // 12 + 5, + 'sym2', // 14 11, ]); }); @@ -84,7 +84,7 @@ describe('convertManifestToBundleGraph', () => { }, 'c.js': { size: 0, - hasSegments: true, + symbols: ['sym1'], }, } as Record, mapping: {}, @@ -134,12 +134,14 @@ describe('convertManifestToBundleGraph', () => { 8, 'transitive-dep.js', // 8 'has-a-symbol.js', // 9 - 'sym1', // 11 - 0, - 'sym2', // 13 + 'sym1', // 10 + 5, + 'sym2', // 12 9, - 'dashboard/', // 15 + 'dashboard/', // 14 2, + -1, + 8, ]); }); @@ -181,8 +183,6 @@ describe('convertManifestToBundleGraph', () => { "preload-helper.js", "root.js", 0, - "jn5XSz7NZ88", - 19, "5fYbrS6ABNA", 11, "DDeCLEw4BYU", @@ -197,6 +197,8 @@ describe('convertManifestToBundleGraph', () => { 13, "wEyctjlC58Q", 15, + "jn5XSz7NZ88", + 19, ] `); }); diff --git a/packages/qwik/src/optimizer/src/plugins/plugin.ts b/packages/qwik/src/optimizer/src/plugins/plugin.ts index 0868608e8c9..d40dee54490 100644 --- a/packages/qwik/src/optimizer/src/plugins/plugin.ts +++ b/packages/qwik/src/optimizer/src/plugins/plugin.ts @@ -485,6 +485,19 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) { id: QWIK_CLIENT_MANIFEST_ID, moduleSideEffects: false, }; + } else if (!devServer && !isServer && pathId.endsWith(QWIK_PRELOADER_ID)) { + debug(`resolveId(${count})`, 'Resolved', QWIK_PRELOADER_ID); + const preloader = await ctx.resolve(QWIK_PRELOADER_ID, importerId, { + skipSelf: true, + }); + if (preloader) { + ctx.emitFile({ + id: preloader.id, + type: 'chunk', + preserveSignature: 'allow-extension', + }); + return preloader; + } } else { const qrlMatch = /^(?.*\.[mc]?[jt]sx?)_(?[^/]+)\.js(?$|\?.*$)/.exec(id) ?.groups as { parent: string; name: string; query: string } | undefined; @@ -876,6 +889,11 @@ export const manifest = ${JSON.stringify(manifest)};\n`; } function manualChunks(id: string, { getModuleInfo }: Rollup.ManualChunkMeta) { + // The preloader has to stay in a separate chunk + if (id.endsWith(QWIK_PRELOADER_REAL_ID)) { + return 'qwik-preloader'; + } + // TODO when manual chunks fix lands in rollup, remove this guard if ((opts.entryStrategy as SmartEntryStrategy).manual) { const module = getModuleInfo(id)!; const segment = module.meta.segment as SegmentAnalysis | undefined; @@ -983,6 +1001,9 @@ export const QWIK_CORE_SERVER = '@builder.io/qwik/server'; export const QWIK_CLIENT_MANIFEST_ID = '@qwik-client-manifest'; +export const QWIK_PRELOADER_ID = '@builder.io/qwik/preloader'; +export const QWIK_PRELOADER_REAL_ID = 'qwik/dist/preloader.mjs'; + export const SRC_DIR_DEFAULT = 'src'; export const CLIENT_OUT_DIR = 'dist'; diff --git a/packages/qwik/src/optimizer/src/types.ts b/packages/qwik/src/optimizer/src/types.ts index 40d909192ab..85143cf6e2c 100644 --- a/packages/qwik/src/optimizer/src/types.ts +++ b/packages/qwik/src/optimizer/src/types.ts @@ -223,6 +223,8 @@ export interface QwikManifest { mapping: { [symbolName: string]: string }; /** All code bundles, used to know the import graph */ bundles: { [fileName: string]: QwikBundle }; + /** The preloader bundle */ + preloader?: string; /** CSS etc to inject in the document head */ injections?: GlobalInjections[]; version: string; diff --git a/packages/qwik/src/server/prefetch-implementation.ts b/packages/qwik/src/server/prefetch-implementation.ts index 8c4598f4fed..c6b5ebbb09c 100644 --- a/packages/qwik/src/server/prefetch-implementation.ts +++ b/packages/qwik/src/server/prefetch-implementation.ts @@ -1,11 +1,15 @@ import { Fragment, jsx, type JSXNode } from '@builder.io/qwik'; import { flattenPrefetchResources, getMostReferenced, workerFetchScript } from './prefetch-utils'; -import type { PrefetchImplementation, PrefetchResource, PrefetchStrategy } from './types'; -import { makeMakePreloadLink } from '../core/qrl/preload'; +import type { + PrefetchImplementation, + PrefetchResource, + PrefetchStrategy, + QwikManifest, +} from './types'; export function applyPrefetchImplementation( base: string, - manifestHash: string | undefined, + manifest: QwikManifest | undefined, prefetchStrategy: PrefetchStrategy | undefined, prefetchResources: PrefetchResource[], nonce?: string @@ -23,7 +27,7 @@ export function applyPrefetchImplementation( if (prefetchImpl.linkInsert === 'html-append') { linkHtmlImplementation( base, - manifestHash, + manifest?.manifestHash, nonce, prefetchNodes, prefetchResources, @@ -32,7 +36,7 @@ export function applyPrefetchImplementation( } if (prefetchImpl.linkInsert === 'js-append') { - linkJsImplementation(base, manifestHash, nonce, prefetchNodes, prefetchResources, prefetchImpl); + linkJsImplementation(base, manifest, nonce, prefetchNodes, prefetchResources, prefetchImpl); } else if (prefetchImpl.workerFetchInsert === 'always') { workerFetchImplementation(prefetchNodes, prefetchResources, nonce); } @@ -55,7 +59,7 @@ function prefetchUrlsEvent( prefetchNodes.push( jsx('link', { rel: 'modulepreload', - href: url, + href: base + url, nonce, }) ); @@ -103,7 +107,7 @@ function linkHtmlImplementation( : undefined; prefetchNodes.push( jsx('link', { - href: url, + href: base + url, rel, fetchpriority, nonce, @@ -114,56 +118,83 @@ function linkHtmlImplementation( } /** - * Uses JS to add the `` elements at runtime, and if the link prefetching isn't supported, - * it'll also add the web worker fetch. - * - * TODO use idle event + * Uses the preloader chunk to add the `` elements at runtime. This allows core to simply + * import the preloader as well and have all the state there, plus it makes it easy to write a + * complex implementation. */ function linkJsImplementation( base: string, - manifestHash: string | undefined, + manifest: QwikManifest | undefined, nonce: string | undefined, prefetchNodes: JSXNode[], prefetchResources: PrefetchResource[], prefetchImpl: Required ) { - const injector = makeMakePreloadLink.toString(); - const urls = flattenPrefetchResources(prefetchResources); - const fetchPriority = prefetchImpl.linkFetchPriority; - const forceLow = fetchPriority === 'low'; - const prio = []; - const low = []; - for (const [url, priority] of urls) { - if (!priority || forceLow) { - low.push(url); - } else { - prio.push(url); - } + const preloadChunk = manifest?.preloader; + if (!preloadChunk) { + return linkHtmlImplementation( + base, + manifest?.manifestHash, + nonce, + prefetchNodes, + prefetchResources, + prefetchImpl + ); } + const manifestHash = manifest.manifestHash; + const urlMap = flattenPrefetchResources(prefetchResources); + + // TODO modulepreload the preloader before ssr, optional because we can't predict if we need it + + // TODO order imports by size/number of dependents? + if (urlMap.size) { + const urls = [...urlMap.keys()]; + + // Already fetch the first 7 urls while we wait for the preloader to load + for (const url of urls.slice(0, 7)) { + prefetchNodes.push( + jsx('link', { + href: base + url, + // not modulepreload, we don't want to fetch dependencies yet + rel: 'preload', + as: 'script', + fetchpriority: 'low', + }) + ); + } - // Maybe this needs to be delayed - const script = ` - var _=(${injector})(null); - ${prio.length ? `${JSON.stringify(prio)}.forEach(u=>_(u,1));` : ''} - ${low.length ? `${JSON.stringify(low)}.forEach(u=>_(u,0));` : ''} - `.replaceAll(/^\s+|\s*\n/gm, ''); + // preload the bundle graph at low priority + // TODO make this a .js file so we can modulepreload it + prefetchNodes.push( + jsx('link', { + rel: 'fetch', + id: `qwik-bg-${manifestHash}`, + href: `${base}q-bundle-graph-${manifestHash}.json`, + as: 'fetch', + crossorigin: 'anonymous', + fetchpriority: 'low', + }) + ); - prefetchNodes.push( - jsx('script', { - type: 'module', - 'q:type': 'link-js', - dangerouslySetInnerHTML: script, - nonce, - }), - jsx('link', { - rel: 'fetch', - id: `qwik-bg-${manifestHash}`, - href: `${base}q-bundle-graph-${manifestHash}.json`, - as: 'fetch', - crossorigin: 'anonymous', - fetchpriority: prefetchImpl.linkFetchPriority || undefined, - }) - ); + // We request all the urls as low priority, so that newly needed resources are loaded first + // We use a Promise so the script doesn't block the initial page load + const script = + `const d=Date.now();console.log('preloader loading',d);` + + `import("${base}${preloadChunk}").then(({l,p})=>{` + + (`console.log('preloader start',Date.now()-d);` + + `l(${JSON.stringify(base)},${JSON.stringify(manifestHash)});` + + `p(${JSON.stringify([...urlMap.keys()])});`) + + `})`; + + prefetchNodes.push( + jsx('script', { + type: 'module', + 'q:type': 'link-js', + dangerouslySetInnerHTML: script, + nonce, + }) + ); + } } function workerFetchImplementation( diff --git a/packages/qwik/src/server/prefetch-strategy.ts b/packages/qwik/src/server/prefetch-strategy.ts index 6b4a4ae1137..d6ac5d2e6d7 100644 --- a/packages/qwik/src/server/prefetch-strategy.ts +++ b/packages/qwik/src/server/prefetch-strategy.ts @@ -79,15 +79,14 @@ function addBundle( bundleFileName: string, priority: boolean ) { - const url = buildBase + bundleFileName; - let prefetchResource = urls.get(url); + let prefetchResource = urls.get(bundleFileName); if (!prefetchResource) { prefetchResource = { - url, + url: bundleFileName, imports: [], priority, }; - urls.set(url, prefetchResource); + urls.set(bundleFileName, prefetchResource); const bundle = manifest.bundles[bundleFileName]; if (bundle) { diff --git a/packages/qwik/src/server/preloading.md b/packages/qwik/src/server/preloading.md new file mode 100644 index 00000000000..fe8ae0f87a0 --- /dev/null +++ b/packages/qwik/src/server/preloading.md @@ -0,0 +1,106 @@ +# Preloading + +(this is wip, need to explain how bundlegraph stores score modifiers and how they are used) + +When a user clicks a button, we want the code to be already there. +When they navigate to a new page, we want this to be as fast as possible. + +If code is missing, the user has to wait for it to load. Then, its static imports also have to load. More waiting, this is a waterfall. + +We could simply downlad all code at start, but on a slow connection or device this can mean that the code that is needed will be loaded last. + +We aim to minimize the time it takes for the code to be loaded and executed. For every interaction, there are some loading strategies that result in the shortest wait for the user. We try to find the best strategy for each case. + +Users will start to notice a latency in UI response from 200ms. At weak mobile speeds, that translates to about 8kB of data, but there's also the 100-500ms latency of 3G to consider. + +The Qwik Docs site has 2.3MB of Brotli-compressed JS. Over a weak mobile connection this easily can take a minute to download. So we need to split the code into bundles. + +We need to balance bundle size versus more bundles increasing latency via HTTP request overhead. + +## Strategy + +### Waterfall prevention + +To prevent waterfalls, we need to tell the browser which imports will be needed when an import loads. This is the entire static import graph, all the `import` statements from all the bundles that are loaded. + +We can do this because the majority of our imports are QRLs. When a QRL is run, we can use information about the import graph to give the browser the entire list of all imports that need to be loaded. + +### Loading code before it is needed + +If we have available bandwidth, we can download code before it is needed. We should determine which code is most likely to be needed, and download it first, as well as its static imports. + +To know which code is most likely to be needed, we can use the bundle scoring system. + +We will err on the side of caution, so if we are not sure, we will preload the bundle. This is better than making the user wait. In the end, all of the code that might be needed for the current page should be preloaded. However, we should make sure that bundles that are more likely to be needed are preloaded first. + +### Bundle scoring system + +Each bundle gets some metrics that will be used to score it. The scoring should help us decide which bundles to preload first. + +- Interactivity: How much impact to interactivity is there if the bundle is missing? + - score 0 to 5 + - a click handler is very interactive, but the Task that gets executed by the signal that changes might also be important + - qwik core is not interactive directly, so it gets 0. +- Size: How much code is there to download? + - score: percentage of total js bundle size, including all static imports and their static imports etc + - heavy bundles that are not interactive should not be preloaded at all, but very interactive bundles should be preloaded before others, so that smaller bundles can download in parallel. +- Likelihood of being needed + - score: chance % of being needed in the next 5 seconds + - this is a guess, based on the type of symbols in the bundle and the type of the bundle + - dependencies get the sum score of their importers + - Insights can be used to improve this guess, but this is only implemented for Link SPA preloading and bundle bundling. + - this also changes during execution: importing one bundle can increase the score of another. The extreme case is direct imports, they are then 100% sure to be needed. + +## Available techniques + +Note, currently we are only interested in preloading code, not assets. Furthermore, we only target browsers that support ES bundles. + +You can preload a bundle either declaratively or imperatively. + +### Declarative + +By declaring a bundle, you tell the browser to expect running the bundle soon. The browser can then decide what to do with it. + +The best way to preload is to use `` tags. This tells the browser to expect running the bundle soon. The browser will then download the bundle in the background, parse it and also download its static imports. + +If the browser does not support this, we can use `` tags. This is not as good, because the browser will not parse the code. + +At the time of writing, `bundlepreload` has 93% support and `preload` has 97% support. + +Note that once you add a preload tag, you can't control when the browser will download the bundle. Therefore you should not have too many tags at the same time, and the preloader keeps a queue of bundles to preload. The bundles with high likelihood are allowed to have more tags at the same time than the ones with low likelihood. + +### Imperative + +We can also simply `fetch` the bundle and discard the response. The browser the hopefully keeps it in cache. + +This is unlikely to be optimal because we don't have the same information as the browser about the currently available resources. + +It is a possible workaround for when devices don't support bundle preloading. We'll use this if we see a need. + +## Implementation + +### Bundle graph + +Ideally, we have a function that gets the current DOM, the browser position and the user interaction history and returns the likelihoods of the bundles. Perhaps one day we can use a neural network to do this, but for now we use simple heuristics. + +For each bundle, we have a list of scoring modifiers for other bundles. These are numbers that are added to the score of the bundle. + +We encode the scores in a "bundle graph". This is a compact representation of the known bundles and how they influence the likelihood of other bundles. + +### SSR + +We have early preloading, by adding `` tags to the SSR response. This should be used only for the bundles that are almost certain to be needed. + +Then, we inject a script tag that imports the preloader and passes the list of likely needed bundles to it. This list depends on the SSR result. + +Note that the preloader is a small bundle in a separate bundle that is also imported by Qwik itself, so its state is available instantly. Qwik can then request preloading for QRL segments etc. We tell the bundler to make sure to keep the preloader in a separate bundle. + +Once the preloader is loaded, it will start downloading the bundles in the background in order of their score. It also downloads the bundle graph so to have a complete picture. + +### QRL preloading + +When a QRL is created, we tell the preloader about the symbol with low priority. + +### Link preloading + +When a Link is visible, we can preload the likely bundles of the target page. The scoring could also use the popularity of target page, but this is not implemented yet. diff --git a/packages/qwik/src/server/render.ts b/packages/qwik/src/server/render.ts index f94d40a5d09..d93d8c8994a 100644 --- a/packages/qwik/src/server/render.ts +++ b/packages/qwik/src/server/render.ts @@ -177,7 +177,7 @@ export async function renderToStream( if (prefetchResources.length > 0) { const prefetchImpl = applyPrefetchImplementation( base, - resolvedManifest?.manifest.manifestHash, + resolvedManifest?.manifest, opts.prefetchStrategy, prefetchResources, opts.serverData?.nonce diff --git a/packages/qwik/tsconfig.json b/packages/qwik/tsconfig.json index c58f0237398..c969cbb2027 100644 --- a/packages/qwik/tsconfig.json +++ b/packages/qwik/tsconfig.json @@ -7,6 +7,7 @@ "@builder.io/qwik/jsx-dev-runtime": ["packages/qwik/src/jsx-runtime"], "@builder.io/qwik/build": ["packages/qwik/src/build"], "@builder.io/qwik/optimizer": ["packages/qwik/src/optimizer/src"], + "@builder.io/qwik/preloader": ["packages/qwik/src/preloader"], "@builder.io/qwik/server": ["packages/qwik/src/server"], "@builder.io/qwik/testing": ["packages/qwik/src/testing"] } diff --git a/scripts/build.ts b/scripts/build.ts index 5f68bf5c2a2..fc1ef77d599 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -30,6 +30,7 @@ import { tsc, tscQwik, tscQwikCity } from './tsc'; import { tscDocs } from './tsc-docs'; import { emptyDir, ensureDir, panic, type BuildConfig } from './util'; import { validateBuild } from './validate-build'; +import { submodulePreloader } from './submodule-preloader'; /** * Complete a full build for all of the package's submodules. Passed in config has all the correct @@ -69,6 +70,7 @@ export async function build(config: BuildConfig) { emptyDir(config.distQwikPkgDir); } + await submodulePreloader(config); await Promise.all([ submoduleCore(config), submoduleQwikLoader(config), @@ -186,7 +188,7 @@ export async function build(config: BuildConfig) { }); } } catch (e: any) { - panic(String(e ? e.stack || e : 'Error')); + panic(e); } } diff --git a/scripts/submodule-core.ts b/scripts/submodule-core.ts index 779ad822404..c9a13e924ff 100644 --- a/scripts/submodule-core.ts +++ b/scripts/submodule-core.ts @@ -22,7 +22,7 @@ async function submoduleCoreProd(config: BuildConfig) { const input: InputOptions = { input: join(config.tscDir, 'packages', 'qwik', 'src', 'core', 'index.js'), onwarn: rollupOnWarn, - external: ['@builder.io/qwik/build'], + external: ['@builder.io/qwik/build', '@builder.io/qwik/preloader'], plugins: [ { name: 'setVersion', @@ -57,6 +57,8 @@ async function submoduleCoreProd(config: BuildConfig) { sourcemap: true, globals: { '@builder.io/qwik/build': 'qwikBuild', + // not actually used + '@builder.io/qwik/preloader': 'qwikPreloader', }, banner: getBanner('@builder.io/qwik', config.distVersion), }; @@ -69,6 +71,7 @@ async function submoduleCoreProd(config: BuildConfig) { const inputCore = join(config.distQwikPkgDir, 'core.mjs'); const inputMin: InputOptions = { + external: ['@builder.io/qwik/preloader'], input: inputCore, onwarn: rollupOnWarn, plugins: [ @@ -221,9 +224,9 @@ async function submoduleCoreDev(config: BuildConfig) { }, }; - const esm = build({ + const esm = await build({ ...opts, - external: ['@builder.io/qwik/build'], + external: ['@builder.io/qwik/build', '@builder.io/qwik/preloader'], format: 'esm', outExtension: { '.js': '.mjs' }, }); diff --git a/scripts/submodule-preloader.ts b/scripts/submodule-preloader.ts new file mode 100644 index 00000000000..53ecb80bddb --- /dev/null +++ b/scripts/submodule-preloader.ts @@ -0,0 +1,72 @@ +import { join } from 'node:path'; +import { rollup, type InputOptions, type OutputOptions } from 'rollup'; +import { fileSize, rollupOnWarn, type BuildConfig } from './util'; +import { minify } from 'terser'; +import { transform } from 'esbuild'; + +/** + * Builds the qwikloader javascript files. These files can be used by other tooling, and are + * provided in the package so CDNs could point to them. The @builder.io/optimizer submodule also + * provides a utility function. + */ +export async function submodulePreloader(config: BuildConfig) { + const input: InputOptions = { + external: ['@builder.io/qwik/build'], + input: join(config.srcQwikDir, 'core/preloader.ts'), + onwarn: rollupOnWarn, + plugins: [ + { + name: 'qwikloaderTranspile', + resolveId(id) { + if (!id.endsWith('.ts')) { + return join(config.srcQwikDir, id + '.ts'); + } + return null; + }, + async transform(code, id) { + const result = await transform(code, { + sourcefile: id, + target: 'es2017', + format: 'esm', + loader: 'ts', + }); + + // Rename $properties$ to short names but leave the rest legible + // The final app will minify it when needed + + const minified = await minify(result.code, { + compress: { + // Trying to eliminate the enum declaration and failing + dead_code: true, + unused: true, + conditionals: true, + }, + mangle: { + toplevel: false, + module: false, + keep_fnames: true, + properties: { + regex: '^\\$.+\\$$', + }, + }, + }); + return minified.code; + }, + }, + ], + }; + + const output: OutputOptions = { + dir: config.distQwikPkgDir, + format: 'es', + entryFileNames: `preloader.mjs`, + exports: 'named', + }; + + const build = await rollup(input); + + await build.write(output); + + const preloaderSize = await fileSize(join(config.distQwikPkgDir, 'preloader.mjs')); + console.log(`🐮 preloader:`, preloaderSize); +} diff --git a/scripts/util.ts b/scripts/util.ts index a06732c1718..7da8724206c 100644 --- a/scripts/util.ts +++ b/scripts/util.ts @@ -283,8 +283,9 @@ export async function run( } } -export function panic(msg: string) { - console.error(`\n❌ ${msg}\n`, new Error(msg).stack); +export function panic(msg: string | Error) { + const err = typeof msg === 'string' ? new Error(msg) : msg; + console.error(`\n❌ `, err); process.exit(1); } diff --git a/starters/apps/perf.prod/vite.config.mts b/starters/apps/perf.prod/vite.config.mts new file mode 100644 index 00000000000..62f73900702 --- /dev/null +++ b/starters/apps/perf.prod/vite.config.mts @@ -0,0 +1,12 @@ +import { defineConfig } from "vite"; +import { qwikVite } from "@builder.io/qwik/optimizer"; +export default defineConfig({ + plugins: [ + qwikVite({ + debug: true, + }), + ], + build: { + minify: true, + }, +}); From 0c027bbe043216c0973573d77871341da21a9776 Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Fri, 4 Apr 2025 15:38:44 +0200 Subject: [PATCH 14/17] feat(repl): show/hide large code blocks --- .../docs/src/repl/repl-output-modules.tsx | 44 ++++++++++++------- packages/docs/src/repl/repl-output-panel.tsx | 8 +--- packages/docs/src/repl/types.ts | 3 +- .../docs/src/repl/worker/app-bundle-client.ts | 8 ---- 4 files changed, 32 insertions(+), 31 deletions(-) diff --git a/packages/docs/src/repl/repl-output-modules.tsx b/packages/docs/src/repl/repl-output-modules.tsx index 4cdf156fba0..ea6635b6e3c 100644 --- a/packages/docs/src/repl/repl-output-modules.tsx +++ b/packages/docs/src/repl/repl-output-modules.tsx @@ -1,4 +1,4 @@ -import { $, component$, useSignal } from '@builder.io/qwik'; +import { $, component$, createSignal, useSignal } from '@builder.io/qwik'; import { CodeBlock } from '../components/code-block/code-block'; import type { ReplModuleOutput } from './types'; const FILE_MODULE_DIV_ID = 'file-modules-client-modules'; @@ -35,22 +35,34 @@ export const ReplOutputModules = component$(({ outputs, headerText }: ReplOutput
- {outputs.map((o, i) => ( -
-
- {o.path} - {o.size ? ({o.size}) : null} + {outputs.map((o, i) => { + const isLarge = o.code.length > 3000; + if (isLarge && !o.shorten) { + o.shorten = createSignal(true); + } + const code = o.shorten?.value ? o.code.slice(0, 3000) : o.code; + return ( +
+
+ {o.path} + {o.size ? ({o.size}) : null} +
+
+ + {o.shorten && ( + + )} +
-
- -
-
- ))} + ); + })}
); diff --git a/packages/docs/src/repl/repl-output-panel.tsx b/packages/docs/src/repl/repl-output-panel.tsx index 5c2ba35c156..06385eb677f 100644 --- a/packages/docs/src/repl/repl-output-panel.tsx +++ b/packages/docs/src/repl/repl-output-panel.tsx @@ -1,4 +1,4 @@ -import { component$, useComputed$ } from '@builder.io/qwik'; +import { component$ } from '@builder.io/qwik'; import { CodeBlock } from '../components/code-block/code-block'; import { ReplOutputModules } from './repl-output-modules'; import { ReplOutputSymbols } from './repl-output-symbols'; @@ -8,10 +8,6 @@ import type { ReplAppInput, ReplStore } from './types'; export const ReplOutputPanel = component$(({ input, store }: ReplOutputPanelProps) => { const diagnosticsLen = store.diagnostics.length + store.monacoDiagnostics.length; - const clientBundlesNoCore = useComputed$(() => - // Qwik Core is not interesting and is large, slowing down the UI - store.clientBundles.filter((b) => !b.path.endsWith('qwikCore.js')) - ); return (
@@ -115,7 +111,7 @@ export const ReplOutputPanel = component$(({ input, store }: ReplOutputPanelProp ) : null} {store.selectedOutputPanel === 'clientBundles' ? ( - + ) : null} {store.selectedOutputPanel === 'serverModules' ? ( diff --git a/packages/docs/src/repl/types.ts b/packages/docs/src/repl/types.ts index faebfa0524b..11768f1cc4a 100644 --- a/packages/docs/src/repl/types.ts +++ b/packages/docs/src/repl/types.ts @@ -1,10 +1,10 @@ +import type { NoSerialize, Signal } from '@builder.io/qwik'; import type { Diagnostic, QwikManifest, QwikRollupPluginOptions, TransformModule, } from '@builder.io/qwik/optimizer'; -import type { NoSerialize } from '@builder.io/qwik'; export interface ReplAppInput { buildId: number; @@ -58,6 +58,7 @@ export interface ReplModuleOutput { path: string; code: string; size?: string; + shorten?: Signal; } export interface ReplMessageBase { diff --git a/packages/docs/src/repl/worker/app-bundle-client.ts b/packages/docs/src/repl/worker/app-bundle-client.ts index 6bc1dcbd5a1..4a2f6aa582f 100644 --- a/packages/docs/src/repl/worker/app-bundle-client.ts +++ b/packages/docs/src/repl/worker/app-bundle-client.ts @@ -104,14 +104,6 @@ export const appBundleClient = async ( }); } - result.transformedModules = result.transformedModules.filter((f) => { - return ( - !f.path.endsWith('app.js') && - !f.path.endsWith('entry.server.js') && - !f.path.endsWith('root.js') - ); - }); - result.events.push({ kind: 'console-log', scope: 'build', From 68961333b1356ba307ffb1687c9392d820255b1f Mon Sep 17 00:00:00 2001 From: maiieul Date: Fri, 28 Mar 2025 16:46:21 +0100 Subject: [PATCH 15/17] refactor: opts.entryStrategy.manual check in manualChunks --- .../qwik/src/optimizer/src/plugins/plugin.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/qwik/src/optimizer/src/plugins/plugin.ts b/packages/qwik/src/optimizer/src/plugins/plugin.ts index d40dee54490..bd2d2bb274c 100644 --- a/packages/qwik/src/optimizer/src/plugins/plugin.ts +++ b/packages/qwik/src/optimizer/src/plugins/plugin.ts @@ -893,17 +893,14 @@ export const manifest = ${JSON.stringify(manifest)};\n`; if (id.endsWith(QWIK_PRELOADER_REAL_ID)) { return 'qwik-preloader'; } - // TODO when manual chunks fix lands in rollup, remove this guard - if ((opts.entryStrategy as SmartEntryStrategy).manual) { - const module = getModuleInfo(id)!; - const segment = module.meta.segment as SegmentAnalysis | undefined; - - if (segment) { - const { hash } = segment; - const chunkName = (opts.entryStrategy as SmartEntryStrategy).manual![hash] || segment.entry; - if (chunkName) { - return chunkName; - } + + const module = getModuleInfo(id)!; + const segment = module.meta.segment as SegmentAnalysis | undefined; + if (segment) { + const { hash } = segment; + const chunkName = (opts.entryStrategy as SmartEntryStrategy).manual?.[hash] || segment.entry; + if (chunkName) { + return chunkName; } } return null; From da5ed6442f3cff350cd982f47af4c49a7e88767f Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Thu, 27 Mar 2025 17:05:03 +0100 Subject: [PATCH 16/17] feat(preloader): use probabilities to sort preloads --- packages/docs/src/entry.ssr.tsx | 2 - .../src/routes/api/qwik-optimizer/api.json | 6 +- .../src/routes/api/qwik-optimizer/index.md | 68 +- .../docs/src/routes/api/qwik-server/api.json | 10 +- .../docs/src/routes/api/qwik-server/index.md | 142 +- packages/docs/src/routes/api/qwik/api.json | 4 +- packages/docs/src/routes/api/qwik/index.md | 2 +- .../advanced/modules-prefetching/index.mdx | 89 +- .../speculative-module-fetching/index.mdx | 8 +- .../deployments/cloudflare-pages/index.mdx | 3 +- packages/docs/vite.config.mts | 2 +- packages/insights/src/entry.ssr.tsx | 2 - .../src/buildtime/vite/dev-server.ts | 14 +- .../buildtime/vite/get-route-imports.unit.ts | 37 +- .../qwik-city/src/buildtime/vite/image-jsx.ts | 2 +- .../qwik-city/src/buildtime/vite/plugin.ts | 2 +- .../src/middleware/aws-lambda/index.ts | 2 +- .../request-handler/request-event.ts | 39 +- .../request-handler/request-handler.ts | 3 +- .../request-handler/user-response.ts | 11 +- packages/qwik-labs/src/entry.ssr.tsx | 6 +- packages/qwik-react/src/entry.ssr.tsx | 6 +- packages/qwik/src/core/preloader.ts | 317 -- .../qwik/src/core/preloader/bundle-graph.ts | 149 + packages/qwik/src/core/preloader/constants.ts | 23 + packages/qwik/src/core/preloader/index.ts | 12 + packages/qwik/src/core/preloader/queue.ts | 222 ++ packages/qwik/src/core/preloader/types.ts | 35 + packages/qwik/src/core/qrl/qrl-class.ts | 22 +- packages/qwik/src/optimizer/src/api.md | 10 +- packages/qwik/src/optimizer/src/manifest.ts | 173 +- .../src/optimizer/src/plugins/bundle-graph.ts | 84 +- .../src/plugins/bundle-graph.unit.ts | 446 ++- .../src/plugins/fixture-output-bundles.json | 2945 ++++++++++++++--- .../qwik/src/optimizer/src/plugins/plugin.ts | 99 +- .../qwik/src/optimizer/src/plugins/rollup.ts | 38 +- .../optimizer/src/plugins/vite-dev-server.ts | 18 +- .../src/optimizer/src/plugins/vite-utils.ts | 14 + .../qwik/src/optimizer/src/plugins/vite.ts | 67 +- packages/qwik/src/optimizer/src/types.ts | 23 +- packages/qwik/src/server/api.md | 21 +- packages/qwik/src/server/index.ts | 2 +- .../src/server/prefetch-implementation.ts | 298 +- packages/qwik/src/server/prefetch-strategy.ts | 150 +- packages/qwik/src/server/prefetch-utils.ts | 40 +- .../qwik/src/server/prefetch-utils.unit.ts | 21 +- packages/qwik/src/server/preloading.md | 87 +- packages/qwik/src/server/render.ts | 51 +- packages/qwik/src/server/server-modules.d.ts | 2 +- packages/qwik/src/server/types.ts | 72 +- packages/qwik/tsconfig.json | 3 +- scripts/submodule-core.ts | 4 + scripts/submodule-preloader.ts | 89 +- scripts/submodule-server.ts | 10 + .../aws-lambda/src/entry_aws-lambda.tsx | 3 +- .../azure-swa/src/entry.azure-swa.tsx | 3 +- starters/adapters/bun/src/entry.bun.ts | 2 - .../cloud-run/src/entry.cloud-run.tsx | 2 - .../src/entry.cloudflare-pages.tsx | 3 +- starters/adapters/deno/src/entry.deno.ts | 2 - .../adapters/express/src/entry.express.tsx | 2 - .../adapters/firebase/src/entry-firebase.tsx | 3 +- .../netlify-edge/src/entry.netlify-edge.tsx | 3 +- .../node-server/src/entry.node-server.tsx | 2 - .../vercel-edge/src/entry.vercel-edge.tsx | 3 +- starters/apps/base/src/entry.ssr.tsx | 2 - starters/apps/library/src/entry.ssr.tsx | 6 +- starters/apps/perf.prod/src/entry.ssr.tsx | 6 +- .../apps/preloader-test/src/entry.ssr.tsx | 8 +- .../apps/qwikcity-test.prod/src/entry.ssr.tsx | 2 - starters/apps/qwikcity-test/src/entry.ssr.tsx | 2 - .../starter-partytown-test/src/entry.ssr.tsx | 6 +- starters/apps/todo-test/src/entry.ssr.tsx | 6 +- starters/e2e/e2e.containers.spec.ts | 8 +- starters/e2e/e2e.signals.spec.ts | 3 +- starters/features/localize/src/entry.ssr.tsx | 2 - 76 files changed, 4344 insertions(+), 1742 deletions(-) delete mode 100644 packages/qwik/src/core/preloader.ts create mode 100644 packages/qwik/src/core/preloader/bundle-graph.ts create mode 100644 packages/qwik/src/core/preloader/constants.ts create mode 100644 packages/qwik/src/core/preloader/index.ts create mode 100644 packages/qwik/src/core/preloader/queue.ts create mode 100644 packages/qwik/src/core/preloader/types.ts diff --git a/packages/docs/src/entry.ssr.tsx b/packages/docs/src/entry.ssr.tsx index b88f9909ae5..f16130af5a2 100644 --- a/packages/docs/src/entry.ssr.tsx +++ b/packages/docs/src/entry.ssr.tsx @@ -1,10 +1,8 @@ import { renderToStream, type RenderToStreamOptions } from '@builder.io/qwik/server'; -import { manifest } from '@qwik-client-manifest'; import Root from './root'; export default function (opts: RenderToStreamOptions) { return renderToStream(, { - manifest, qwikLoader: { // The docs can be long so make sure to intercept events before the end of the document. position: 'top', diff --git a/packages/docs/src/routes/api/qwik-optimizer/api.json b/packages/docs/src/routes/api/qwik-optimizer/api.json index 19dfabf8136..63b6cdd0794 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/api.json +++ b/packages/docs/src/routes/api/qwik-optimizer/api.json @@ -406,7 +406,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface QwikBundle \n```\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[dynamicImports?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_ Dynamic imports\n\n\n
\n\n[imports?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_ Direct imports\n\n\n
\n\n[origins?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_ Source files of the bundle\n\n\n
\n\n[size](#)\n\n\n\n\n\n\n\nnumber\n\n\n\n\nSize of the bundle\n\n\n
\n\n[symbols?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_ Symbols in the bundle\n\n\n
", + "content": "```typescript\nexport interface QwikBundle \n```\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[dynamicImports?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_ Dynamic imports\n\n\n
\n\n[imports?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_ Direct imports\n\n\n
\n\n[interactivity?](#)\n\n\n\n\n\n\n\nnumber\n\n\n\n\n_(Optional)_ Interactivity score of the bundle\n\n\n
\n\n[origins?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_ Source files of the bundle\n\n\n
\n\n[size](#)\n\n\n\n\n\n\n\nnumber\n\n\n\n\nSize of the bundle\n\n\n
\n\n[symbols?](#)\n\n\n\n\n\n\n\nstring\\[\\]\n\n\n\n\n_(Optional)_ Symbols in the bundle\n\n\n
\n\n[total](#)\n\n\n\n\n\n\n\nnumber\n\n\n\n\nTotal size of this bundle's static import graph\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts", "mdFile": "qwik.qwikbundle.md" }, @@ -434,7 +434,7 @@ } ], "kind": "Interface", - "content": "The metadata of the build. One of its uses is storing where QRL symbols are located.\n\n\n```typescript\nexport interface QwikManifest \n```\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[bundles](#)\n\n\n\n\n\n\n\n{ \\[fileName: string\\]: [QwikBundle](#qwikbundle); }\n\n\n\n\nAll code bundles, used to know the import graph\n\n\n
\n\n[injections?](#)\n\n\n\n\n\n\n\n[GlobalInjections](#globalinjections)\\[\\]\n\n\n\n\n_(Optional)_ CSS etc to inject in the document head\n\n\n
\n\n[manifestHash](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\nContent hash of the manifest, if this changes, the code changed\n\n\n
\n\n[mapping](#)\n\n\n\n\n\n\n\n{ \\[symbolName: string\\]: string; }\n\n\n\n\nWhere QRLs are located\n\n\n
\n\n[options?](#)\n\n\n\n\n\n\n\n{ target?: string; buildMode?: string; entryStrategy?: { \\[key: string\\]: any; }; }\n\n\n\n\n_(Optional)_\n\n\n
\n\n[platform?](#)\n\n\n\n\n\n\n\n{ \\[name: string\\]: string; }\n\n\n\n\n_(Optional)_\n\n\n
\n\n[preloader?](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n_(Optional)_ The preloader bundle\n\n\n
\n\n[symbols](#)\n\n\n\n\n\n\n\n{ \\[symbolName: string\\]: [QwikSymbol](#qwiksymbol); }\n\n\n\n\nQRL symbols\n\n\n
\n\n[version](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
", + "content": "The metadata of the build. One of its uses is storing where QRL symbols are located.\n\n\n```typescript\nexport interface QwikManifest \n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[bundleGraph?](#)\n\n\n\n\n\n\n\n[QwikBundleGraph](#qwikbundlegraph)\n\n\n\n\n_(Optional)_ All bundles in a compact graph format with probabilities\n\n\n
\n\n[bundles](#)\n\n\n\n\n\n\n\n{ \\[fileName: string\\]: [QwikBundle](#qwikbundle); }\n\n\n\n\nAll code bundles, used to know the import graph\n\n\n
\n\n[injections?](#)\n\n\n\n\n\n\n\n[GlobalInjections](#globalinjections)\\[\\]\n\n\n\n\n_(Optional)_ CSS etc to inject in the document head\n\n\n
\n\n[manifestHash](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\nContent hash of the manifest, if this changes, the code changed\n\n\n
\n\n[mapping](#)\n\n\n\n\n\n\n\n{ \\[symbolName: string\\]: string; }\n\n\n\n\nWhere QRLs are located\n\n\n
\n\n[options?](#)\n\n\n\n\n\n\n\n{ target?: string; buildMode?: string; entryStrategy?: { type: [EntryStrategy](#entrystrategy)\\['type'\\]; }; }\n\n\n\n\n_(Optional)_ The options used to build the manifest\n\n\n
\n\n[platform?](#)\n\n\n\n\n\n\n\n{ \\[name: string\\]: string; }\n\n\n\n\n_(Optional)_ The platform used to build the manifest\n\n\n
\n\n[preloader?](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n_(Optional)_ The preloader bundle fileName\n\n\n
\n\n[symbols](#)\n\n\n\n\n\n\n\n{ \\[symbolName: string\\]: [QwikSymbol](#qwiksymbol); }\n\n\n\n\nQRL symbols\n\n\n
\n\n[version](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\nThe version of the manifest\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts", "mdFile": "qwik.qwikmanifest.md" }, @@ -594,7 +594,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface ResolvedManifest \n```\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[manifest](#)\n\n\n\n\n\n\n\n[QwikManifest](#qwikmanifest)\n\n\n\n\n\n
\n\n[mapper](#)\n\n\n\n\n\n\n\n[SymbolMapper](#symbolmapper)\n\n\n\n\n\n
", + "content": "```typescript\nexport interface ResolvedManifest \n```\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[injections](#)\n\n\n\n\n\n\n\n[GlobalInjections](#globalinjections)\\[\\]\n\n\n\n\n\n
\n\n[manifest](#)\n\n\n\n\n\n\n\n[QwikManifest](#qwikmanifest)\n\n\n\n\n\n
\n\n[mapper](#)\n\n\n\n\n\n\n\n[SymbolMapper](#symbolmapper)\n\n\n\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts", "mdFile": "qwik.resolvedmanifest.md" }, diff --git a/packages/docs/src/routes/api/qwik-optimizer/index.md b/packages/docs/src/routes/api/qwik-optimizer/index.md index b156b9a1384..722bdf1e224 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/index.md +++ b/packages/docs/src/routes/api/qwik-optimizer/index.md @@ -1287,6 +1287,21 @@ _(Optional)_ Direct imports +[interactivity?](#) + + + + + +number + + + +_(Optional)_ Interactivity score of the bundle + + + + [origins?](#) @@ -1329,6 +1344,21 @@ string[] _(Optional)_ Symbols in the bundle + + + +[total](#) + + + + + +number + + + +Total size of this bundle's static import graph + @@ -1373,6 +1403,21 @@ Description +[bundleGraph?](#) + + + + + +[QwikBundleGraph](#qwikbundlegraph) + + + +_(Optional)_ All bundles in a compact graph format with probabilities + + + + [bundles](#) @@ -1439,11 +1484,11 @@ Where QRLs are located -{ target?: string; buildMode?: string; entryStrategy?: { [key: string]: any; }; } +{ target?: string; buildMode?: string; entryStrategy?: { type: [EntryStrategy](#entrystrategy)['type']; }; } -_(Optional)_ +_(Optional)_ The options used to build the manifest @@ -1458,7 +1503,7 @@ _(Optional)_ -_(Optional)_ +_(Optional)_ The platform used to build the manifest @@ -1473,7 +1518,7 @@ string -_(Optional)_ The preloader bundle +_(Optional)_ The preloader bundle fileName @@ -1503,6 +1548,8 @@ string +The version of the manifest + @@ -2307,6 +2354,19 @@ Description +[injections](#) + + + + + +[GlobalInjections](#globalinjections)[] + + + + + + [manifest](#) diff --git a/packages/docs/src/routes/api/qwik-server/api.json b/packages/docs/src/routes/api/qwik-server/api.json index 52935809bbf..e8e8f2a706c 100644 --- a/packages/docs/src/routes/api/qwik-server/api.json +++ b/packages/docs/src/routes/api/qwik-server/api.json @@ -82,7 +82,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface PrefetchImplementation \n```\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[linkFetchPriority?](#)\n\n\n\n\n\n\n\n'auto' \\| 'low' \\| 'high' \\| null\n\n\n\n\n_(Optional)_ Value of the `` attribute when link is used. Defaults to `null`.\n\n\n
\n\n[linkInsert?](#)\n\n\n\n\n\n\n\n'js-append' \\| 'html-append' \\| null\n\n\n\n\n_(Optional)_ `js-append`: Use JS runtime to create each `` and append to the head.\n\n`html-append`: Render each `` within html, appended at the end of the body.\n\nDefaults to `js-append`.\n\n\n
\n\n[linkRel?](#)\n\n\n\n\n\n\n\n'prefetch' \\| 'preload' \\| 'modulepreload' \\| null\n\n\n\n\n_(Optional)_ Value of the `` attribute when link is used. Defaults to `modulepreload`.\n\n\n
\n\n[prefetchEvent?](#)\n\n\n\n\n\n\n\n'always' \\| null\n\n\n\n\n_(Optional)_ Dispatch a `qprefetch` event with detail data containing the bundles that should be prefetched. The event dispatch script will be inlined into the document's HTML so any listeners of this event should already be ready to handle the event.\n\nThis implementation will inject a script similar to:\n\n```\n\n```\nBy default, the `prefetchEvent` implementation will be set to `null`.\n\n\n
\n\n[workerFetchInsert?](#)\n\n\n\n\n\n\n\n'always' \\| 'no-link-support' \\| null\n\n\n\n\n_(Optional)_ `always`: Always include the worker fetch JS runtime.\n\n`no-link-support`: Only include the worker fetch JS runtime when the browser doesn't support `` prefetch/preload/modulepreload.\n\nDefaults to `null`.\n\n\n
", + "content": "```typescript\nexport interface PrefetchImplementation \n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[debug?](#)\n\n\n\n\n\n\n\nboolean\n\n\n\n\n_(Optional)_ If true, the preloader will log debug information to the console.\n\nDefaults to `false`\n\n\n
\n\n[linkFetchPriority?](#)\n\n\n\n\n\n\n\n'auto' \\| 'low' \\| 'high' \\| null\n\n\n\n\n_(Optional)_ Value of the `` attribute when links are added. Defaults to `null`.\n\n\n
\n\n[linkInsert?](#)\n\n\n\n\n\n\n\n'js-append' \\| 'html-append' \\| null\n\n\n\n\n_(Optional)_\n\n\n
\n\n[linkRel?](#)\n\n\n\n\n\n\n\n'prefetch' \\| 'preload' \\| 'modulepreload' \\| null\n\n\n\n\n_(Optional)_ Value of the `` attribute when links are added. The preloader itself will autodetect which attribute to use based on the browser capabilities.\n\nDefaults to `modulepreload`.\n\n\n
\n\n[maxPreloads?](#)\n\n\n\n\n\n\n\nnumber\n\n\n\n\n_(Optional)_ Maximum number of preload links to add during SSR. These instruct the browser to preload likely bundles before the preloader script is active. This includes the 2 preloads used for the preloader script itself and the bundle information. Setting this to 0 will disable all preload links.\n\nDefaults to `5`\n\n\n
\n\n[maxSimultaneousPreloads?](#)\n\n\n\n\n\n\n\nnumber\n\n\n\n\n_(Optional)_ Maximum number of simultaneous preload links that the preloader will maintain.\n\nDefaults to `5`\n\n\n
\n\n[minPreloadProbability?](#)\n\n\n\n\n\n\n\nnumber\n\n\n\n\n_(Optional)_ The minimum probability for a bundle to be added to the preload queue.\n\nDefaults to `0.25` (25% probability)\n\n\n
\n\n[minProbability?](#)\n\n\n\n\n\n\n\nnumber\n\n\n\n\n_(Optional)_ The minimum probability of a bundle to be added as a preload link during SSR.\n\nDefaults to `0.6` (60% probability)\n\n\n
\n\n[prefetchEvent?](#)\n\n\n\n\n\n\n\n'always' \\| null\n\n\n\n\n_(Optional)_\n\n\n
\n\n[workerFetchInsert?](#)\n\n\n\n\n\n\n\n'always' \\| 'no-link-support' \\| null\n\n\n\n\n_(Optional)_\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/server/types.ts", "mdFile": "qwik.prefetchimplementation.md" }, @@ -96,7 +96,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface PrefetchResource \n```\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[imports](#)\n\n\n\n\n\n\n\n[PrefetchResource](#prefetchresource)\\[\\]\n\n\n\n\n\n
\n\n[priority](#)\n\n\n\n\n\n\n\nboolean\n\n\n\n\n\n
\n\n[url](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
", + "content": "```typescript\nexport interface PrefetchResource \n```\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[imports](#)\n\n\n\n\n\n\n\n[PrefetchResource](#prefetchresource)\\[\\]\n\n\n\n\n\n
\n\n[url](#)\n\n\n\n\n\n\n\nstring\n\n\n\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/server/types.ts", "mdFile": "qwik.prefetchresource.md" }, @@ -292,7 +292,7 @@ } ], "kind": "Function", - "content": "```typescript\nexport declare function resolveManifest(manifest: QwikManifest | ResolvedManifest | undefined): ResolvedManifest | undefined;\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nmanifest\n\n\n\n\nQwikManifest \\| ResolvedManifest \\| undefined\n\n\n\n\n\n
\n**Returns:**\n\nResolvedManifest \\| undefined", + "content": "Merges a given manifest with the built manifest and provides mappings for symbols.\n\n\n```typescript\nexport declare function resolveManifest(manifest?: Partial | undefined): ResolvedManifest | undefined;\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nmanifest\n\n\n\n\nPartial<QwikManifest \\| ResolvedManifest> \\| undefined\n\n\n\n\n_(Optional)_\n\n\n
\n**Returns:**\n\nResolvedManifest \\| undefined", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/server/render.ts", "mdFile": "qwik.resolvemanifest.md" }, @@ -306,7 +306,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface SerializeDocumentOptions \n```\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[debug?](#)\n\n\n\n\n\n\n\nboolean\n\n\n\n\n_(Optional)_\n\n\n
\n\n[manifest?](#)\n\n\n\n\n\n\n\nQwikManifest \\| ResolvedManifest\n\n\n\n\n_(Optional)_\n\n\n
\n\n[symbolMapper?](#)\n\n\n\n\n\n\n\nSymbolMapperFn\n\n\n\n\n_(Optional)_\n\n\n
", + "content": "```typescript\nexport interface SerializeDocumentOptions \n```\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\n[debug?](#)\n\n\n\n\n\n\n\nboolean\n\n\n\n\n_(Optional)_\n\n\n
\n\n[manifest?](#)\n\n\n\n\n\n\n\nPartial<QwikManifest \\| ResolvedManifest>\n\n\n\n\n_(Optional)_\n\n\n
\n\n[symbolMapper?](#)\n\n\n\n\n\n\n\nSymbolMapperFn\n\n\n\n\n_(Optional)_\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/server/types.ts", "mdFile": "qwik.serializedocumentoptions.md" }, @@ -320,7 +320,7 @@ } ], "kind": "Function", - "content": "```typescript\nexport declare function setServerPlatform(manifest: QwikManifest | ResolvedManifest | undefined): Promise;\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nmanifest\n\n\n\n\nQwikManifest \\| ResolvedManifest \\| undefined\n\n\n\n\n\n
\n**Returns:**\n\nPromise<void>", + "content": "```typescript\nexport declare function setServerPlatform(manifest?: Partial): Promise;\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nmanifest\n\n\n\n\nPartial<QwikManifest \\| ResolvedManifest>\n\n\n\n\n_(Optional)_\n\n\n
\n**Returns:**\n\nPromise<void>", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/server/index.ts", "mdFile": "qwik.setserverplatform.md" }, diff --git a/packages/docs/src/routes/api/qwik-server/index.md b/packages/docs/src/routes/api/qwik-server/index.md index 904a8c646e2..fe5279e0a25 100644 --- a/packages/docs/src/routes/api/qwik-server/index.md +++ b/packages/docs/src/routes/api/qwik-server/index.md @@ -233,6 +233,23 @@ Description +[debug?](#) + + + + + +boolean + + + +_(Optional)_ If true, the preloader will log debug information to the console. + +Defaults to `false` + + + + [linkFetchPriority?](#) @@ -243,7 +260,7 @@ Description -_(Optional)_ Value of the `` attribute when link is used. Defaults to `null`. +_(Optional)_ Value of the `` attribute when links are added. Defaults to `null`. @@ -258,11 +275,7 @@ _(Optional)_ Value of the `` attribute when link is us -_(Optional)_ `js-append`: Use JS runtime to create each `` and append to the head. - -`html-append`: Render each `` within html, appended at the end of the body. - -Defaults to `js-append`. +_(Optional)_ @@ -277,51 +290,107 @@ Defaults to `js-append`. -_(Optional)_ Value of the `` attribute when link is used. Defaults to `modulepreload`. +_(Optional)_ Value of the `` attribute when links are added. The preloader itself will autodetect which attribute to use based on the browser capabilities. + +Defaults to `modulepreload`. -[prefetchEvent?](#) +[maxPreloads?](#) -'always' \| null +number -_(Optional)_ Dispatch a `qprefetch` event with detail data containing the bundles that should be prefetched. The event dispatch script will be inlined into the document's HTML so any listeners of this event should already be ready to handle the event. +_(Optional)_ Maximum number of preload links to add during SSR. These instruct the browser to preload likely bundles before the preloader script is active. This includes the 2 preloads used for the preloader script itself and the bundle information. Setting this to 0 will disable all preload links. -This implementation will inject a script similar to: +Defaults to `5` -``` - -``` + + + +[maxSimultaneousPreloads?](#) + + -By default, the `prefetchEvent` implementation will be set to `null`. + + +number + + + +_(Optional)_ Maximum number of simultaneous preload links that the preloader will maintain. + +Defaults to `5` -[workerFetchInsert?](#) +[minPreloadProbability?](#) -'always' \| 'no-link-support' \| null +number + + + +_(Optional)_ The minimum probability for a bundle to be added to the preload queue. + +Defaults to `0.25` (25% probability) + + + + +[minProbability?](#) -_(Optional)_ `always`: Always include the worker fetch JS runtime. + -`no-link-support`: Only include the worker fetch JS runtime when the browser doesn't support `` prefetch/preload/modulepreload. +number -Defaults to `null`. + + +_(Optional)_ The minimum probability of a bundle to be added as a preload link during SSR. + +Defaults to `0.6` (60% probability) + + + + +[prefetchEvent?](#) + + + + + +'always' \| null + + + +_(Optional)_ + + + + +[workerFetchInsert?](#) + + + + + +'always' \| 'no-link-support' \| null + + + +_(Optional)_ @@ -366,19 +435,6 @@ Description -[priority](#) - - - - - -boolean - - - - - - [url](#) @@ -1005,9 +1061,11 @@ string ## resolveManifest +Merges a given manifest with the built manifest and provides mappings for symbols. + ```typescript export declare function resolveManifest( - manifest: QwikManifest | ResolvedManifest | undefined, + manifest?: Partial | undefined, ): ResolvedManifest | undefined; ``` @@ -1030,10 +1088,12 @@ manifest -QwikManifest \| ResolvedManifest \| undefined +Partial<QwikManifest \| ResolvedManifest> \| undefined +_(Optional)_ + **Returns:** @@ -1088,7 +1148,7 @@ _(Optional)_ -QwikManifest \| ResolvedManifest +Partial<QwikManifest \| ResolvedManifest> @@ -1118,7 +1178,7 @@ _(Optional)_ ```typescript export declare function setServerPlatform( - manifest: QwikManifest | ResolvedManifest | undefined, + manifest?: Partial, ): Promise; ``` @@ -1141,10 +1201,12 @@ manifest -QwikManifest \| ResolvedManifest \| undefined +Partial<QwikManifest \| ResolvedManifest> +_(Optional)_ + **Returns:** diff --git a/packages/docs/src/routes/api/qwik/api.json b/packages/docs/src/routes/api/qwik/api.json index bdb8463f87d..d03d9d6e066 100644 --- a/packages/docs/src/routes/api/qwik/api.json +++ b/packages/docs/src/routes/api/qwik/api.json @@ -1774,7 +1774,7 @@ } ], "kind": "Function", - "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n> Warning: This API is now obsolete.\n> \n> This is no longer needed as the preloading happens automatically in qrl-class.ts. Leave this in your app for a while so it uninstalls existing service workers, but don't use it for new projects.\n> \n\n\n```typescript\nPrefetchServiceWorker: (opts: {\n base?: string;\n scope?: string;\n path?: string;\n verbose?: boolean;\n fetchBundleGraph?: boolean;\n nonce?: string;\n}) => JSXNode<'script'>\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nopts\n\n\n\n\n{ base?: string; scope?: string; path?: string; verbose?: boolean; fetchBundleGraph?: boolean; nonce?: string; }\n\n\n\n\n\n
\n**Returns:**\n\n[JSXNode](#jsxnode)<'script'>", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n> Warning: This API is now obsolete.\n> \n> This is no longer needed as the preloading happens automatically in qrl-class.ts. Leave this in your app for a while so it uninstalls existing service workers, but don't use it for new projects.\n> \n\n\n```typescript\nPrefetchServiceWorker: (opts: {\n base?: string;\n scope?: string;\n path?: string;\n verbose?: boolean;\n fetchBundleGraph?: boolean;\n nonce?: string;\n}) => JSXNode<'script'>\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nopts\n\n\n\n\n{ base?: string; scope?: string; path?: string; verbose?: boolean; fetchBundleGraph?: boolean; nonce?: string; }\n\n\n\n\n\n
\n**Returns:**\n\nJSXNode<'script'>", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts", "mdFile": "qwik.prefetchserviceworker.md" }, @@ -3473,4 +3473,4 @@ "mdFile": "qwik.webviewhtmlattributes.md" } ] -} +} \ No newline at end of file diff --git a/packages/docs/src/routes/api/qwik/index.md b/packages/docs/src/routes/api/qwik/index.md index b049939a8c5..ce41430014f 100644 --- a/packages/docs/src/routes/api/qwik/index.md +++ b/packages/docs/src/routes/api/qwik/index.md @@ -3651,7 +3651,7 @@ opts **Returns:** -[JSXNode](#jsxnode)<'script'> +JSXNode<'script'> [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts) diff --git a/packages/docs/src/routes/docs/(qwik)/advanced/modules-prefetching/index.mdx b/packages/docs/src/routes/docs/(qwik)/advanced/modules-prefetching/index.mdx index 5397a75392b..48e649e09e0 100644 --- a/packages/docs/src/routes/docs/(qwik)/advanced/modules-prefetching/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/advanced/modules-prefetching/index.mdx @@ -18,11 +18,7 @@ created_at: '2023-03-20T23:45:13Z' # Prefetching Modules -Qwik provides various strategies to prefetch modules ahead of time. This page describes the **low-level** features of Qwik's prefetching. - -- [Pre-populate the Cache with service workers](../../../(qwikcity)/advanced/speculative-module-fetching/index.mdx) -- [Link rel](#link-rel) -- [Web Worker Fetch](#web-worker-fetch) +Qwik prefetches modules ahead of time. This page describes the **low-level** features of Qwik's prefetching. Prefetching modules allows applications to start downloading necessary code in the background before users actually need it. The ideal solution is to prefetch only the smallest amount of relevant code that is highly likely to be executed from a user's interaction, while also avoiding any JavaScript that _will not_ be used. @@ -38,87 +34,26 @@ For example, consider a product page that is mostly static except for one "Add t For our "Add to cart" example, the optimizer collects the symbols only for the click event listener and the renderer for the add to cart widget. There is no need to download, hydrate, and re-render any other parts of the application that aren't relevant. This demonstrates Qwik's capability to determine which interactions are possible and to prefetch only the necessary code for the event listener. In contrast, traditional approaches require the entire application or route, including framework code, to be prefetched just to add the click event listener. -## Prefetch Strategy - -The prefetching strategy is the logic that decides which JavaScript, if any, Qwik should prefetch in the background. By default, Qwik will prefetch any visible listeners on the page. To configure the prefetching strategy, use the options argument of the `renderToStream()` function, often found in the `src/entry.ssr.tsx` source file. Providing optimal prefetching strategies is a continual commitment of Qwik. - -```ts -export default function (opts: RenderToStreamOptions) { - return renderToStream(, { - manifest, - prefetchStrategy: { - // custom prefetching config - }, - ...opts, - }); -} -``` - -### Implementation - -Browsers offer numerous ways to implement a [prefetching strategy](#prefetchStrategy). Qwik can be configured to prefer one implementation over another, each with pros and cons. Depending on this configuration, the generated HTML content will include the chosen prefetch implementation. - -```ts -export default function (opts: RenderToStreamOptions) { - return renderToStream(, { - manifest, - prefetchStrategy: { - implementation: { - // custom prefetching implementation - }, - }, - ...opts, - }); -} -``` - +## Preloader -| Option | Description | -| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `prefetchEvent` | Dispatch a `qprefetch` event with `detail` data containing the urls that should be prefetched. The event dispatch script will be inlined into the document's HTML. By default, the `prefetchEvent` implementation will be set to `always`. | -| `linkInsert` | Insert the `` element into the document. When using `html-append`, it will render each `` directly within the html, appended at the end of the body. Using the `js-append` option, it will instead insert some JavaScript, which creates the elements at runtime and appends them at the end of the body. | -| `linkRel` | This option is used to define the [`rel` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types) of the `` element. When the `linkInsert` option is used, the default is `prefetch`. Other options include `preload` and `modulepreload`. | -| `linkFetchPriority` | This option is used to define the [`fetchpriority` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#fetchpriority) of the `` element. When the `linkInsert` option is used, the default is `null`. Other options include `low`, `high` and `auto`. -| `workerFetchInsert` | Prefetch urls by calling a `fetch()` for each module, with the goal of populating the network cache. | +The preloader is the part of Qwik that decides which JavaScript, if any, Qwik should prefetch in the background. By default, Qwik will prefetch any event handlers on the page, as well as their dependencies. -#### Dispatched Prefetch Event +This is done probabilistically based on various metrics, including the real-use metrics collected from [Insights](/docs/labs/insights/) -[Speculative Module Fetching](../../../(qwikcity)/advanced/speculative-module-fetching/index.mdx) is the preferred caching strategy. This strategy listens for the `qprefetch` event, which is dispatched by the Qwik framework. The event contains a list of URLs that the background thread should use to pre-populate the browser's [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache). +To give an example, suppose we have two event handlers, one an `onClick` handler and the other a `beforePrint` handler. The click handler is much more likely to be used than the print handler, so Qwik will prefetch the click handler with a higher probability. Furthermore if both handlers are importing the Qwik core bundle, that core bundle is more important than either of the event handlers, and will be prefetched first. -Qwik should be configured to use the `prefetchEvent` implementation, which will dispatch a `qprefetch` event. By default, the `prefetchEvent` implementation will be set to `always`. Next, [Speculative Module Fetching](../../../(qwikcity)/advanced/speculative-module-fetching/index.mdx) will listen for this event and communicate with its service worker to persist the Request / Response object pairs so they are cached in long-lived memory. +During execution, new segments of code are imported and they each impact the probability of the segments to be loaded. -By using a service worker to intercept `fetch` requests from the browser, this approach allows granular control over caching, along with preventing duplicate requests for the same resource. - -Below is an example of manually dispatching the event. These events are dispatched from Qwik itself and do not require developers to dispatch these events manually. Additionally, the [service worker](../../../(qwikcity)/advanced/speculative-module-fetching/index.mdx) will automatically add listeners for these events. - -```ts -dispatchEvent(new CustomEvent("qprefetch", { detail: { - bundles: [...] -}})); -``` - -#### Link `rel` - -Using the `` element with the `rel` attribute is a common approach by today's frameworks, and Qwik can use this method by configuring the `linkInsert` and `linkRel` options. The link rel approach, although effective, currently faces a lack of support on all devices, at least at the time of writing. Additionally, during development, it can be misleading to assume that it works everywhere, since prefetching on mobile devices is not easily visible. - -For example, Safari does not support `modulepreload`. This is significant because mobile devices may benefit the most from module preloading. Similarly Firefox does not support link rel `prefetch` when on `https`. - -> Prefetch is a feature designed to enhance the speed of our visitors' experiences. However, the effectiveness can vary depending on the combination of browser and CDN/server used, highlighting the importance of an optimized setup to ensure the best performance. -> -> \- Rel=prefetch and the Importance of Effective HTTP/2 Prioritisation - -Additionally, it's possible for multiple requests for the same resource. For example, let's say we want to prefetch `module-a.js`, and while it's downloading regardless of how long it takes, the user interacts with the app. The app then decides to actually request and execute `module-a.js`. At the time of this writing, browsers will often fire off a second request making matters worse. +### Implementation -##### link rel="modulepreload" +The prefetching is done by adding `` elements to the document. SSR will determine the most likely bundles and add some `` elements to the HTML. -- Even though it's in the HTML spec, that doesn't mean your end-users are preloading your app correctly. [Can I Use: modulepreload](https://caniuse.com/link-rel-modulepreload) -- Not supported by [Firefox](https://bugzilla.mozilla.org/show_bug.cgi?id=1425310). +Then, a script is addded that loads the preloader as well as the module graph, and it is passed all the bundles discovered during the SSR render. -#### Web Worker Fetch +The preloader will then add the remaining `` elements to the document head, keeping only a few active at a time so that high priority bundles can be added at any time. -`workerFetchInsert` instructs Qwik to employ a web worker to `fetch()` a JavaScript file, with the goal of priming the browser cache with the module. By using a web worker, the fetch and caching logic lives on another thread. The fetch response will also have an `immutable` or long cache-control header, so the browser doesn't make a second network request. +Most browsers support `rel="modulepreload"`, but for older browsers, Qwik will fall back to `rel="preload"` attribute. -The downside of this setting is that the fetched response is thrown away, and it's only at the browser level that hopefully the file is cached. ## Frequently Asked Prefetching Questions @@ -141,4 +76,4 @@ No, for several reasons: **QUESTION**: _Who is responsible for knowing what code to prefetch?_ -Qwik can automatically generate the prefetch instructions as part of the SSR rendering. By executing the application, Qwik has runtime knowledge of which components are visible, which events the users can trigger and what code will need to be downloaded. The result is that the prefetch is an ideal set of files for this page. No action on the developers' part is required other than adding the prefetching strategy to `renderToStream()`. +Qwik automatically generates the prefetch instructions as part of the SSR rendering. By executing the application, Qwik has runtime knowledge of which components are visible, which events the users can trigger and what code will need to be downloaded. The result is that the prefetch is an ideal set of files for this page. No action on the developers' part is required. diff --git a/packages/docs/src/routes/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx index 09b00a0451a..d55f9fb3999 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/advanced/speculative-module-fetching/index.mdx @@ -62,7 +62,7 @@ However, Qwik knows the module graph and controls QRL segment loading, we do kno The Qwik build process generates a `q-manifest.json` file. The `q-manifest.json` includes a detailed module graph of how bundles are associated and which symbols are within each bundle. -This is then further used to generate the `build/q-bundle-graph-xyz.json` file, which is a compact representation of the module graph, and is loaded by Qwik to preload QRL segment loading. +This is then further used to generate the `build/q-bundle-graph-xyz.js` file, which is a compact representation of the module graph, including probabilities of which bundlers are more likely to be requested next given a certain bundle has loaded, and is loaded by Qwik to preload QRL segment loading. Qwik-City also injects all routes with their dependencies into this bundlegraph file. @@ -70,9 +70,9 @@ When a container resumes, it will fetch the bundlegraph file (probably from the Static imports are preloaded with high priority, and dynamic imports are preloaded with low priority. The browser will decide when to download the modules. -The preloading happens with the `_preload` function, which is an internal Qwik API. It works by traversing the bundlegraph and adding `` tags for each module. +The preloading happens with the `_preload` function, which is an internal Qwik API. It works by traversing the bundlegraph and adding `` tags for each bundle that is likely to be requested next. -## Disabled During Development +## During Development -Speculative module fetching only kicks in preview or on a production build. In development, the service worker is disabled which also disables speculative module fetching. This is because during development we want to always ensure the latest development code is being used, rather than what's been previously cached. +Speculative module fetching only really works in preview or on a production build. In development, we don't have the bundle graph available, so we can't prevent waterfalls from happening. However, we do ask the browser to preload the bundles that are most likely to be requested after SSR. diff --git a/packages/docs/src/routes/docs/deployments/cloudflare-pages/index.mdx b/packages/docs/src/routes/docs/deployments/cloudflare-pages/index.mdx index 8fa40decc68..72ede71a878 100644 --- a/packages/docs/src/routes/docs/deployments/cloudflare-pages/index.mdx +++ b/packages/docs/src/routes/docs/deployments/cloudflare-pages/index.mdx @@ -205,10 +205,9 @@ import { type PlatformCloudflarePages, } from '@builder.io/qwik-city/middleware/cloudflare-pages'; import qwikCityPlan from '@qwik-city-plan'; -import { manifest } from '@qwik-client-manifest'; import render from './entry.ssr'; -const fetch = createQwikCity({ render, qwikCityPlan, manifest }); +const fetch = createQwikCity({ render, qwikCityPlan }); export { fetch }; ``` diff --git a/packages/docs/vite.config.mts b/packages/docs/vite.config.mts index 4d7f3cc2073..e0d7cbfd1ac 100644 --- a/packages/docs/vite.config.mts +++ b/packages/docs/vite.config.mts @@ -173,7 +173,7 @@ export default defineConfig(async () => { ], }, }), - qwikVite(), + qwikVite({ debug: false }), partytownVite({ dest: resolve('dist', '~partytown'), }), diff --git a/packages/insights/src/entry.ssr.tsx b/packages/insights/src/entry.ssr.tsx index 51151b935aa..99e531d3dd4 100644 --- a/packages/insights/src/entry.ssr.tsx +++ b/packages/insights/src/entry.ssr.tsx @@ -10,12 +10,10 @@ * - `npm run build` */ import { renderToStream, type RenderToStreamOptions } from '@builder.io/qwik/server'; -import { manifest } from '@qwik-client-manifest'; import Root from './root'; export default function (opts: RenderToStreamOptions) { return renderToStream(, { - manifest, ...opts, // Use container attributes to set attributes on the html tag. containerAttributes: { diff --git a/packages/qwik-city/src/buildtime/vite/dev-server.ts b/packages/qwik-city/src/buildtime/vite/dev-server.ts index eeada990f5c..310d60ede56 100644 --- a/packages/qwik-city/src/buildtime/vite/dev-server.ts +++ b/packages/qwik-city/src/buildtime/vite/dev-server.ts @@ -1,4 +1,4 @@ -import type { QwikManifest, QwikViteDevResponse } from '@builder.io/qwik/optimizer'; +import type { QwikViteDevResponse } from '@builder.io/qwik/optimizer'; import fs from 'node:fs'; import type { ServerResponse } from 'node:http'; import { join, resolve } from 'node:path'; @@ -10,8 +10,8 @@ import { } from '../../middleware/request-handler/resolve-request-handlers'; import { getQwikCityServerData } from '../../middleware/request-handler/response-page'; import { - getRouteMatchPathname, QDATA_JSON, + getRouteMatchPathname, runQwikCity, } from '../../middleware/request-handler/user-response'; import { matchRoute } from '../../runtime/src/route-matcher'; @@ -223,15 +223,6 @@ export function ssrDevMiddleware(ctx: BuildContext, server: ViteDevServer) { const serverRequestEv = await fromNodeHttp(url, req, res, 'dev'); Object.assign(serverRequestEv.platform, ctx.opts.platform); - const manifest: QwikManifest = { - manifestHash: '', - symbols: {}, - mapping: {}, - bundles: {}, - injections: [], - version: '1', - }; - const { _deserializeData, _serializeData, _verifySerializable } = await server.ssrLoadModule('@qwik-serializer'); const qwikSerializer = { _deserializeData, _serializeData, _verifySerializable }; @@ -240,7 +231,6 @@ export function ssrDevMiddleware(ctx: BuildContext, server: ViteDevServer) { serverRequestEv, loadedRoute, requestHandlers, - manifest, ctx.opts.trailingSlash, ctx.opts.basePathname, qwikSerializer diff --git a/packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts b/packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts index b1634eaa8cf..4f87f94ded9 100644 --- a/packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts +++ b/packages/qwik-city/src/buildtime/vite/get-route-imports.unit.ts @@ -7,32 +7,29 @@ describe('modifyBundleGraph', () => { test(`GIVEN 2 routes, one with a layout AND a manifest with 3 bundles THEN the bundle graph should contain the routes and their dependencies`, () => { + const size = 0; + const total = 0; const fakeManifest = { bundles: { 'fake-bundle1.js': { - size: 0, + size, + total, imports: ['fake-bundle-static-dep.js'], origins: ['src/routes/index.tsx'], }, 'fake-bundle-static-dep.js': { - size: 0, + size, + total, dynamicImports: ['fake-bundle-dynamic-dep.js'], }, - 'fake-bundle-dynamic-dep.js': { - size: 0, - }, + 'fake-bundle-dynamic-dep.js': { size, total }, 'fake-bundle-part-of-sub-route.js': { - size: 0, + size, + total, origins: ['src/routes/subroute/index.tsx', 'src/some/other/component.tsx'], }, - 'fake-bundle-part-of-layout.js': { - size: 0, - origins: ['src/routes/layout.tsx'], - }, - 'q-city-plan.js': { - size: 0, - origins: ['@qwik-city-plan'], - }, + 'fake-bundle-part-of-layout.js': { size, total, origins: ['src/routes/layout.tsx'] }, + 'q-city-plan.js': { size, total, origins: ['@qwik-city-plan'] }, } as Record, } as QwikManifest; @@ -76,17 +73,13 @@ describe('modifyBundleGraph', () => { test(`GIVEN a mismatch between the bundle graph and the manifest THEN the resulted bundle graph routes should not contain -1 (not found) indices `, () => { + const size = 0; + const total = 0; const fakeManifest = { bundles: { - 'fake-bundle1.js': { - size: 0, - origins: ['src/routes/index.tsx'], - }, + 'fake-bundle1.js': { size, total, origins: ['src/routes/index.tsx'] }, // 👇 doesn't exist in the bundle graph for some reason - 'fake-bundle2.js': { - size: 0, - origins: ['src/routes/index.tsx'], - }, + 'fake-bundle2.js': { size, total, origins: ['src/routes/index.tsx'] }, } as Record, } as QwikManifest; diff --git a/packages/qwik-city/src/buildtime/vite/image-jsx.ts b/packages/qwik-city/src/buildtime/vite/image-jsx.ts index 9b708c22f18..cf2084b69d3 100644 --- a/packages/qwik-city/src/buildtime/vite/image-jsx.ts +++ b/packages/qwik-city/src/buildtime/vite/image-jsx.ts @@ -3,7 +3,7 @@ import type { PluginOption } from 'vite'; import { optimize } from 'svgo'; import fs from 'node:fs'; import path from 'node:path'; -import { parseId } from '../../../../qwik/src/optimizer/src/plugins/plugin'; +import { parseId } from '../../../../qwik/src/optimizer/src/plugins/vite-utils'; import type { QwikCityVitePluginOptions } from './types'; import type { Config as SVGOConfig } from 'svgo'; diff --git a/packages/qwik-city/src/buildtime/vite/plugin.ts b/packages/qwik-city/src/buildtime/vite/plugin.ts index 2312a0ffa19..880e5d22a9c 100644 --- a/packages/qwik-city/src/buildtime/vite/plugin.ts +++ b/packages/qwik-city/src/buildtime/vite/plugin.ts @@ -21,8 +21,8 @@ import { generateQwikCityEntries } from '../runtime-generation/generate-entries' import { generateQwikCityPlan } from '../runtime-generation/generate-qwik-city-plan'; import { generateServiceWorkerRegister } from '../runtime-generation/generate-service-worker'; import type { BuildContext } from '../types'; -import { getRouteImports } from './get-route-imports'; import { ssrDevMiddleware, staticDistMiddleware } from './dev-server'; +import { getRouteImports } from './get-route-imports'; import { imagePlugin } from './image-jsx'; import type { QwikCityPluginApi, QwikCityVitePluginOptions } from './types'; import { validatePlugin } from './validate-plugin'; diff --git a/packages/qwik-city/src/middleware/aws-lambda/index.ts b/packages/qwik-city/src/middleware/aws-lambda/index.ts index a2a2e138561..1ac392d0658 100644 --- a/packages/qwik-city/src/middleware/aws-lambda/index.ts +++ b/packages/qwik-city/src/middleware/aws-lambda/index.ts @@ -5,7 +5,7 @@ import type { QwikManifest, Render } from 'packages/qwik/src/server/types'; interface AwsOpt { render: Render; - manifest: QwikManifest; + manifest?: QwikManifest; qwikCityPlan: QwikCityPlan; } diff --git a/packages/qwik-city/src/middleware/request-handler/request-event.ts b/packages/qwik-city/src/middleware/request-handler/request-event.ts index 419aac7d241..d4ade181aae 100644 --- a/packages/qwik-city/src/middleware/request-handler/request-event.ts +++ b/packages/qwik-city/src/middleware/request-handler/request-event.ts @@ -1,32 +1,31 @@ -import type { - RequestEvent, - RequestEventLoader, - ServerRequestEvent, - ServerRequestMode, - RequestHandler, - RequestEventCommon, - ResolveValue, - QwikSerializer, - CacheControlTarget, - CacheControl, -} from './types'; +import type { ValueOrPromise } from '@builder.io/qwik'; +import { QDATA_KEY } from '../../runtime/src/constants'; import type { ActionInternal, + FailReturn, JSONValue, LoadedRoute, LoaderInternal, - FailReturn, } from '../../runtime/src/types'; +import { isPromise } from './../../runtime/src/utils'; +import { createCacheControl } from './cache-control'; import { Cookie } from './cookie'; +import { ServerError } from './error-handler'; import { AbortMessage, RedirectMessage } from './redirect-handler'; import { encoder } from './resolve-request-handlers'; -import { createCacheControl } from './cache-control'; -import type { ValueOrPromise } from '@builder.io/qwik'; -import type { QwikManifest, ResolvedManifest } from '@builder.io/qwik/optimizer'; +import type { + CacheControl, + CacheControlTarget, + QwikSerializer, + RequestEvent, + RequestEventCommon, + RequestEventLoader, + RequestHandler, + ResolveValue, + ServerRequestEvent, + ServerRequestMode, +} from './types'; import { IsQData, QDATA_JSON, QDATA_JSON_LEN } from './user-response'; -import { isPromise } from './../../runtime/src/utils'; -import { QDATA_KEY } from '../../runtime/src/constants'; -import { ServerError } from './error-handler'; const RequestEvLoaders = Symbol('RequestEvLoaders'); const RequestEvMode = Symbol('RequestEvMode'); @@ -42,7 +41,6 @@ export function createRequestEvent( serverRequestEv: ServerRequestEvent, loadedRoute: LoadedRoute | null, requestHandlers: RequestHandler[], - manifest: QwikManifest | ResolvedManifest | undefined, trailingSlash: boolean, basePathname: string, qwikSerializer: QwikSerializer, @@ -61,7 +59,6 @@ export function createRequestEvent( } sharedMap.set(IsQData, true); } - sharedMap.set('@manifest', manifest); let routeModuleIndex = -1; let writableStream: WritableStream | null = null; diff --git a/packages/qwik-city/src/middleware/request-handler/request-handler.ts b/packages/qwik-city/src/middleware/request-handler/request-handler.ts index b8626beb6a4..02ab430732d 100644 --- a/packages/qwik-city/src/middleware/request-handler/request-handler.ts +++ b/packages/qwik-city/src/middleware/request-handler/request-handler.ts @@ -15,7 +15,7 @@ export async function requestHandler( opts: ServerRenderOptions, qwikSerializer: QwikSerializer ): Promise | null> { - const { render, qwikCityPlan, manifest, checkOrigin } = opts; + const { render, qwikCityPlan, checkOrigin } = opts; const pathname = serverRequestEv.url.pathname; const matchPathname = getRouteMatchPathname(pathname, qwikCityPlan.trailingSlash); const routeAndHandlers = await loadRequestHandlers( @@ -31,7 +31,6 @@ export async function requestHandler( serverRequestEv, route, requestHandlers, - manifest, qwikCityPlan.trailingSlash, qwikCityPlan.basePathname, qwikSerializer diff --git a/packages/qwik-city/src/middleware/request-handler/user-response.ts b/packages/qwik-city/src/middleware/request-handler/user-response.ts index ce6f5b3eecf..6f240dbc157 100644 --- a/packages/qwik-city/src/middleware/request-handler/user-response.ts +++ b/packages/qwik-city/src/middleware/request-handler/user-response.ts @@ -1,16 +1,15 @@ -import type { QwikSerializer, ServerRequestEvent, StatusCodes } from './types'; import type { RequestEvent, RequestHandler } from '@builder.io/qwik-city'; +import type { LoadedRoute } from '../../runtime/src/types'; +import { ServerError, getErrorHtml, minimalHtmlResponse } from './error-handler'; +import { AbortMessage, RedirectMessage } from './redirect-handler'; import { RequestEvQwikSerializer, createRequestEvent, getRequestMode, type RequestEventInternal, } from './request-event'; -import { ServerError, getErrorHtml, minimalHtmlResponse } from './error-handler'; -import { AbortMessage, RedirectMessage } from './redirect-handler'; -import type { LoadedRoute } from '../../runtime/src/types'; import { encoder } from './resolve-request-handlers'; -import type { QwikManifest, ResolvedManifest } from '@builder.io/qwik/optimizer'; +import type { QwikSerializer, ServerRequestEvent, StatusCodes } from './types'; export interface QwikCityRun { response: Promise; @@ -36,7 +35,6 @@ export function runQwikCity( serverRequestEv: ServerRequestEvent, loadedRoute: LoadedRoute | null, requestHandlers: RequestHandler[], - manifest: QwikManifest | ResolvedManifest | undefined, trailingSlash = true, basePathname = '/', qwikSerializer: QwikSerializer @@ -47,7 +45,6 @@ export function runQwikCity( serverRequestEv, loadedRoute, requestHandlers, - manifest, trailingSlash, basePathname, qwikSerializer, diff --git a/packages/qwik-labs/src/entry.ssr.tsx b/packages/qwik-labs/src/entry.ssr.tsx index 55242552333..581a221460f 100644 --- a/packages/qwik-labs/src/entry.ssr.tsx +++ b/packages/qwik-labs/src/entry.ssr.tsx @@ -10,12 +10,8 @@ * - `npm run build` */ import { renderToStream, type RenderToStreamOptions } from '@builder.io/qwik/server'; -import { manifest } from '@qwik-client-manifest'; import Root from './root'; export default function (opts: RenderToStreamOptions) { - return renderToStream(, { - manifest, - ...opts, - }); + return renderToStream(, opts); } diff --git a/packages/qwik-react/src/entry.ssr.tsx b/packages/qwik-react/src/entry.ssr.tsx index 7480cf54e7f..78924474111 100644 --- a/packages/qwik-react/src/entry.ssr.tsx +++ b/packages/qwik-react/src/entry.ssr.tsx @@ -1,13 +1,9 @@ import { renderToStream, type RenderToStreamOptions } from '@builder.io/qwik/server'; -import { manifest } from '@qwik-client-manifest'; import { Root } from './root'; /** Server-Side Render method to be called by a server. */ export default function (opts: RenderToStreamOptions) { // Render the Root component to a string // Pass in the manifest that was generated from the client build - return renderToStream(, { - manifest, - ...opts, - }); + return renderToStream(, opts); } diff --git a/packages/qwik/src/core/preloader.ts b/packages/qwik/src/core/preloader.ts deleted file mode 100644 index 53ba3e08a64..00000000000 --- a/packages/qwik/src/core/preloader.ts +++ /dev/null @@ -1,317 +0,0 @@ -/* eslint-disable no-console */ -/** - * Note: this file gets built separately from the rest of the core module, and is then kept separate - * in the dist directory via manualChunks. This way it can run before the rest of the core module is - * loaded, but core can still use it. - * - * Here we handle preloading of bundles. - * - * Given a symbol hash (in fact any string), we can find all the bundles that it depends on, via the - * bundle graph. We then generate preload link tags for each of those bundles. - * - * There are several parts to this: - * - * - Load the bundle graph from the preload link tag that was injected during SSR - * - Given a string, find all the bundles that it depends on - * - Generate the preload link tags if needed - * - * In practice, we queue incoming requests and when we process - */ -/** - * Todo - * - * - High and low priority sets - * - Num preloads active at a time - * - If no modulepreload support, do 1 fetch at a time - */ - -import { isBrowser } from '@builder.io/qwik/build'; -import type { QwikBundleGraph } from '../optimizer/src/types'; - -const DEBUG = true; - -const enum BundleImportState { - None, - QueuedLow, - QueuedHigh, - /** Preload link was made */ - Loading, - LoadingHigh, - /** All imports are >=Loading */ - Loaded, -} -type BundleImport = { - $name$: string; - $url$: string | null; - $state$: BundleImportState; - $imports$: string[]; - $dynamicImports$: string[]; - $priority$: boolean; - $created$: number; - $waited$: number; - $loaded$: number; - $didLoadHigh$: boolean; -}; -const bundles = new Map(); -let gotBundleGraph = false; -const high: BundleImport[] = []; -const low: BundleImport[] = []; - -let highCount = 0; -let lowCount = 0; -const loadStart = Date.now(); - -const log = (...args: any[]) => { - console.log( - `PL ${Date.now() - loadStart}> hi ${highCount}/${high.length} lo ${lowCount}/${low.length}`, - ...args - ); -}; -let base: string | undefined; - -// minification helpers -const doc = isBrowser ? document : undefined!; -const modulePreloadStr = 'modulepreload'; -const preloadStr = 'preload'; - -const checkLoaded = (bundle: BundleImport) => { - if (bundle.$state$ === BundleImportState.Loaded) { - return true; - } - if ( - bundle.$state$ === BundleImportState.Loading && - bundle.$imports$.every((dep) => bundles.get(dep)!.$state$ >= BundleImportState.Loading) - ) { - bundle.$state$ = BundleImportState.Loaded; - return true; - } -}; - -/** - * This is called when a bundle is queued or finished loading. - * - * Because Chrome doesn't treat new modulepreloads as higher priority, we only make 5 links - * available at a time, so that when a new high priority bundle comes in, it is soon preloaded. - * - * We make sure to first empty the high priority items, first-in-last-out. - */ -const trigger = () => { - // high is confirmed needed so we go as wide as possible - while (high.length) { - const bundle = high.pop()!; - preloadOne(bundle!, true); - } - /** - * The low priority bundles are opportunistic, and we want to give the browser some breathing room - * for other resources, so we cycle between 4 and 10 outstanding modulepreloads. - */ - if (highCount + lowCount < 5) { - while (highCount + lowCount < 10 && low.length) { - const bundle = low.pop()!; - preloadOne(bundle!); - } - } - if (DEBUG && !high.length && !low.length) { - const loaded = [...bundles.values()].filter((b) => b.$state$ >= BundleImportState.Loading); - const waitTime = loaded.reduce((acc, b) => acc + b.$waited$, 0); - const loadTime = loaded.reduce((acc, b) => acc + b.$loaded$, 0); - log(`done ${loaded.length} total: ${waitTime}ms waited, ${loadTime}ms loaded`); - } -}; - -const rel = - isBrowser && doc.createElement('link').relList.supports(modulePreloadStr) - ? modulePreloadStr - : preloadStr; -/** - * Note, we considered using `preload` for low priority bundles, but those don't get preparsed and - * that slows down interaction - */ -const preloadOne = (bundle: BundleImport, priority?: boolean) => { - if (checkLoaded(bundle)) { - return; - } - if ((priority && !bundle.$didLoadHigh$) || bundle.$state$ < BundleImportState.Loading) { - const start = Date.now(); - bundle.$waited$ = start - bundle.$created$; - bundle.$state$ = priority ? BundleImportState.LoadingHigh : BundleImportState.Loading; - bundle.$didLoadHigh$ = priority!; - if (bundle.$url$) { - DEBUG && - log(`load ${priority ? 'high' : 'low'} after ${`${bundle.$waited$}ms`}`, bundle.$name$); - const link = doc.createElement('link'); - link.href = bundle.$url$!; - link.rel = priority ? rel : 'preload'; - if (priority) { - link.rel = rel; - highCount++; - } else { - link.rel = 'preload'; - link.as = 'script'; - link.fetchPriority = 'low'; - lowCount++; - } - link.as = 'script'; - link.onload = link.onerror = () => { - const end = Date.now(); - bundle.$loaded$ = end - start; - DEBUG && log(`DONE ${bundle.$priority$ ? 'high' : 'low'} ${end - start}ms`, bundle.$name$); - link.remove(); - if (priority) { - highCount--; - } else { - lowCount--; - } - preload(bundle.$dynamicImports$); - trigger(); - }; - - doc.head.appendChild(link); - } - } - - // (re)queue dependencies - preload(bundle.$imports$, priority); - if (priority) { - preload(bundle.$dynamicImports$); - } -}; - -const makeBundle = (path: string, imports: string[], dynamicImports: string[]) => { - const url = path.endsWith('.js') ? new URL(`${base}${path}`, doc.baseURI).toString() : null; - return { - $name$: path, - $url$: url, - $state$: BundleImportState.None, - $imports$: imports, - $dynamicImports$: dynamicImports, - $priority$: false, - $created$: Date.now(), - $waited$: 0, - $loaded$: 0, - $didLoadHigh$: false, - }; -}; - -const parseBundleGraph = (text: string) => { - DEBUG && log(`parseBundleGraph ${text.length >> 10}kB`); - const graph = JSON.parse(text) as QwikBundleGraph; - let i = 0; - // All existing loading bundles need imports processed - const toProcess = [...bundles.values()] - .filter((bundle) => { - return bundle.$state$ >= BundleImportState.Loading; - }) - .reverse(); - while (i < graph.length) { - const name = graph[i++] as string; - const imports: string[] = []; - const dynamicImports: string[] = []; - let idx: number | string; - let collection = imports; - while (((idx = graph[i]), typeof idx === 'number')) { - if (idx === -1) { - collection = dynamicImports; - } else { - collection.push(graph[idx] as string); - } - i++; - } - if (bundles.has(name)) { - const bundle = bundles.get(name)!; - bundle.$imports$ = imports; - bundle.$dynamicImports$ = dynamicImports; - if (bundle.$state$ === BundleImportState.Loaded) { - bundle.$state$ = BundleImportState.Loading; - } - } else { - bundles.set(name, makeBundle(name, imports, dynamicImports)); - } - } - DEBUG && - log(`parseBundleGraph done ${bundles.size} bundles, will process ${toProcess.length} bundles`); - gotBundleGraph = true; - for (const bundle of toProcess) { - preload(bundle.$imports$, true); - preload(bundle.$dynamicImports$); - } -}; - -const handleBundle = (name: string, collection: BundleImport[], priority: boolean) => { - let bundle = bundles.get(name); - if (!bundle) { - if (gotBundleGraph) { - return; - } - bundle = makeBundle(name, [], []); - bundles.set(name, bundle); - } - if (checkLoaded(bundle)) { - return; - } - if (bundle.$state$ < BundleImportState.QueuedHigh) { - if (priority) { - bundle.$priority$ = true; - bundle.$state$ = BundleImportState.QueuedHigh; - collection.push(bundle); - } else { - bundle.$state$ = BundleImportState.QueuedLow; - collection.unshift(bundle); - } - return true; - } -}; - -let allOk = true; -/** - * Preload a bundle or bundles. Requires calling loadBundleGraph first. - * - * @internal - */ -const preload = (name: string | string[], priority?: boolean) => { - if (!isBrowser || !base || !name.length) { - return; - } - if (!allOk) { - return; - } - const queue = priority ? high : low; - let didQueue = false; - if (Array.isArray(name)) { - // We must process in reverse order to ensure first bundles are handled first - for (let i = name.length - 1; i >= 0; i--) { - didQueue = handleBundle(name[i], queue, priority!) || didQueue; - } - } else { - didQueue = handleBundle(name, queue, priority!)!; - } - if (didQueue) { - DEBUG && log(`queue ${priority ? 'high' : 'low'}`, name); - trigger(); - if (low.length > 5000) { - // just a precaution, should never happen - allOk = false; - } - } -}; - -/** - * Lazily load the bundle graph and then import dependencies of bundles that were loaded already. - * - * @internal - */ -const loadBundleGraph = (basePath: string, manifestHash: string) => { - if (!isBrowser || base) { - return; - } - base = basePath; - // TODO check TTI, maybe inject fetch link with timeout so we don't do the fetch directly - fetch(`${basePath}q-bundle-graph-${manifestHash}.json`) - .then((res) => res.text()) - .then((text) => parseBundleGraph(text)) - // We warn because it's not critical, and in the CI tests Windows serves up a HTML file instead the bundle graph sometimes, which breaks the tests that don't expect error logs - .catch(console.warn); -}; - -// Short names for minification -export { loadBundleGraph as l, preload as p }; diff --git a/packages/qwik/src/core/preloader/bundle-graph.ts b/packages/qwik/src/core/preloader/bundle-graph.ts new file mode 100644 index 00000000000..facab9a9f09 --- /dev/null +++ b/packages/qwik/src/core/preloader/bundle-graph.ts @@ -0,0 +1,149 @@ +import { isBrowser } from '@builder.io/qwik/build'; +import { + config, + doc, + maxSignificantInverseProbabilityStr, + maxSimultaneousPreloadsStr, +} from './constants'; +import { adjustProbabilities, bundles, log, trigger } from './queue'; +import type { BundleGraph, BundleImport, ImportProbability } from './types'; +import { BundleImportState } from './types'; + +export let base: string | undefined; +export let graph: BundleGraph; + +const makeBundle = (name: string, deps?: ImportProbability[]) => { + const url = name.endsWith('.js') + ? doc + ? new URL(`${base}${name}`, doc.baseURI).toString() + : name + : null; + return { + $name$: name, + $url$: url, + $state$: url ? BundleImportState.None : BundleImportState.Alias, + $deps$: deps, + $inverseProbability$: 1, + $createdTs$: Date.now(), + $waitedMs$: 0, + $loadedMs$: 0, + }; +}; + +export const parseBundleGraph = (serialized: (string | number)[]) => { + const graph: BundleGraph = new Map(); + let i = 0; + while (i < serialized.length) { + const name = serialized[i++] as string; + const deps: ImportProbability[] = []; + let idx: number | string; + let probability = 1; + while (((idx = serialized[i]), typeof idx === 'number')) { + if (idx < 0) { + probability = -idx / 10; + } else { + deps.push({ $name$: serialized[idx] as string, $probability$: probability, $factor$: 1 }); + } + i++; + } + graph.set(name, deps); + } + return graph; +}; + +export const getBundle = (name: string) => { + let bundle = bundles.get(name); + if (!bundle) { + let deps: ImportProbability[] | undefined; + if (graph) { + deps = graph.get(name); + if (!deps) { + return; + } + if (!deps.length) { + deps = undefined; + } + } + bundle = makeBundle(name, deps); + bundles.set(name, bundle); + } + return bundle; +}; + +/** Used in browser */ +export const loadBundleGraph = ( + basePath: string, + manifestHash: string, + opts?: { + /** Enable logging */ + debug?: boolean; + /** Maximum number of simultaneous preload links */ + P?: number; + /** Minimum probability for a bundle to be added to the preload queue */ + Q?: number; + } +) => { + if (opts) { + if ('d' in opts) { + config.DEBUG = !!opts.d; + } + if ('P' in opts) { + config[maxSimultaneousPreloadsStr] = opts['P'] as number; + } + if ('Q' in opts) { + config[maxSignificantInverseProbabilityStr] = 1 - (opts['Q'] as number); + } + } + if (!isBrowser || basePath == null) { + return; + } + base = basePath; + + if (manifestHash) { + import(/* @vite-ignore */ `${basePath}q-bundle-graph-${manifestHash}.js`) + .then((m) => { + graph = parseBundleGraph(m.B); + const toAdjust: [BundleImport, number][] = []; + for (const [name, deps] of graph.entries()) { + const bundle = getBundle(name)!; + bundle.$deps$ = deps; + if (bundle.$inverseProbability$ < 1) { + toAdjust.push([bundle, bundle.$inverseProbability$]); + bundle.$inverseProbability$ = 1; + } + } + config.DEBUG && + log(`parseBundleGraph got ${graph.size} bundles, adjusting ${toAdjust.length}`); + for (const [bundle, inverseProbability] of toAdjust) { + adjustProbabilities(bundle, inverseProbability); + } + trigger(); + }) + .catch(console.warn); + } +}; + +/** Used during SSR */ +export const initPreloader = ( + serializedBundleGraph?: (string | number)[], + opts?: { + debug?: boolean; + maxSignificantInverseProbability?: number; + } +) => { + if (opts) { + if ('debug' in opts) { + config.DEBUG = !!opts.debug; + } + if (maxSignificantInverseProbabilityStr in opts) { + config[maxSignificantInverseProbabilityStr] = opts[ + maxSignificantInverseProbabilityStr + ] as number; + } + } + if (base != null || !serializedBundleGraph) { + return; + } + base = ''; + graph = parseBundleGraph(serializedBundleGraph); +}; diff --git a/packages/qwik/src/core/preloader/constants.ts b/packages/qwik/src/core/preloader/constants.ts new file mode 100644 index 00000000000..76af6c17164 --- /dev/null +++ b/packages/qwik/src/core/preloader/constants.ts @@ -0,0 +1,23 @@ +import { isBrowser } from '@builder.io/qwik/build'; + +// Browser-specific setup +export const doc = isBrowser ? document : undefined!; +export const modulePreloadStr = 'modulepreload'; +export const preloadStr = 'preload'; +export const maxSimultaneousPreloadsStr = 'maxSimultaneousPreloads'; +export const maxSignificantInverseProbabilityStr = 'maxSignificantInverseProbability'; + +export const config = { + DEBUG: false, + [maxSimultaneousPreloadsStr]: 6, + [maxSignificantInverseProbabilityStr]: 0.75, +}; + +// Determine which rel attribute to use based on browser support +export const rel = + isBrowser && doc.createElement('link').relList.supports(modulePreloadStr) + ? modulePreloadStr + : preloadStr; + +// Global state +export const loadStart = Date.now(); diff --git a/packages/qwik/src/core/preloader/index.ts b/packages/qwik/src/core/preloader/index.ts new file mode 100644 index 00000000000..77a4eb55455 --- /dev/null +++ b/packages/qwik/src/core/preloader/index.ts @@ -0,0 +1,12 @@ +/* eslint-disable no-console */ +/** + * Note: this file gets built separately from the rest of the core module, and is then kept separate + * in the dist directory via manualChunks. This way it can run before the rest of the core module is + * loaded, but core can still use it. + * + * Here we handle preloading of bundles. See @link{./preloading.md} for more details. + */ + +// Short names for minification +export { loadBundleGraph as l, parseBundleGraph as g } from './bundle-graph'; +export { preload as p, handleBundle as h } from './queue'; diff --git a/packages/qwik/src/core/preloader/queue.ts b/packages/qwik/src/core/preloader/queue.ts new file mode 100644 index 00000000000..c52b6c5c76e --- /dev/null +++ b/packages/qwik/src/core/preloader/queue.ts @@ -0,0 +1,222 @@ +import { isBrowser } from '@builder.io/qwik/build'; +import { base, getBundle, graph } from './bundle-graph'; +import { + config, + doc, + loadStart, + maxSimultaneousPreloadsStr, + maxSignificantInverseProbabilityStr, + rel, +} from './constants'; +import type { BundleImport, BundleImports } from './types'; +import { BundleImportState } from './types'; + +export const bundles: BundleImports = new Map(); +let queueDirty: boolean; +let preloadCount = 0; +const queue: BundleImport[] = []; + +export const log = (...args: any[]) => { + // eslint-disable-next-line no-console + console.log( + `Preloader ${Date.now() - loadStart}ms ${preloadCount}/${queue.length} queued>`, + ...args + ); +}; + +export const resetQueue = () => { + bundles.clear(); + queueDirty = false; + preloadCount = 0; + queue.length = 0; +}; +export const sortQueue = () => { + if (queueDirty) { + queue.sort((a, b) => a.$inverseProbability$ - b.$inverseProbability$); + queueDirty = false; + } +}; +/** + * This returns `[probability, url1, url2, probability, url3, ...]` in the way `preload()` expects. + * + * `probability` is a number between 0 and 10. + * + * The client will use this array to reconstruct the queue. + */ +export const getQueue = () => { + sortQueue(); + let probability = 0.4; + const result: (string | number)[] = []; + for (const b of queue) { + const nextProbability = Math.round((1 - b.$inverseProbability$) * 10); + if (nextProbability !== probability) { + probability = nextProbability; + result.push(probability); + } + result.push(b.$name$); + } + return result; +}; + +/** + * This is called when a bundle is queued, or finished loading. + * + * Because Chrome doesn't treat new modulepreloads as higher priority, we only make + * maxSimultaneousPreloads links available at a time, so that when a new high priority bundle comes + * in, it is soon preloaded. + * + * We make sure to first preload the high priority items. + */ +export const trigger = () => { + if (!queue.length) { + return; + } + sortQueue(); + while (queue.length) { + const bundle = queue[0]; + const inverseProbability = bundle.$inverseProbability$; + const probability = 1 - inverseProbability; + const allowedPreloads = graph + ? // The more likely the bundle, the more simultaneous preloads we want to allow + Math.max(1, config[maxSimultaneousPreloadsStr] * probability) + : // While the graph is not available, we limit to 2 preloads + 2; + if (preloadCount < allowedPreloads) { + queue.shift(); + preloadOne(bundle); + } else { + break; + } + } + /** + * The low priority bundles are opportunistic, and we want to give the browser some breathing room + * for other resources, so we cycle between 4 and 10 outstanding modulepreloads. + */ + if (config.DEBUG && !queue.length) { + const loaded = [...bundles.values()].filter((b) => b.$state$ > BundleImportState.None); + const waitTime = loaded.reduce((acc, b) => acc + b.$waitedMs$, 0); + const loadTime = loaded.reduce((acc, b) => acc + b.$loadedMs$, 0); + log( + `>>>> done ${loaded.length}/${bundles.size} total: ${waitTime}ms waited, ${loadTime}ms loaded` + ); + } +}; + +const preloadOne = (bundle: BundleImport) => { + if (bundle.$state$ >= BundleImportState.Preload) { + return; + } + preloadCount++; + + const start = Date.now(); + bundle.$waitedMs$ = start - bundle.$createdTs$; + bundle.$state$ = BundleImportState.Preload; + + config.DEBUG && log(`<< load after ${`${bundle.$waitedMs$}ms`}`, bundle.$name$); + + const link = doc.createElement('link'); + link.href = bundle.$url$!; + link.rel = rel; + // Needed when rel is 'preload' + link.as = 'script'; + // Handle completion of the preload + link.onload = link.onerror = () => { + preloadCount--; + const end = Date.now(); + bundle.$loadedMs$ = end - start; + bundle.$state$ = BundleImportState.Loaded; + config.DEBUG && log(`>> done after ${bundle.$loadedMs$}ms`, bundle.$name$); + // Keep the clean + link.remove(); + // More bundles may be ready to preload + trigger(); + }; + + doc.head.appendChild(link); +}; + +export const adjustProbabilities = ( + bundle: BundleImport, + adjustFactor: number, + seen?: Set +) => { + if (seen?.has(bundle)) { + return; + } + + const previousInverseProbability = bundle.$inverseProbability$; + bundle.$inverseProbability$ *= adjustFactor; + if (previousInverseProbability - bundle.$inverseProbability$ < 0.01) { + return; + } + + if ( + bundle.$state$ < BundleImportState.Preload && + bundle.$inverseProbability$ < config[maxSignificantInverseProbabilityStr] + ) { + if (bundle.$state$ === BundleImportState.None) { + bundle.$state$ = BundleImportState.Queued; + queue.push(bundle); + config.DEBUG && + log(`queued ${Math.round((1 - bundle.$inverseProbability$) * 100)}%`, bundle.$name$); + } + + // It's in the queue, so we need to re-sort it + queueDirty = true; + } + + if (bundle.$deps$) { + seen ||= new Set(); + seen.add(bundle); + for (const dep of bundle.$deps$) { + const depBundle = getBundle(dep.$name$)!; + const prevAdjust = dep.$factor$; + /** + * The chance that a dep won't be loaded is 1-(the chance that the dep will be loaded)*(the + * chance that the current bundle will be loaded) + * + * We can multiply this chance together with all other bundle adjustments to get the chance + * that a dep will be loaded given all the chances of the other bundles + */ + const newInverseProbability = 1 - dep.$probability$ * (1 - bundle.$inverseProbability$); + + /** We need to undo the previous adjustment */ + const factor = newInverseProbability / prevAdjust; + dep.$factor$ = factor; + + adjustProbabilities(depBundle, factor, seen); + } + } +}; + +export const handleBundle = (name: string, inverseProbability: number) => { + const bundle = getBundle(name); + if (bundle && bundle.$inverseProbability$ > inverseProbability) { + adjustProbabilities(bundle, inverseProbability / bundle.$inverseProbability$); + } +}; + +export const preload = (name: string | (number | string)[], probability?: number) => { + if (base == null || !name.length) { + return; + } + + let inverseProbability = probability ? 1 - probability : 0.4; + if (Array.isArray(name)) { + // We must process in reverse order to ensure first bundles are handled first + for (let i = name.length - 1; i >= 0; i--) { + const item = name[i]; + if (typeof item === 'number') { + inverseProbability = 1 - item / 10; + } else { + handleBundle(item, inverseProbability); + inverseProbability *= 1.005; + } + } + } else { + handleBundle(name, inverseProbability); + } + if (isBrowser) { + trigger(); + } +}; diff --git a/packages/qwik/src/core/preloader/types.ts b/packages/qwik/src/core/preloader/types.ts new file mode 100644 index 00000000000..56e79c2f292 --- /dev/null +++ b/packages/qwik/src/core/preloader/types.ts @@ -0,0 +1,35 @@ +export const enum BundleImportState { + None, + Queued, + Preload, + Alias, + Loaded, +} + +export type BundleInfo = { + $inverseProbability$: number; + // TODO check if for performance we should use refs instead of ids + $deps$?: ImportProbability[]; +}; + +export type BundleImport = BundleInfo & { + $name$: string; + $url$: string | null; + $state$: BundleImportState; + $createdTs$: number; + $waitedMs$: number; + $loadedMs$: number; +}; + +export type BundleImports = Map; + +export type ImportProbability = { + /** Bundle name */ + $name$: string; + /** Probability */ + $probability$: number; + /** Probability adjust factor */ + $factor$: number; +}; + +export type BundleGraph = Map; diff --git a/packages/qwik/src/core/qrl/qrl-class.ts b/packages/qwik/src/core/qrl/qrl-class.ts index 36eca9b26f4..61840d4aebc 100644 --- a/packages/qwik/src/core/qrl/qrl-class.ts +++ b/packages/qwik/src/core/qrl/qrl-class.ts @@ -16,9 +16,10 @@ import { isPromise, maybeThen } from '../util/promises'; import { qDev, qSerialize, qTest, seal } from '../util/qdev'; import { isArray, isFunction, type ValueOrPromise } from '../util/types'; // @ts-expect-error we don't have types for the preloader -import { l as loadBundleGraph, p as preload } from '@builder.io/qwik/preloader'; +import { p as preload } from '@builder.io/qwik/preloader'; import type { QRLDev } from './qrl'; import type { QRL, QrlArgs, QrlReturn } from './qrl.public'; +import { isBrowser } from '@builder.io/qwik/build'; export const isQrl = (value: unknown): value is QRLInternal => { return typeof value === 'function' && typeof (value as any).getSymbol === 'function'; @@ -91,10 +92,6 @@ export const createQRL = ( if (!_containerEl) { _containerEl = el; } - // try every time just in case - if (el) { - loadBundleGraph(el); - } return _containerEl; }; @@ -127,8 +124,6 @@ export const createQRL = ( }; const resolve = async (containerEl?: Element): Promise => { - // Give it another bump - preload(getSymbolHash(symbol), true); if (symbolRef !== null) { // Resolving (Promise) or already resolved (value) return symbolRef; @@ -146,6 +141,11 @@ export const createQRL = ( return (qrl.resolved = symbolRef = qFuncs[Number(symbol)] as TYPE); } + if (isBrowser && chunk) { + /** We run the QRL, so now the probability of the chunk is 100% */ + preload(chunk, 1); + } + const start = now(); const ctx = tryGetInvokeContext(); if (symbolFn !== null) { @@ -229,7 +229,13 @@ export const createQRL = ( if (qDev) { seal(qrl); } - preload(hash); + if (isBrowser && resolvedSymbol) { + /** + * Preloading the symbol instead of the chunk allows us to get probabilities for the bundle + * based on its contents. + */ + preload(resolvedSymbol, 0.8); + } return qrl; }; diff --git a/packages/qwik/src/optimizer/src/api.md b/packages/qwik/src/optimizer/src/api.md index 714efe23254..17f6c5f2d62 100644 --- a/packages/qwik/src/optimizer/src/api.md +++ b/packages/qwik/src/optimizer/src/api.md @@ -169,9 +169,11 @@ export type QwikBuildTarget = 'client' | 'ssr' | 'lib' | 'test'; export interface QwikBundle { dynamicImports?: string[]; imports?: string[]; + interactivity?: number; origins?: string[]; size: number; symbols?: string[]; + total: number; } // @public @@ -179,6 +181,7 @@ export type QwikBundleGraph = Array; // @public export interface QwikManifest { + bundleGraph?: QwikBundleGraph; bundles: { [fileName: string]: QwikBundle; }; @@ -187,15 +190,13 @@ export interface QwikManifest { mapping: { [symbolName: string]: string; }; - // (undocumented) options?: { target?: string; buildMode?: string; entryStrategy?: { - [key: string]: any; + type: EntryStrategy['type']; }; }; - // (undocumented) platform?: { [name: string]: string; }; @@ -203,7 +204,6 @@ export interface QwikManifest { symbols: { [symbolName: string]: QwikSymbol; }; - // (undocumented) version: string; } @@ -301,6 +301,8 @@ export type QwikVitePluginOptions = QwikVitePluginCSROptions | QwikVitePluginSSR // @public (undocumented) export interface ResolvedManifest { + // (undocumented) + injections: GlobalInjections[]; // (undocumented) manifest: QwikManifest; // (undocumented) diff --git a/packages/qwik/src/optimizer/src/manifest.ts b/packages/qwik/src/optimizer/src/manifest.ts index 7d80916f73a..767017780b3 100644 --- a/packages/qwik/src/optimizer/src/manifest.ts +++ b/packages/qwik/src/optimizer/src/manifest.ts @@ -236,6 +236,158 @@ export function getValidManifest(manifest: QwikManifest | undefined | null) { return undefined; } +const getBundleInteractivity = (bundle: QwikBundle, manifest: QwikManifest) => { + let maxScore = 0; + if (bundle.symbols) { + for (const symbolName of bundle.symbols) { + let score = 1; + const symbol = manifest.symbols[symbolName]; + if (symbol) { + if (symbol.ctxKind === 'function') { + if (/(component|useStyles|useStylesScoped)/i.test(symbol.ctxName)) { + score += 1; + } else if (/(useComputed|useTask|useVisibleTask|useOn)/i.test(symbol.ctxName)) { + score += 2; + } + } else { + score += 1; + if (/(click|mouse|pointer|touch|key|scroll|gesture|wheel)/i.test(symbol.ctxName)) { + score += 3; + } + } + } + maxScore = Math.max(maxScore, score); + } + } + return maxScore; +}; + +/** + * Computes the total size of each bundle based on its dependencies. Written by ChatGPT ;) - it's + * harder than you think to total nodes in a directed cyclic graph + */ +export function computeTotals(graph: QwikManifest['bundles']): void { + // 1) Prepare Tarjan's structures + let index = 0; + const stack: string[] = []; + const sccList: string[][] = []; + + // Maps for Tarjan + const idx = new Map(); // node -> index + const low = new Map(); // node -> low-link + const onStack = new Set(); + + function strongConnect(v: string) { + idx.set(v, index); + low.set(v, index); + index++; + stack.push(v); + onStack.add(v); + + // Explore children + const children = graph[v].imports || []; + for (const w of children) { + if (!idx.has(w)) { + strongConnect(w); + low.set(v, Math.min(low.get(v)!, low.get(w)!)); + } else if (onStack.has(w)) { + low.set(v, Math.min(low.get(v)!, idx.get(w)!)); + } + } + + // If v is a root node, pop stack to form an SCC + if (low.get(v) === idx.get(v)) { + const comp: string[] = []; + let x: string; + do { + x = stack.pop()!; + onStack.delete(x); + comp.push(x); + } while (x !== v); + sccList.push(comp); + } + } + + // Run Tarjan over all nodes + for (const v of Object.keys(graph)) { + if (!idx.has(v)) { + strongConnect(v); + } + } + + // 2) Build DAG of SCCs + // sccIndex: which SCC a node belongs to + const sccIndex = new Map(); + sccList.forEach((comp, i) => { + for (const v of comp) { + sccIndex.set(v, i); + } + }); + + // Create adjacency for the SCC graph + const sccDAG: Set[] = Array.from({ length: sccList.length }, () => new Set()); + for (const v of Object.keys(graph)) { + const i = sccIndex.get(v)!; + for (const w of graph[v].imports || []) { + const j = sccIndex.get(w)!; + if (i !== j) { + sccDAG[i].add(j); + } + } + } + + // 3) Topological sort the SCC DAG + const visited = new Set(); + const order: number[] = []; + + function dfsSCC(u: number) { + visited.add(u); + for (const v of sccDAG[u]) { + if (!visited.has(v)) { + dfsSCC(v); + } + } + order.push(u); + } + + for (let i = 0; i < sccList.length; i++) { + if (!visited.has(i)) { + dfsSCC(i); + } + } + order.reverse(); // Now it's a topological order + + // 4) Compute totals from bottom to top + const sccTotals = new Array(sccList.length).fill(0); + + // First compute the sum of 'size' in each SCC + for (let i = 0; i < sccList.length; i++) { + let sumSize = 0; + for (const nodeId of sccList[i]) { + sumSize += graph[nodeId].size; + } + sccTotals[i] = sumSize; + } + + // Then add child totals in topological order (reversed) + for (let k = order.length - 1; k >= 0; k--) { + const sccId = order[k]; + let total = sccTotals[sccId]; + for (const child of sccDAG[sccId]) { + total += sccTotals[child]; + } + sccTotals[sccId] = total; + } + + // 5) Assign computed totals back to each node in the original graph + for (let i = 0; i < sccList.length; i++) { + const total = sccTotals[i]; + for (const nodeId of sccList[i]) { + graph[nodeId].total = total; + } + } +} + export function generateManifestFromBundles( path: Path, segments: SegmentAnalysis[], @@ -249,13 +401,13 @@ export function generateManifestFromBundles( symbols: {}, mapping: {}, bundles: {}, - preloader: '', injections, version: '1', options: { target: opts.target, buildMode: opts.buildMode, - entryStrategy: opts.entryStrategy, + // don't copy the insights stuff + entryStrategy: opts.entryStrategy && { type: opts.entryStrategy.type }, }, }; @@ -270,16 +422,19 @@ export function generateManifestFromBundles( } return canonPath(bundle.fileName); }; + // We need to find our QRL exports - const qrlNames = new Set([...segments.map((h) => h.name)]); + const qrlNames = new Set(segments.map((h) => h.name)); for (const outputBundle of Object.values(outputBundles)) { if (outputBundle.type !== 'chunk') { continue; } const bundleFileName = canonPath(outputBundle.fileName); + const size = outputBundle.code.length; const bundle: QwikBundle = { - size: outputBundle.code.length, + size, + total: -1, }; for (const symbol of outputBundle.exports) { @@ -344,6 +499,14 @@ export function generateManifestFromBundles( loc: segment.loc, }; } + + for (const bundle of Object.values(manifest.bundles)) { + const interactivityScore = getBundleInteractivity(bundle, manifest); + bundle.interactivity = interactivityScore; + } + + computeTotals(manifest.bundles); + // To inspect the bundles, uncomment the following lines // import('node:fs').then((fs) => // fs.writeFileSync( @@ -356,7 +519,7 @@ export function generateManifestFromBundles( // n, // { // ...b, - // // code: 'code' in b ? `` : undefined, + // code: 'code' in b ? b.code.slice(0, 5000) : undefined, // map: 'map' in b ? `` : undefined, // source: 'source' in b ? `` : undefined, // modules: diff --git a/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts b/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts index 8c6a6a3b719..87a62e7d405 100644 --- a/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts +++ b/packages/qwik/src/optimizer/src/plugins/bundle-graph.ts @@ -1,6 +1,9 @@ -import { getSymbolHash } from 'packages/qwik/src/core/qrl/qrl-class'; import type { QwikBundle, QwikBundleGraph, QwikManifest } from '../types'; +const minimumSpeed = 300; // kbps +// size that takes 0.5 seconds to download at minimumSpeed +const slowSize = 0.5 / ((minimumSpeed * 1024) / 8); + /** * A function that returns a map of bundle names to their dependencies. * @@ -10,7 +13,14 @@ export type BundleGraphAdder = ( manifest: QwikManifest ) => Record; -const dynamicTag = ''; +const getSymbolHash = (symbolName: string) => { + const index = symbolName.lastIndexOf('_'); + if (index > -1) { + return symbolName.slice(index + 1); + } + return symbolName; +}; + /** * This creates a compact array of dependencies for each bundle. It also contains the symbols. The * format is: @@ -32,18 +42,18 @@ export function convertManifestToBundleGraph( if (!manifest.bundles) { return []; } - // All known chunks + // All known chunks and symbols const graph = { ...manifest.bundles }; - // Symbols - Object.assign( - graph, - Object.fromEntries( - Object.entries(manifest.mapping).map(([symbol, chunkname]) => [ - getSymbolHash(symbol), - { imports: [chunkname] } as QwikBundle, - ]) - ) - ); + for (const [symbol, bundleName] of Object.entries(manifest.mapping)) { + const hash = getSymbolHash(symbol); + if (hash) { + /** + * We use dynamic imports so that we will get probabilities for the bundle when preloading the + * symbol. We still confirm load at 100% probability with the bundle name. + */ + graph[hash] = { dynamicImports: [bundleName] } as QwikBundle; + } + } // Routes etc if (bundleGraphAdders) { for (const adder of bundleGraphAdders) { @@ -54,6 +64,11 @@ export function convertManifestToBundleGraph( } } + // Remove the preloader, it will already be loaded and has no dependencies + if (manifest.preloader) { + delete graph[manifest.preloader]; + } + // Filter out external and non-segment dynamic imports for (const bundleName of Object.keys(graph)) { const bundle = graph[bundleName]; @@ -120,13 +135,47 @@ export function convertManifestToBundleGraph( clearTransitiveDeps(deps, depName); } const dynDeps = new Set(bundle.dynamicImports!); + const depProbability = new Map(); for (const depName of dynDeps) { clearTransitiveDeps(dynDeps, depName); + const dep = graph[depName]; + + // Calculate the probability of the dependency + // Start with a 50% chance + let probability = 0.5; + // Add a 4% chance for each interactivity point (max 20%) + probability += (dep.interactivity || 0) * 0.04; + + // If the dependency has a segment from the same parent, it's more likely to be loaded + if (bundle.origins && dep.origins) { + for (const origin of bundle.origins) { + if (dep.origins.some((o) => o.startsWith(origin))) { + // Add a 25% chance + probability += 0.25; + break; + } + } + } + + // If the dependency is a likely big import graph, it should be loaded earlier so it doesn't get blocked by smaller files, but when unlikely it should be loaded later so it doesn't block other files + if (dep.total > slowSize) { + probability += probability > 0.5 ? 0.02 : -0.02; + } + + depProbability.set(depName, probability); } + if (dynDeps.size > 0) { + const sorted = Array.from(dynDeps).sort( + (a, b) => depProbability.get(b)! - depProbability.get(a)! + ); + let lastProbability = -1; // We rely on the Set keeping the items in order, everything after this is dynamic - deps.add(dynamicTag); - for (const depName of dynDeps) { + for (const depName of sorted) { + if (depProbability.get(depName)! !== lastProbability) { + lastProbability = depProbability.get(depName)!; + deps.add(-Math.round(lastProbability * 10) as any as string); + } deps.add(depName); } } @@ -146,8 +195,9 @@ export function convertManifestToBundleGraph( let { index, deps } = bundle; index++; for (const depName of deps) { - if (depName === dynamicTag) { - bundleGraph[index++] = -1; + if (typeof depName === 'number') { + // negative number means dynamic import + bundleGraph[index++] = depName; continue; } const dep = map.get(depName)!; diff --git a/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts b/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts index e835731a8c7..b3319ce936a 100644 --- a/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts +++ b/packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts @@ -3,43 +3,44 @@ import path from 'node:path'; import { describe, expect, test } from 'vitest'; import { generateManifestFromBundles } from '../manifest'; import { convertManifestToBundleGraph } from './bundle-graph'; -// You can generate this by uncommenting the writing code in manifest.ts, building, running `pnpm serve`, and visiting the perf.prod app +// You can generate this file by uncommenting the writing code in manifest.ts, building, running `pnpm build.client` in the starters/apps/preload-test dir and moving the output import outputBundles from './fixture-output-bundles.json'; describe('convertManifestToBundleGraph', () => { + const size = 0, + total = 0; const fakeManifest = { bundles: { - 'app.js': { - size: 0, - imports: ['static-dep.js', '@external-dep'], - }, + 'app.js': { size, total, imports: ['static-dep.js', '@external-dep'] }, 'static-dep.js': { - size: 0, + size, + total, dynamicImports: ['@other', 'transitive-dep.js', 'dynamic-dep.js', 'no-symbols.js'], }, 'dynamic-dep.js': { - size: 0, + size, + total, imports: ['static-dep.js', 'transitive-dep.js', '@external-dep'], - dynamicImports: ['has-a-symbol.js', 'no-symbols.js'], + dynamicImports: ['has-a-symbol.js', 'boring-dep.js', 'no-symbols.js'], + origins: ['dynamic-dep.js'], symbols: ['sym1'], }, - 'transitive-dep.js': { - size: 0, - symbols: ['sym4'], - }, - 'not-used.js': { - size: 0, - }, + 'transitive-dep.js': { size, total, symbols: ['sym4'] }, + 'not-used.js': { size, total }, 'has-a-symbol.js': { - size: 0, + size, + total, + dynamicImports: ['large-file.js'], symbols: ['sym2'], + origins: ['dynamic-dep.js_handleClick_sym2.js'], }, - 'no-symbols.js': { - size: 0, - }, + 'no-symbols.js': { size, total }, + 'boring-dep.js': { size, total, symbols: ['sym5'], origins: ['boring-dep.js'] }, + 'large-file.js': { size: 100000, total: 100000, symbols: ['sym3'] }, } as Record, - mapping: { sym1: 'dynamic-dep.js', sym2: 'has-a-symbol.js' }, + mapping: { sym1: 'dynamic-dep.js', sym2: 'has-a-symbol.js', sym3: 'large-file.js' }, symbols: {}, + preloader: 'no-symbols.js', manifestHash: '123', version: '1.0.0', } as QwikManifest; @@ -49,20 +50,31 @@ describe('convertManifestToBundleGraph', () => { 'app.js', // 0 2, 'static-dep.js', // 2 - -1, + -5, 5, - // doesn't list 8 because it's also statically imported by dynamic-dep.js + // doesn't list 13 because it's also statically imported by dynamic-dep.js 'dynamic-dep.js', // 5 2, - 10, - -1, - 11, - 'transitive-dep.js', // 10 - 'has-a-symbol.js', // 11 - 'sym1', // 12 + 12, + -8, + 13, + -5, + 16, + 'transitive-dep.js', // 12 + 'has-a-symbol.js', // 13 + -5, + 17, + 'boring-dep.js', // 16 + 'large-file.js', // 17 + 'sym1', // 18 + -5, 5, - 'sym2', // 14 - 11, + 'sym2', // 21 + -5, + 13, + 'sym3', // 24 + -5, + 17, ]); }); @@ -73,29 +85,19 @@ describe('convertManifestToBundleGraph', () => { test('simple file set', () => { const manifest = { bundles: { - 'a.js': { - size: 0, - imports: ['b.js'], - dynamicImports: ['c.js'], - }, - 'b.js': { - size: 0, - dynamicImports: ['c.js'], - }, - 'c.js': { - size: 0, - symbols: ['sym1'], - }, + 'a.js': { size, total, imports: ['b.js'], dynamicImports: ['c.js'] }, + 'b.js': { size, total, dynamicImports: ['c.js'] }, + 'c.js': { size, total, symbols: ['sym1'] }, } as Record, mapping: {}, } as QwikManifest; expect(convertManifestToBundleGraph(manifest)).toEqual([ 'a.js', // 0 4, - -1, + -5, 7, 'b.js', // 4 - -1, + -5, 7, 'c.js', // 7 ]); @@ -127,20 +129,28 @@ describe('convertManifestToBundleGraph', () => { 'app.js', // 0 2, 'static-dep.js', // 2 - -1, + -5, 5, 'dynamic-dep.js', // 5 2, 8, 'transitive-dep.js', // 8 'has-a-symbol.js', // 9 - 'sym1', // 10 + -5, + 12, + 'large-file.js', // 12 + 'sym1', // 13 + -5, 5, - 'sym2', // 12 + 'sym2', // 16 + -5, 9, - 'dashboard/', // 14 + 'sym3', // 19 + -5, + 12, + 'dashboard/', // 22 2, - -1, + -5, 8, ]); }); @@ -155,50 +165,316 @@ describe('convertManifestToBundleGraph', () => { console.error ); + // Interactivity scores + expect( + Object.fromEntries( + Object.entries(manifest.bundles).map(([k, v]) => [ + k, + `${v.interactivity} (${v.size}/${v.total})`, + ]) + ) + ).toMatchInlineSnapshot(` + { + "@qwik-city-plan.js": "0 (4886/7301)", + "core.js": "0 (4954/9908)", + "index.js": "0 (1001/18278)", + "index.qwik.mjs_ErrorBoundary_component_VJVmS9CcP1U.js": "2 (4954/22231)", + "index.qwik.mjs_ErrorBoundary_component_useOnWindow_0s3bilOgUys.js": "1 (4954/19816)", + "index.qwik.mjs_Form_form_onSubmit_p0qDGZV34Qs.js": "1 (598/15460)", + "index.qwik.mjs_GetForm_component_amqstTwiNo0.js": "2 (4954/39508)", + "index.qwik.mjs_GetForm_component_form_onSubmit_1_8hsX2DP6YsI.js": "1 (730/15592)", + "index.qwik.mjs_GetForm_component_form_onSubmit_OBdjRsvd4IY.js": "1 (4954/19816)", + "index.qwik.mjs_Link_component_bp3n7NtzXfs.js": "2 (3091/37645)", + "index.qwik.mjs_Link_component_handleClick_gZBt5yIBEB4.js": "1 (957/15819)", + "index.qwik.mjs_Link_component_handlePrefetch_FpLYno2MZMA.js": "1 (4954/39508)", + "index.qwik.mjs_QwikCityMockProvider_component_4XSnxjzQSYM.js": "2 (4954/39508)", + "index.qwik.mjs_QwikCityMockProvider_component_goto_cTkWSVfU9vo.js": "1 (4954/19816)", + "index.qwik.mjs_QwikCityProvider_component_OzSZb6UyaCs.js": "2 (3792/38346)", + "index.qwik.mjs_QwikCityProvider_component_goto_IDVXhQLfmNQ.js": "1 (4954/46809)", + "index.qwik.mjs_QwikCityProvider_component_registerPreventNav_zQJLLuPn6rA.js": "1 (4954/39508)", + "index.qwik.mjs_QwikCityProvider_component_useStyles_OC53PuG02nc.js": "2 (4954/4954)", + "index.qwik.mjs_QwikCityProvider_component_useTask_n397ZlNS8UY.js": "3 (4954/46809)", + "index.qwik.mjs_RouterOutlet_component_KRtEZRf6Xh8.js": "2 (2944/37498)", + "index.qwik.mjs_routeActionQrl_action_submit_WcRKLKMW88U.js": "1 (4954/19816)", + "index.qwik.mjs_serverQrl_rpc_AaY04hL3w3A.js": "1 (4954/39508)", + "index.qwik.mjs_spaInit_event_EMGw8L1tB9Y.js": "1 (4954/4954)", + "index.qwik.mjs_usePreventNavigateQrl_useVisibleTask_0OaA1Rw0yxk.js": "3 (628/15490)", + "index.tsx_about_component_m7u9ARcfDGU.js": "2 (2305/19582)", + "index.tsx_about_component_useStyles_WOcPLNnm2is.js": "2 (1135/1135)", + "index.tsx_form_component_ds9jIPT1g9s.js": "2 (4886/74924)", + "index.tsx_form_component_useStyles_0pasaG6nmEA.js": "2 (2158/2158)", + "index.tsx_routes_component_div_button_onClick_BjxcCeNQ9ak.js": "5 (4886/19748)", + "index.tsx_routes_component_handleClick_ep3t0fF0SDA.js": "1 (4886/40441)", + "index.tsx_routes_component_useStyles_Iyy38y0K3Hw.js": "2 (4886/4886)", + "index.tsx_routes_component_useTask_99K9SAWjPFQ.js": "3 (902/15764)", + "index.tsx_routes_component_useVisibleTask_HEFxKy9cwuk.js": "3 (935/36490)", + "index.tsx_routes_component_vG0UuU4cNCg.js": "2 (4886/22163)", + "index2.js": "0 (771/18048)", + "index3.js": "0 (930/35484)", + "layout.js": "0 (4886/22163)", + "layout.tsx_layout_component_SHtFir1Ia94.js": "2 (2598/37152)", + "layout.tsx_layout_component_div_header_div_label_input_bind_checked_eevMxFvmCM8.js": "2 (4226/19088)", + "layout.tsx_layout_component_useStyles_MOLFIZOhXmE.js": "2 (1207/1207)", + "preload-helper.js": "0 (2415/2415)", + "preloader.js": "0 (4954/4954)", + "qwik-city.js": "0 (4954/17277)", + "root.js": "0 (4886/22163)", + "root.tsx_root_component_9PcKHFjikV0.js": "2 (4886/39440)", + "router-head.tsx_RouterHead_component_dAo05yeFq1I.js": "2 (4886/39440)", + "src-vendor-lib-helper.ts.js": "0 (4886/4886)", + "src-vendor-lib-libA.ts.js": "0 (465/5351)", + "src-vendor-lib-libB.ts.js": "0 (4886/9772)", + } + `); + expect(convertManifestToBundleGraph(manifest)).toMatchInlineSnapshot(` [ - "app.js", + "@qwik-city-plan.js", + 118, + "core.js", + "index.js", + 2, + 118, + -9, + 86, + "index.qwik.mjs_ErrorBoundary_component_useOnWindow_0s3bilOgUys.js", + 2, + "index.qwik.mjs_ErrorBoundary_component_VJVmS9CcP1U.js", + 2, + 118, + -6, + 8, + "index.qwik.mjs_Form_form_onSubmit_p0qDGZV34Qs.js", + 2, + "index.qwik.mjs_GetForm_component_amqstTwiNo0.js", + 119, + -6, 22, - 23, - -1, - 19, - "app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js", + 24, + "index.qwik.mjs_GetForm_component_form_onSubmit_1_8hsX2DP6YsI.js", + 2, + "index.qwik.mjs_GetForm_component_form_onSubmit_OBdjRsvd4IY.js", + 2, + "index.qwik.mjs_Link_component_bp3n7NtzXfs.js", + 119, + -6, + 31, + 33, + "index.qwik.mjs_Link_component_handleClick_gZBt5yIBEB4.js", + 2, + "index.qwik.mjs_Link_component_handlePrefetch_FpLYno2MZMA.js", + 119, + "index.qwik.mjs_QwikCityMockProvider_component_4XSnxjzQSYM.js", + 119, + -6, + 39, + "index.qwik.mjs_QwikCityMockProvider_component_goto_cTkWSVfU9vo.js", + 2, + "index.qwik.mjs_QwikCityProvider_component_goto_IDVXhQLfmNQ.js", 0, - "app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js", + 119, + "index.qwik.mjs_QwikCityProvider_component_OzSZb6UyaCs.js", + 119, + -6, + 54, + 53, + 41, + 51, + "index.qwik.mjs_QwikCityProvider_component_registerPreventNav_zQJLLuPn6rA.js", + 119, + "index.qwik.mjs_QwikCityProvider_component_useStyles_OC53PuG02nc.js", + "index.qwik.mjs_QwikCityProvider_component_useTask_n397ZlNS8UY.js", 0, - "app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js", - 22, - "app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js", - 22, - "app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js", - 22, - "app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js", - 0, - "app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js", - 22, - "app.tsx_App_component_jn5XSz7NZ88.js", - 22, - 23, - "core.js", + 119, + "index.qwik.mjs_routeActionQrl_action_submit_WcRKLKMW88U.js", + 2, + "index.qwik.mjs_RouterOutlet_component_KRtEZRf6Xh8.js", + 119, + "index.qwik.mjs_serverQrl_rpc_AaY04hL3w3A.js", + 119, + "index.qwik.mjs_spaInit_event_EMGw8L1tB9Y.js", + "index.qwik.mjs_usePreventNavigateQrl_useVisibleTask_0OaA1Rw0yxk.js", + 2, + "index.tsx_about_component_m7u9ARcfDGU.js", + 2, + 118, + -6, + 71, + "index.tsx_about_component_useStyles_WOcPLNnm2is.js", + "index.tsx_form_component_ds9jIPT1g9s.js", + 101, + -6, + 76, + "index.tsx_form_component_useStyles_0pasaG6nmEA.js", + "index.tsx_routes_component_div_button_onClick_BjxcCeNQ9ak.js", + 2, + "index.tsx_routes_component_handleClick_ep3t0fF0SDA.js", + 3, + "index.tsx_routes_component_useStyles_Iyy38y0K3Hw.js", + "index.tsx_routes_component_useTask_99K9SAWjPFQ.js", + 2, + "index.tsx_routes_component_useVisibleTask_HEFxKy9cwuk.js", + 3, + "index.tsx_routes_component_vG0UuU4cNCg.js", + 2, + 118, + -7, + 77, + -6, + 82, + 84, + 81, + 79, + "index2.js", + 2, + 118, + -9, + 66, + "index3.js", + 119, + -9, + 72, + "layout.js", + 2, + 118, + -9, + 112, + "layout.tsx_layout_component_div_header_div_label_input_bind_checked_eevMxFvmCM8.js", + 2, + "layout.tsx_layout_component_SHtFir1Ia94.js", + 119, + -6, + 110, + 117, + "layout.tsx_layout_component_useStyles_MOLFIZOhXmE.js", "preload-helper.js", + "qwik-city.js", + 2, + 118, "root.js", - 0, - "5fYbrS6ABNA", - 11, - "DDeCLEw4BYU", + 2, + 118, + -9, + 127, + "root.tsx_root_component_9PcKHFjikV0.js", + 119, + -9, + 131, + "router-head.tsx_RouterHead_component_dAo05yeFq1I.js", + 119, + "src-vendor-lib-helper.ts.js", + "src-vendor-lib-libA.ts.js", + 133, + "src-vendor-lib-libB.ts.js", + 133, + "BjxcCeNQ9ak", + -7, + 77, + "eevMxFvmCM8", + -6, + 110, + "99K9SAWjPFQ", + -6, + 82, + "n397ZlNS8UY", + -6, + 54, + "0OaA1Rw0yxk", + -6, + 64, + "HEFxKy9cwuk", + -6, + 84, + "4XSnxjzQSYM", + -6, + 35, + "9PcKHFjikV0", + -6, + 127, + "KRtEZRf6Xh8", + -6, + 59, + "OzSZb6UyaCs", + -6, + 44, + "SHtFir1Ia94", + -6, + 112, + "VJVmS9CcP1U", + -6, + 10, + "amqstTwiNo0", + -6, 17, - "WC1qsF4RHoU", - 5, - "cibDwmrlmRI", - 7, - "dHjr9JWbW34", - 9, - "rpMfjSetIeI", - 13, - "wEyctjlC58Q", + "bp3n7NtzXfs", + -6, + 26, + "dAo05yeFq1I", + -6, + 131, + "ds9jIPT1g9s", + -6, + 72, + "m7u9ARcfDGU", + -6, + 66, + "vG0UuU4cNCg", + -6, + 86, + "0pasaG6nmEA", + -6, + 76, + "Iyy38y0K3Hw", + -6, + 81, + "MOLFIZOhXmE", + -6, + 117, + "OC53PuG02nc", + -6, + 53, + "WOcPLNnm2is", + -6, + 71, + "AaY04hL3w3A", + -6, + 61, + "EMGw8L1tB9Y", + -6, + 63, + "WcRKLKMW88U", + -6, + 57, + "p0qDGZV34Qs", + -6, 15, - "jn5XSz7NZ88", - 19, + "0s3bilOgUys", + -6, + 8, + "8hsX2DP6YsI", + -6, + 22, + "FpLYno2MZMA", + -6, + 33, + "IDVXhQLfmNQ", + -6, + 41, + "OBdjRsvd4IY", + -6, + 24, + "cTkWSVfU9vo", + -6, + 39, + "ep3t0fF0SDA", + -6, + 79, + "gZBt5yIBEB4", + -6, + 31, + "zQJLLuPn6rA", + -6, + 51, ] `); }); diff --git a/packages/qwik/src/optimizer/src/plugins/fixture-output-bundles.json b/packages/qwik/src/optimizer/src/plugins/fixture-output-bundles.json index 14217a1ab6a..af43574ff4b 100644 --- a/packages/qwik/src/optimizer/src/plugins/fixture-output-bundles.json +++ b/packages/qwik/src/optimizer/src/plugins/fixture-output-bundles.json @@ -1,162 +1,2287 @@ { "segments": [ { - "origin": "components/app/app.tsx", - "name": "App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI", - "entry": null, - "displayName": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_2", - "hash": "cibDwmrlmRI", - "canonicalFilename": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI", - "path": "components/app", + "origin": "root.tsx", + "name": "s_9PcKHFjikV0", + "entry": "root.tsx_entry_root", + "displayName": "root.tsx_root_component", + "hash": "9PcKHFjikV0", + "canonicalFilename": "root.tsx_root_component_9PcKHFjikV0", + "path": "", "extension": "js", - "parent": "App_component_jn5XSz7NZ88", - "ctxKind": "eventHandler", - "ctxName": "onClick$", + "parent": null, + "ctxKind": "function", + "ctxName": "component$", + "captures": false, + "loc": [238, 675] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_p0qDGZV34Qs", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_Form", + "displayName": "index.qwik.mjs_Form_form_onSubmit", + "hash": "p0qDGZV34Qs", + "canonicalFilename": "index.qwik.mjs_Form_form_onSubmit_p0qDGZV34Qs", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": null, + "ctxKind": "function", + "ctxName": "$", "captures": true, - "loc": [2427, 2502] + "loc": [58110, 58224] }, { - "origin": "components/app/app.tsx", - "name": "App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34", - "entry": null, - "displayName": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_3", - "hash": "dHjr9JWbW34", - "canonicalFilename": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34", - "path": "components/app", + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_0OaA1Rw0yxk", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_usePreventNavigateQrl", + "displayName": "index.qwik.mjs_usePreventNavigateQrl_useVisibleTask", + "hash": "0OaA1Rw0yxk", + "canonicalFilename": "index.qwik.mjs_usePreventNavigateQrl_useVisibleTask_0OaA1Rw0yxk", + "path": "../../../../packages/qwik-city/lib", "extension": "js", - "parent": "App_component_jn5XSz7NZ88", - "ctxKind": "eventHandler", - "ctxName": "onClick$", + "parent": null, + "ctxKind": "function", + "ctxName": "useVisibleTask$", "captures": true, - "loc": [2769, 3038] + "loc": [22543, 22571] }, { - "origin": "components/app/app.tsx", - "name": "App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU", - "entry": null, - "displayName": "app.tsx_App_component_div_table_tbody_tr_td_a_onClick", - "hash": "DDeCLEw4BYU", - "canonicalFilename": "app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU", - "path": "components/app", + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_gZBt5yIBEB4", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_Link", + "displayName": "index.qwik.mjs_Link_component_handleClick", + "hash": "gZBt5yIBEB4", + "canonicalFilename": "index.qwik.mjs_Link_component_handleClick_gZBt5yIBEB4", + "path": "../../../../packages/qwik-city/lib", "extension": "js", - "parent": "App_component_jn5XSz7NZ88", - "ctxKind": "eventHandler", - "ctxName": "onClick$", + "parent": "s_bp3n7NtzXfs", + "ctxKind": "function", + "ctxName": "$", "captures": true, - "loc": [4339, 4357] + "loc": [40963, 41389] }, { - "origin": "components/app/app.tsx", - "name": "App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU", - "entry": null, - "displayName": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_1", - "hash": "WC1qsF4RHoU", - "canonicalFilename": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU", - "path": "components/app", + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_KRtEZRf6Xh8", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_RouterOutlet", + "displayName": "index.qwik.mjs_RouterOutlet_component", + "hash": "KRtEZRf6Xh8", + "canonicalFilename": "index.qwik.mjs_RouterOutlet_component_KRtEZRf6Xh8", + "path": "../../../../packages/qwik-city/lib", "extension": "js", - "parent": "App_component_jn5XSz7NZ88", - "ctxKind": "eventHandler", - "ctxName": "onClick$", + "parent": null, + "ctxKind": "function", + "ctxName": "component$", + "captures": false, + "loc": [7100, 8254] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_0s3bilOgUys", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_ErrorBoundary", + "displayName": "index.qwik.mjs_ErrorBoundary_component_useOnWindow", + "hash": "0s3bilOgUys", + "canonicalFilename": "index.qwik.mjs_ErrorBoundary_component_useOnWindow_0s3bilOgUys", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": "s_VJVmS9CcP1U", + "ctxKind": "function", + "ctxName": "$", "captures": true, - "loc": [2144, 2181] + "loc": [57415, 57462] }, { - "origin": "components/app/app.tsx", - "name": "App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q", - "entry": null, - "displayName": "app.tsx_App_component_div_div_div_div_div_div_button_onClick", - "hash": "wEyctjlC58Q", - "canonicalFilename": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q", - "path": "components/app", + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_OzSZb6UyaCs", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_QwikCityProvider", + "displayName": "index.qwik.mjs_QwikCityProvider_component", + "hash": "OzSZb6UyaCs", + "canonicalFilename": "index.qwik.mjs_QwikCityProvider_component_OzSZb6UyaCs", + "path": "../../../../packages/qwik-city/lib", "extension": "js", - "parent": "App_component_jn5XSz7NZ88", - "ctxKind": "eventHandler", - "ctxName": "onClick$", + "parent": null, + "ctxKind": "function", + "ctxName": "component$", + "captures": false, + "loc": [23979, 38218] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_n397ZlNS8UY", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_QwikCityProvider", + "displayName": "index.qwik.mjs_QwikCityProvider_component_useTask", + "hash": "n397ZlNS8UY", + "canonicalFilename": "index.qwik.mjs_QwikCityProvider_component_useTask_n397ZlNS8UY", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": "s_OzSZb6UyaCs", + "ctxKind": "function", + "ctxName": "useTask$", "captures": true, - "loc": [1859, 1895] + "loc": [28796, 38172] }, { - "origin": "components/app/app.tsx", - "name": "App_component_jn5XSz7NZ88", - "entry": null, - "displayName": "app.tsx_App_component", - "hash": "jn5XSz7NZ88", - "canonicalFilename": "app.tsx_App_component_jn5XSz7NZ88", - "path": "components/app", + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_VJVmS9CcP1U", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_ErrorBoundary", + "displayName": "index.qwik.mjs_ErrorBoundary_component", + "hash": "VJVmS9CcP1U", + "canonicalFilename": "index.qwik.mjs_ErrorBoundary_component_VJVmS9CcP1U", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": null, + "ctxKind": "function", + "ctxName": "component$", + "captures": false, + "loc": [57339, 57653] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_AaY04hL3w3A", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_serverQrl", + "displayName": "index.qwik.mjs_serverQrl_rpc", + "hash": "AaY04hL3w3A", + "canonicalFilename": "index.qwik.mjs_serverQrl_rpc_AaY04hL3w3A", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": null, + "ctxKind": "function", + "ctxName": "$", + "captures": true, + "loc": [52759, 55831] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_OC53PuG02nc", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_QwikCityProvider", + "displayName": "index.qwik.mjs_QwikCityProvider_component_useStyles", + "hash": "OC53PuG02nc", + "canonicalFilename": "index.qwik.mjs_QwikCityProvider_component_useStyles_OC53PuG02nc", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": "s_OzSZb6UyaCs", + "ctxKind": "function", + "ctxName": "useStyles$", + "captures": false, + "loc": [24005, 24039] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_4XSnxjzQSYM", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_QwikCityMockProvider", + "displayName": "index.qwik.mjs_QwikCityMockProvider_component", + "hash": "4XSnxjzQSYM", + "canonicalFilename": "index.qwik.mjs_QwikCityMockProvider_component_4XSnxjzQSYM", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": null, + "ctxKind": "function", + "ctxName": "component$", + "captures": false, + "loc": [38420, 39625] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_OBdjRsvd4IY", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_GetForm", + "displayName": "index.qwik.mjs_GetForm_component_form_onSubmit", + "hash": "OBdjRsvd4IY", + "canonicalFilename": "index.qwik.mjs_GetForm_component_form_onSubmit_OBdjRsvd4IY", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": "s_amqstTwiNo0", + "ctxKind": "function", + "ctxName": "$", + "captures": true, + "loc": [59343, 59723] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_amqstTwiNo0", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_GetForm", + "displayName": "index.qwik.mjs_GetForm_component", + "hash": "amqstTwiNo0", + "canonicalFilename": "index.qwik.mjs_GetForm_component_amqstTwiNo0", + "path": "../../../../packages/qwik-city/lib", "extension": "js", "parent": null, "ctxKind": "function", "ctxName": "component$", "captures": false, - "loc": [1334, 5133] + "loc": [58979, 60132] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_zQJLLuPn6rA", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_QwikCityProvider", + "displayName": "index.qwik.mjs_QwikCityProvider_component_registerPreventNav", + "hash": "zQJLLuPn6rA", + "canonicalFilename": "index.qwik.mjs_QwikCityProvider_component_registerPreventNav_zQJLLuPn6rA", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": "s_OzSZb6UyaCs", + "ctxKind": "function", + "ctxName": "$", + "captures": false, + "loc": [25296, 26173] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_FpLYno2MZMA", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_Link", + "displayName": "index.qwik.mjs_Link_component_handlePrefetch", + "hash": "FpLYno2MZMA", + "canonicalFilename": "index.qwik.mjs_Link_component_handlePrefetch_FpLYno2MZMA", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": "s_bp3n7NtzXfs", + "ctxKind": "function", + "ctxName": "$", + "captures": false, + "loc": [40364, 40713] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_8hsX2DP6YsI", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_GetForm", + "displayName": "index.qwik.mjs_GetForm_component_form_onSubmit_1", + "hash": "8hsX2DP6YsI", + "canonicalFilename": "index.qwik.mjs_GetForm_component_form_onSubmit_1_8hsX2DP6YsI", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": "s_amqstTwiNo0", + "ctxKind": "function", + "ctxName": "$", + "captures": false, + "loc": [59734, 60070] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_WcRKLKMW88U", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_routeActionQrl", + "displayName": "index.qwik.mjs_routeActionQrl_action_submit", + "hash": "WcRKLKMW88U", + "canonicalFilename": "index.qwik.mjs_routeActionQrl_action_submit_WcRKLKMW88U", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": null, + "ctxKind": "function", + "ctxName": "$", + "captures": true, + "loc": [44681, 46326] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_IDVXhQLfmNQ", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_QwikCityProvider", + "displayName": "index.qwik.mjs_QwikCityProvider_component_goto", + "hash": "IDVXhQLfmNQ", + "canonicalFilename": "index.qwik.mjs_QwikCityProvider_component_goto_IDVXhQLfmNQ", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": "s_OzSZb6UyaCs", + "ctxKind": "function", + "ctxName": "$", + "captures": true, + "loc": [26193, 28267] }, { - "origin": "components/app/app.tsx", - "name": "App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA", + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_EMGw8L1tB9Y", "entry": null, - "displayName": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_4", - "hash": "5fYbrS6ABNA", - "canonicalFilename": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA", - "path": "components/app", + "displayName": "index.qwik.mjs_spaInit_event", + "hash": "EMGw8L1tB9Y", + "canonicalFilename": "index.qwik.mjs_spaInit_event_EMGw8L1tB9Y", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": null, + "ctxKind": "function", + "ctxName": "event$", + "captures": false, + "loc": [1402, 7065] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_cTkWSVfU9vo", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_QwikCityMockProvider", + "displayName": "index.qwik.mjs_QwikCityMockProvider_component_goto", + "hash": "cTkWSVfU9vo", + "canonicalFilename": "index.qwik.mjs_QwikCityMockProvider_component_goto_cTkWSVfU9vo", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": "s_4XSnxjzQSYM", + "ctxKind": "function", + "ctxName": "$", + "captures": false, + "loc": [38810, 38888] + }, + { + "origin": "../../../../packages/qwik-city/lib/index.qwik.mjs", + "name": "s_bp3n7NtzXfs", + "entry": "../../../../packages/qwik-city/lib/index.qwik.mjs_entry_Link", + "displayName": "index.qwik.mjs_Link_component", + "hash": "bp3n7NtzXfs", + "canonicalFilename": "index.qwik.mjs_Link_component_bp3n7NtzXfs", + "path": "../../../../packages/qwik-city/lib", + "extension": "js", + "parent": null, + "ctxKind": "function", + "ctxName": "component$", + "captures": false, + "loc": [39652, 41942] + }, + { + "origin": "components/router-head/router-head.tsx", + "name": "s_dAo05yeFq1I", + "entry": "components/router-head/router-head.tsx_entry_RouterHead", + "displayName": "router-head.tsx_RouterHead_component", + "hash": "dAo05yeFq1I", + "canonicalFilename": "router-head.tsx_RouterHead_component_dAo05yeFq1I", + "path": "components/router-head", + "extension": "js", + "parent": null, + "ctxKind": "function", + "ctxName": "component$", + "captures": false, + "loc": [243, 1134] + }, + { + "origin": "routes/about/index.tsx", + "name": "s_WOcPLNnm2is", + "entry": "routes/about/index.tsx_entry_about", + "displayName": "index.tsx_about_component_useStyles", + "hash": "WOcPLNnm2is", + "canonicalFilename": "index.tsx_about_component_useStyles_WOcPLNnm2is", + "path": "routes/about", + "extension": "js", + "parent": "s_m7u9ARcfDGU", + "ctxKind": "function", + "ctxName": "useStyles$", + "captures": false, + "loc": [167, 766] + }, + { + "origin": "routes/about/index.tsx", + "name": "s_m7u9ARcfDGU", + "entry": "routes/about/index.tsx_entry_about", + "displayName": "index.tsx_about_component", + "hash": "m7u9ARcfDGU", + "canonicalFilename": "index.tsx_about_component_m7u9ARcfDGU", + "path": "routes/about", + "extension": "js", + "parent": null, + "ctxKind": "function", + "ctxName": "component$", + "captures": false, + "loc": [146, 1688] + }, + { + "origin": "routes/layout.tsx", + "name": "s_MOLFIZOhXmE", + "entry": "routes/layout.tsx_entry_layout", + "displayName": "layout.tsx_layout_component_useStyles", + "hash": "MOLFIZOhXmE", + "canonicalFilename": "layout.tsx_layout_component_useStyles_MOLFIZOhXmE", + "path": "routes", + "extension": "js", + "parent": "s_SHtFir1Ia94", + "ctxKind": "function", + "ctxName": "useStyles$", + "captures": false, + "loc": [190, 957] + }, + { + "origin": "routes/layout.tsx", + "name": "s_eevMxFvmCM8", + "entry": "routes/layout.tsx_entry_layout", + "displayName": "layout.tsx_layout_component_div_header_div_label_input_bind_checked", + "hash": "eevMxFvmCM8", + "canonicalFilename": "layout.tsx_layout_component_div_header_div_label_input_bind_checked_eevMxFvmCM8", + "path": "routes", "extension": "js", - "parent": "App_component_jn5XSz7NZ88", + "parent": "s_SHtFir1Ia94", "ctxKind": "eventHandler", - "ctxName": "onClick$", + "ctxName": "onInput$", "captures": true, - "loc": [3289, 3312] + "loc": [0, 0] }, { - "origin": "components/app/app.tsx", - "name": "App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI", - "entry": null, - "displayName": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_5", - "hash": "rpMfjSetIeI", - "canonicalFilename": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI", - "path": "components/app", + "origin": "routes/layout.tsx", + "name": "s_SHtFir1Ia94", + "entry": "routes/layout.tsx_entry_layout", + "displayName": "layout.tsx_layout_component", + "hash": "SHtFir1Ia94", + "canonicalFilename": "layout.tsx_layout_component_SHtFir1Ia94", + "path": "routes", + "extension": "js", + "parent": null, + "ctxKind": "function", + "ctxName": "component$", + "captures": false, + "loc": [169, 1737] + }, + { + "origin": "routes/index.tsx", + "name": "s_vG0UuU4cNCg", + "entry": "routes/index.tsx_entry_routes", + "displayName": "index.tsx_routes_component", + "hash": "vG0UuU4cNCg", + "canonicalFilename": "index.tsx_routes_component_vG0UuU4cNCg", + "path": "routes", + "extension": "js", + "parent": null, + "ctxKind": "function", + "ctxName": "component$", + "captures": false, + "loc": [395, 1763] + }, + { + "origin": "routes/index.tsx", + "name": "s_ep3t0fF0SDA", + "entry": "routes/index.tsx_entry_routes", + "displayName": "index.tsx_routes_component_handleClick", + "hash": "ep3t0fF0SDA", + "canonicalFilename": "index.tsx_routes_component_handleClick_ep3t0fF0SDA", + "path": "routes", + "extension": "js", + "parent": "s_vG0UuU4cNCg", + "ctxKind": "function", + "ctxName": "$", + "captures": true, + "loc": [956, 1095] + }, + { + "origin": "routes/index.tsx", + "name": "s_99K9SAWjPFQ", + "entry": "routes/index.tsx_entry_routes", + "displayName": "index.tsx_routes_component_useTask", + "hash": "99K9SAWjPFQ", + "canonicalFilename": "index.tsx_routes_component_useTask_99K9SAWjPFQ", + "path": "routes", + "extension": "js", + "parent": "s_vG0UuU4cNCg", + "ctxKind": "function", + "ctxName": "useTask$", + "captures": true, + "loc": [876, 927] + }, + { + "origin": "routes/index.tsx", + "name": "s_Iyy38y0K3Hw", + "entry": "routes/index.tsx_entry_routes", + "displayName": "index.tsx_routes_component_useStyles", + "hash": "Iyy38y0K3Hw", + "canonicalFilename": "index.tsx_routes_component_useStyles_Iyy38y0K3Hw", + "path": "routes", "extension": "js", - "parent": "App_component_jn5XSz7NZ88", + "parent": "s_vG0UuU4cNCg", + "ctxKind": "function", + "ctxName": "useStyles$", + "captures": false, + "loc": [416, 645] + }, + { + "origin": "routes/index.tsx", + "name": "s_BjxcCeNQ9ak", + "entry": "routes/index.tsx_entry_routes", + "displayName": "index.tsx_routes_component_div_button_onClick", + "hash": "BjxcCeNQ9ak", + "canonicalFilename": "index.tsx_routes_component_div_button_onClick_BjxcCeNQ9ak", + "path": "routes", + "extension": "js", + "parent": "s_vG0UuU4cNCg", "ctxKind": "eventHandler", "ctxName": "onClick$", "captures": true, - "loc": [3550, 3842] + "loc": [1706, 1725] + }, + { + "origin": "routes/index.tsx", + "name": "s_HEFxKy9cwuk", + "entry": "routes/index.tsx_entry_routes", + "displayName": "index.tsx_routes_component_useVisibleTask", + "hash": "HEFxKy9cwuk", + "canonicalFilename": "index.tsx_routes_component_useVisibleTask_HEFxKy9cwuk", + "path": "routes", + "extension": "js", + "parent": "s_vG0UuU4cNCg", + "ctxKind": "function", + "ctxName": "useVisibleTask$", + "captures": true, + "loc": [731, 861] + }, + { + "origin": "routes/form/index.tsx", + "name": "s_0pasaG6nmEA", + "entry": "routes/form/index.tsx_entry_form", + "displayName": "index.tsx_form_component_useStyles", + "hash": "0pasaG6nmEA", + "canonicalFilename": "index.tsx_form_component_useStyles_0pasaG6nmEA", + "path": "routes/form", + "extension": "js", + "parent": "s_ds9jIPT1g9s", + "ctxKind": "function", + "ctxName": "useStyles$", + "captures": false, + "loc": [445, 2071] + }, + { + "origin": "routes/form/index.tsx", + "name": "s_ds9jIPT1g9s", + "entry": "routes/form/index.tsx_entry_form", + "displayName": "index.tsx_form_component", + "hash": "ds9jIPT1g9s", + "canonicalFilename": "index.tsx_form_component_ds9jIPT1g9s", + "path": "routes/form", + "extension": "js", + "parent": null, + "ctxKind": "function", + "ctxName": "component$", + "captures": false, + "loc": [424, 3240] } ], "bundles": { "build/root.js": { "exports": ["default"], - "facadeModuleId": "/starters/apps/perf.prod/src/root.tsx", + "facadeModuleId": "/src/root.tsx", + "isDynamicEntry": false, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": ["/src/global.css", "/src/root.tsx"], + "name": "root", + "type": "chunk", + "dynamicImports": ["build/root.tsx_root_component_9PcKHFjikV0.js"], + "fileName": "build/root.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/preload-helper.js": ["_"], + "build/core.js": ["c", "q"], + "build/preloader.js": [] + }, + "imports": ["build/preload-helper.js", "build/core.js", "build/preloader.js"], + "modules": { + "/src/global.css": { + "code": "", + "originalLength": 0, + "removedExports": [], + "renderedExports": [], + "renderedLength": 0 + }, + "/src/root.tsx": { + "code": "", + "originalLength": 677, + "removedExports": [], + "renderedExports": ["default"], + "renderedLength": 47088 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { c as componentQrl, q as qrl } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/src/root.tsx\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/root.tsx\");\nconst root = /* @__PURE__ */ componentQrl(/* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./root.tsx_root_component_9PcKHFjikV0.js\"), true ? [] : void 0), \"s_9PcKHFjikV0\"));\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"Iw+aD0bt09LeKb23hnLMCtEbnhz54bG6HTbCjdohhBYLmNJUrITYo1ipOYzZDcphGSq3DubWQUh5bZ6KNzz4cg==sMLgP12JN/TNBNOzF5hKUNhCNuKIPaMjvU8uu7iCFJXGlqIQsPXTP/GRM1VwRUpTif2vanQVwyl1MU2EqRfOlQ==Uj2AvdwDdE6PPiO+uHfo4MbCnPYHiDwEmhQT4MiOQjvIjFDniUSqoq5iA51VVzmw/SEjQdQHK5XuiePFcr2Z6A==S1PNA/kzp8f0S4cD7HbDLk8EF0oiwue/uZC3R0wo/Ce12O61v/xQ9U+ZviJeOwkqx9D9Ytpxsd0it7TBn72Q4Q==/2fVMhl4yFRzH6xErcCshEXN2MkNoTNNniBQvzZf9rkafxVyjpPtqj8HPCScUqUmH3AgK4+9BCSEx0UGiFA0zQ==02pofPVCZT/nNRAIqsCEfdNF1iJtvnsNjbxWrpFEM+xWHVVuJWrQ38P1U85wkd2Gvo6+OMuwdkemQ8fhXqK9uw==tcrwGWX2OZQJJ/wOgJfTSUpaFOFDr+c7+BTHOoIM+gY5+RU7QdpqARvUnhYUsBuhn9eC22TOJJqmXeHdvfJi7Q==5kCMwHtPwjSXKVSfmZbiohwljlSwOP5wSC3qfMyZU8av/DIMbTWD6a7Ap7ENqVtI+bMyAalSZmnH0yvUIXkP9A==SWf7F70EUZjcEMFzFnjJHGiOISp5tdKoWYDHOudg2jDOJJxzHnfCGy8GuwXInastxu+vbiZdRwv0+CozKTUFmQ==k6ho73MLsYMj2ATchfbu+jzRycpoOJEgnmcyrob4NFrZVBQHKDXPOWegAPhkEkxPWcwSdAgadhkbAhskuosDdg==gyZjSPnvYmUOgr8cgVQJGzNY8BrFX3algThMj1QqQR9MQ+bhDB6iaFNMk7dk0gwYwqAmtko3NDb7KR2LsAF54w==wNfe3sYI3d/eCv4WeIa8ujowogU3JIqhRLzsiijCK2MeWjVMA77EckAeOQ7excGrPu9pzPyH5rOOSZsz2QKeUg==HzQTha/PcEr1szsCDk2ylhgMHz8xZIsR6dcVT4M7dkUOAv/fJmcDhPZZKVclxORBcJty/NUKH0/ONt/TPAuffg==3a7nWNoXIV7tOdfqO54gNJiCIv9VJBYUIJ5x1/zaDgKYcU2lobmaVEXfVgRbXNS0yHeg4fC/ZM/dMjb2RJkziA==dYWsk4EPWa4OQoWacn0C8P9Tvk2UB4np0fo2VELLkkijiUma+h8aVhIg6jIR9xxr1qXWgHx3Ap7maE8YD3PRVw==V+EdXgR56oQW3hH0Y7QgFHz4aXH0NGClcVGgFlPrrQwjojjHDYKnklPLnx/n5DFo0kik+mzBtw2N1j+2SwGYfw==YFqrVt29c4Vkkmc2KCb6p+PECaygIzNt528XqSemp4SLiZl9gO7ck+B2cX4ghrGACy8PaxoNsbO00UP55yvYWw==A/UddQYcyoJ0UL2p4Cu89PLAIX68Y3QP00PFlqMNPbpK0JMWz4QUERfvW/Wp31yvRS5D8PDWe0tsyA4pzODjYw==V/ZcodrIADYK1o4THF4nwXOGXRVQvxjmOH6PRd4BmY2poHN2SNr3ykAiJdL2XjBaD4lTgqrzFbNc8lVxIwcfIg==04cRwXZVmXfTAULOl7ZsCRlh2bPgzLavZjQArTOkkJj6wFWsxlBi6dmEQyUsruDbUUcGKUFut+Bh2gDjFaSSEw==UPAqmxx2WWViHvg1z0EyKvcP+L+Ravkew4H4EAZ4J8OoNLsHVVZafY34mUujtI0mJNfBtwbQw4fXbDa0VLFWTQ==8Rc7C0Mdrf4IomzZT4I/oxuC1rfGSqml0BXqYDKKbdWVgLBELWUQprVU5pGcynJlIpbeWCM2FtjQJPEhFmq6ew==mUV8aAvkxRfWzyEMlVSB+wQFDwOuetf77ooxsp1Iw1l972bIugS57+mDTpWxYesox6XCF2sIr21c1jKB8a3IYQ==wxP/IXL9c1zbss8wO/io28ksXhUJZWJKavkaL5xywPDY9HLnxNQu3sljOVIHU4+OnCfxKNz734vTCL2nGI3BmA==H6Z57WSi/VgZmevj8/GkMTzylG9HyXGUMgV2Wh/3UasbksL7gYqhw/lAI15p33/1WermVAo9yP93mQSl6wdiZw==XdYsKh/zs11zrfiVfXR5GxB4/NRlOUxnzAx/vJBu+L1GVJ5eH4vzd4FnpiZDmjBQAgfpy7Car+dPc7j8Y7pFlQ==TFrFpPRDPw8qXXlcXYm7zTdjbfk4W8uemWzDSYXfAKs6jCnVuUwjSJoybswfUzmaXGNz04HKP7x/Oq5Mtnl/KA==JH9dc+fuqunINX6VjHOSxaHqNUWFYjO0Qfggbr581a7fSrmDPZudjVl/O0gSnjbX3XJpH7FlSP8MawL49n/+fw==fM3lHwgaW0ehUnbRqXFTht2P7PHk3j/KT3VBpUD3P/nEOjhmE9eeTiFs7d8wY5w+eAMFn0fXyy0Y5oPq+sia+A==e3D3KPyraOWNb2xDg1tE4osxQMY/RLkvJ+mv1BUYggLx9mdKeJebiElFy7oswNtlYe3gOLWh4P9pgxzoSniknQ==kGhxqIvQLAXcqK3m4URgqSviQCl9oEE8ht8KBo8hACv1GM43o7FI3lvl4A3cN4OioDqTzgClUVH6S4YqspML0Q==fWwbpw82Gr/vipfB+l/UZ396M1l781Zgka4hEOhlfBi9odRoVor8mRPpz5jFWb5nZr36l7kyckvhhcszRViNkw==rwX/15XcnOTO9prUbTKwhIz2HzbT0iCHTYpfWbtUvEJu2PdVO8S+Kld9EdKhLV9P0mtcnOLCRrwdG/nVbah2lw==kNJq2pj95v9YgEZAkZZZ3NR+h7hciQQBS0AaKivLtYonzxzVpg+2LxD58zlIAGPjOZw8/MIsFsi85J7ZiY5tlQ==N4NBbTtTQn/7l0say8Gch6xBsOQcuHrgfExYsZ6v9bGS/dUcBqKLCpt0xRsB505xdpGoH4okjDIm80ztFBWqrg==AlNyk+iJk5tT3VF3MlYDW2fM53SENhan6FlZSu+Pco7hkRUF6GlEo9JpG717OH/NOoEido4vXde51O/8yhA+1w==i6dXSJuPlD1TN4kqITiU5s6fVKEBmJHVHBkDosaNUR+06QqT3U4Pu+MF4d92ceOd+VErJEh3cPbe8w5EMY+KbA==RkQygwCN3ZFsqXVejvS5YJUcns5/0fHXjB3OZxQ3WyAawykabA2CFaRtsbrrTK77NCs6yKqUktuvgAD63xU2mw==JlQ8rn5E3BLaDJt8ZJ/BFX0RBeSjI0nr5mW33eDlX5EsbQF370Rjya7WIqStuoq/U7o3XJbIyl114/wOKO6Y6g==aaap7jjKfAf7fcma4vvbmLWNUVAO6M7iAP88vOwPpXXSn+sEOHAv8XmBR7lecWumCSYTmTHyupH3lyozJToFVg==B8lJgPC8dcyhX6orFXc/yUOp4jb7do0kBf0MtOCbFbOzQx3SpTmJQPxuHLAp0s4SUYkjaHLJ3Gxv+SdbuQ/rgA==mg62d2bS01Prsfi9WLKMkUvwymVIZO47CdN4s31qNYlDnKMGN/hBmmoqU/ZVbf3BDpSXu7gdzn73EAuQ7+SsDQ==R2EWy1jIV4DiP0/GhUPEPnhj+ZnGWq7VDApOpHfYpT35U2RQbSJ3FzcZVCMQMXxRag4FD/hFQ4oNsUfPXN2h3g==0bOMlhegCGwCWZgFFWBCAA5oLpgoL0ApoQZGAGwFWlVdHpAV4bNGOrwq5NL9dkEuh0kSNV+jIlbv5iyz+nnikw==F4AxZWSbbtXaTPiWRgIpuAtA/7ZN6h7RJNnqi8BGB2zScq0apXIx5bUGCAWBEcaIVXnSoLeloW2jmUVR1bV9SA==4oQf870GcyRnhywn3RQ10LgMSSJd0v2wI192Qx8RTZfQNTKzv/3QxvnpVA+m7irH7Dd8O0W4iX+6Gjbq39yWRg==CHRkuSso1vEE0xr+Fsi+FhKMxpNspHaHVqN/tnQ+yICFQPhi+1ProPiXpyB2w3QqhiYASyF684jIOgyfs4SDgQ==41EJmqjtknusVo5QItLYxyRWpmojGti/w9Gg2KZEbL1tfjU3Z/S9tytj/hg8D1KFILMIr5iSoLC0Sbq+b2Z2PQ==ruli+Zs4J8tXMLhTmeS/zQDdBV+bj3NL+Z5V0a4cOf2e3XTPowTW97Mghq7yNirwGEVLTrJCzhx+CVUbzmPd1g==j2XrTkVvkoyIKixrP25GZmUCw+nVv5dbr0fxGwHP2/+87Fsi8bZ8USvUuzbd", + "map": "", + "preliminaryFileName": "build/root.js", + "sourcemapFileName": null + }, + "build/src-vendor-lib-helper.ts.js": { + "exports": ["h"], + "facadeModuleId": null, "isDynamicEntry": false, + "isEntry": false, + "isImplicitEntry": false, + "moduleIds": ["/src/vendor-lib/helper.ts"], + "name": "/src/vendor-lib/helper.ts", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/src-vendor-lib-helper.ts.js", + "implicitlyLoadedBefore": [], + "importedBindings": {}, + "imports": [], + "modules": { + "/src/vendor-lib/helper.ts": { + "code": "", + "originalLength": 115, + "removedExports": [], + "renderedExports": ["helper"], + "renderedLength": 49353 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nconsole.log(\">>> running\", \"/src/vendor-lib/helper.ts\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/vendor-lib/helper.ts\");\nconst helper = () => {\n return \"Helper function called\";\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"Qt/S0qsVPFS16zQo4QZoneD/1OAoOb14R9otWWdkNjrS65bEmqsxA4rbTQ6u42GhZC1Khc4iTUjqDP3fE7FhQA==jYFCDJQOPv8Z+AzPnojWBW+XGQnwyqPDd63T0E/Q3dwNiWX2SIrObNK15nRkkUg60uQorBlkbvo5YgvZHoM1nA==9Si5DvpLTpBb5eKwPZ0CEqv523wGJffc8l9nEUE4dtgVrUFP33L/mxwjsGJVJ+ouofs07+d8k1V99UFSojxwOg==TVLcx7R7A1bONlLSfE6kWHl52clCWarw0OHR5qb0IpedW4yoIRlOKePOz2fXqbBUTbBznkqKCcqEV5M9inGNLw==+L7Kd71mXC1quR/KYfEyoOcQlwgPXXRlU1Gz6A72xhomP4zStbNcWupAz/fljU7LNc/MBS+YS6yq6KbvCrsDxQ==AnRsIqOCNnJbvkfdiW6YWJXYVywrVxM3cPoOpCpJuDdSIiMG/EyathBZxfNlzjKeRCLijBYLa46a4XSJFctRMQ==7BwJmZQidAa7GDMBn5fMq/GFHh3k58karA13gccsFAJqOk9ZeqjM5Huyr1Y7Mw0uYb3/1azLdQ0jHMFucj/7Wg==3BLkDd4JsiB9iqNxv3b9VDqbV5gTwMnroyH+OnkR+awKIO5Zt+wWy8ddXRRxYlO+j4bw3rjRhWVKo7ckdn6Y5A==Ydmv7j2CyiVykZjFOpwrfA4XLIT6WyTk70nNAHwyhK5vkcmEuGti++VfQ4GQpBMzhzhifO8KVR70bKiYA413+A==h/D+eNFeLHqXHncNcbsk2fgSoTX7idam0cZG0i/rvD8ddvu0ZmNPYr4Z8WIwxeWj1KrVo0KKiBNNtaWz7fXh7Q==C41BLjasCx0BcO3xCsgzeRE0t+kPyBrbU4MBFJoQ86qbVGj3izw2CiTB13dBNFdi0IUvDAyatHYgr3Xj4tI3mw==8TbVWK6BkD4dNOHVMMTB8X6ilEcrul/C2RysOuXyR9EQ1TEf8x5O628BPOtW/pM0//kFjRSjMAVnaY14LkzAhQ==z9lwsz7LcO+pfc4TU+ROZoPXpmZ00t2G3UPbCY6nZcmgN57X/Bt9cwHfahfql1AB8YHLOo4/Klaf8W/smAkr5A==vhZxVyWFeSJZWZmopcrQfKRhSo2zXLbfagl4kl3q2QI8atlYOT1YPTUVhP03csD3j/Tkh7945Z0kPq7jHeyLwg==kS1LE2+IgJJtarB1koAyjjxgOxRDFH+1zx4pFYETFh7dNSmr2kCrGHqzzULEbtBPFy5vDoT2R68dVFkKlKJfjg==7x+xwgmii266sWU9Je4/MBeWegHLMmttL0LHbzDwtOa8NM9IWCUgRiTcAkTgxI89sfPTc7i/UrEEh7CHDq9eng==iptEkXhHbx4qXT2le/vegdjlOYlxvJyS1YksWAlQfTWGDbSTr1yYOZgPK1KoyXnFAgzhhWqmmXWqqamOoMeOow==+tKeYdGlcAhFVzchlLdr74KcW92lIPuUu/fJ9FjbV97+4Kf47UizY9u4I7RQleuWFafiilY6fwpxXM3U6z1L+w==PgkIZrczbelS82m1xWRqICDmYAejmN2bpfG7jodyAAppbe1XofqGMtiZNjWaf5U4+7K5EDFTLLs5/sy2ifIucw==9y4F6YdF6F4tjXTC2+lNX+PhzAVGpqIdfThgKui1W32IMw1islgGAU5+2mnhNK21M+ve9Uw6Bi1I1p36dgha9A==1MwTnVR2sw/Yp11/RFdByZmhu+h/a+9kLrI35v8gqAHkAkCKWyw2040UwfhQ4F+xICfRVSg9dGo1nsvEe5hG0A==3aXegQ76B1IoTslIUTgJYPTThKHbOffuEJrKALhKj4ckiF0f5xyZKoUgorKPJfExAhwf8Rc2GQ5A+4HuacO5qw==tZRmSIMjOAPZbym1PFc1se33x1m8SVOh4/BGsnA5BTtRRpaKt6DbWnhuYj8q06s8+RqspK/b9HpcWX6sE+9jMA==+C3Fg+aGlt1POHY0S5b4ZIQU4Uq79fadTJ7wKvE8k4T3GIuckwoyPq3asEUeYczu9UbQ4VDnav1qwLXz7/9wwg==ruGFpYdYJRxZF4Ts01pS7SxttaGY6/KT8rVd52/V6qyOhDOxadWVZvNANbxFzT8+iytj0olhfPekANAFLqMo2g==7aIsQZ3hFruZ7CYLHXNCQbl7kq8puARvUr1+E4ukV/EEDbkgsUhJTVXjFBowLsb47eULfSUek2QEBvP1sDb+Hg==1tn9kUZffRawFAyY1Ma2fsvYWUL/BFhgz9OaNa3+WbKLA/QeUvkl2m4a82gueVZ9qjBGBU75X0DtStel4Br8iA==NlU7HLmZnrkX8Ubo1magd6LRl00qtqFMzd7buijSwE1tkPAy2hOB0F5IgLLRS4vgs1wTkNIsl1XQJI1GxkDpPA==r7MQUKM8sShX6Q9e5rUeRZbqmcJo13GKtSaVT8F5fJa7rjhny+AFve93a5vjlIJV/WpZhYt4yQAQKRm0eb9twQ==oWmVmNqFNszFun1VJiUAux8q3SI+PmusobbBVauQOCSH8+ZdIe2roEmumuET8UjN81ldHOwdIvhRikH+067dJQ==EVRRN2SBwzuUpkBaQ+VsFw7Hk6Zp+9wMVtNDZ/hh15x730YyG+lTudENhWzeEYsujl/Ef6GHc0Q49GJfChBRGg==f5X8tUVxpD/Z6IEm44xg+8fizyCYKn0dvbjfS1cQp2WcdAfJIQHHsSIdEsL4rO2Wt/3sDlMcQeCoaSNUCCrCfA==ruu3YiPcITXtzvy0r4/nj96BpqP5vtw72RdKdnEDkfWyxjOvQEB8uNiGGh8+Dw6xMdz/85GpziNG+ofv7Efycg==1NbC8h4Ld5SHDPwC4vDK74SCRKQmzcEiygpCuulycoX24f/sGp9OYsZpJPLugRzPNQimarIJhClsTsoRk0zQWQ==YIvNvEoWVLZEKHov9reKZ1R/1aCwonM/F4/OuODOzeJWg0oe/o1zCotIGUg4HI7s4L6QvFyZ1PXTQJsXKvIFfw==PZ+LOvQ+eNm/ZDvyB6wzwzkGr1RCh8tFEbWhswMXqA8GjI3falsnIlU0Dvh/CTf91khvL/P8SjQog5kn88EvCg==588LF+VAVORVJ9DqaYcTFaS9CaGAUug10XfO/MoKtSN6w1GPiVjALHmsO75PvBj1I2UkOoLeahF8zpeCKZpJzw==fuWbGHHX2UaWETLmCw1z3/h8HVV2W/1u6l5nQ5lbAJwc/K0960aXf9qf8RPMpHv3L9jWgWls22EATmUcFT4Big==+GhVaMCO1eLNSRBKP7FHNQX86+hV3cjPgpoHJcI+49MB4VEZj2BpJE23zPIlpFbl/fMPYIM6AKJz1Jv1f2gbGw==TnlVaY2xhp+wAbtuNxWlhI19A3kQctGztk+oFmkVEMpeN/ELJCDFpimKgG22gLd2uX0CPmvXNOYJKXsQ7a93kQ==/5z7ruWlV9LQHfON9+hQQLznnsNmHt6tl4IYG8iH59mhfu2VyrygGGzQ9Yh9OjtLurjmstmdRlHCO51oJ4oOhw==UchUKni2OBBp6wL3MAE3Hhj/tKdDxiz17kBI0H9Bo3a0zSJnGhrqAlFmjrtcdivkazzCthNyDJVTgBdXYh6KKA==w8+GuJu2Mhm/eQzLGjmb8k6tF/nbHarijDd4pRHYr68vBGHn9K2KQhD982UJseVaUqGgdlGP2qdRASWvyk+I5A==nEno46JE/sxRFpqgDX2RDMoPmTTJddtgQOxaDxuhzf0ziK1YGgYF9H88X5/xVGLgBFb+vIF0ym3Cw746czaGUw==RQg2iSEQVTGp0o+EeF3IGxK1fogbarB3414EOfRTJpZ/hhLiJEMJTspnEvtVAMxW7dqbH/izy6mOhEoMZp2B/g==MsMk6mtVXZTW5Viapkd4h8bust0gIP3tH9UcyebzPGLM0fsiZNkIPTytk0SRYmUR7sCMVKo7ZThMi/Y/0VRFfA==xgnVDIne5kLax4m0o8bUknQPtRRgtjeKsJyobfFDEvHO5cyzNdfiNZh9mrfcDo6Aemmx16C588XRnUVZbAp+XQ==+qLgG0nO8FvSA/BfzCxISAAb9hKCNOVigkqGC3M3z74I3oBDnSTkEGYZ4H/Hi/viSbHzsAb+5Hpe7VO5DzrcmA==WRwjyouyoThaKulqQ4pyy3SFBVFo0J21KKjdGqiz63zpWKJTNfSvmgEmmgJJyac17u6dNWpvmipgDHRtitVXvA==47fzisQD3Pn63DOZkxNKHbif5btXWIiZuFvEesZ3v9mLjfINycTlN9gLcwoSUjqB9p0DL+J3oMzQgFSIEwaDyA==B3PeiKMFcPys/7vaCyW/6PLhZX606S7N+4xd9LC3nJCcudLgdlI6DPzGxnMSZ3An8CWCHeKx0Y52mU2kHq+WkQ==8GNrMC+ju8uNzByBAxgiZwJGWmOj+pItxXC8eNnTumgi7BJeggWsntV8Hk/+X8jYb38Hw8vDwmdR5r4kpkzy4A==rKRusbljvhCSZKtdBtf7ctm9KRiWf+E", + "map": "", + "preliminaryFileName": "build/src-vendor-lib-helper.ts.js", + "sourcemapFileName": null + }, + "build/src-vendor-lib-libA.ts.js": { + "exports": ["getMessage"], + "facadeModuleId": "/src/vendor-lib/libA.ts", + "isDynamicEntry": true, + "isEntry": false, + "isImplicitEntry": false, + "moduleIds": ["/src/vendor-lib/libA.ts"], + "name": "/src/vendor-lib/libA.ts", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/src-vendor-lib-libA.ts.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/src-vendor-lib-helper.ts.js": ["h"] + }, + "imports": ["build/src-vendor-lib-helper.ts.js"], + "modules": { + "/src/vendor-lib/libA.ts": { + "code": "", + "originalLength": 149, + "removedExports": [], + "renderedExports": ["getMessage"], + "renderedLength": 426 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { h as helper } from \"./src-vendor-lib-helper.ts.js\";\nconsole.log(\">>> running\", \"/src/vendor-lib/libA.ts\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/vendor-lib/libA.ts\");\nconst getMessage = () => {\n return `Message from LibA: ${helper()}`;\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"gaItPpIA6WW4cLZy9ZnddZNqpOSy9U7Wmh+DI6Wb/Sp+PTRJRadQgzVoyEKQ0fWeeR02gc8JLPdnIWbJGt5YaQ==\");\nexport {\n getMessage\n};\n", + "map": "", + "preliminaryFileName": "build/src-vendor-lib-libA.ts.js", + "sourcemapFileName": null + }, + "build/src-vendor-lib-libB.ts.js": { + "exports": ["getMessage"], + "facadeModuleId": "/src/vendor-lib/libB.ts", + "isDynamicEntry": true, + "isEntry": false, + "isImplicitEntry": false, + "moduleIds": ["/src/vendor-lib/libB.ts"], + "name": "/src/vendor-lib/libB.ts", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/src-vendor-lib-libB.ts.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/src-vendor-lib-helper.ts.js": ["h"] + }, + "imports": ["build/src-vendor-lib-helper.ts.js"], + "modules": { + "/src/vendor-lib/libB.ts": { + "code": "", + "originalLength": 149, + "removedExports": [], + "renderedExports": ["getMessage"], + "renderedLength": 25066 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { h as helper } from \"./src-vendor-lib-helper.ts.js\";\nconsole.log(\">>> running\", \"/src/vendor-lib/libB.ts\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/vendor-lib/libB.ts\");\nconst getMessage = () => {\n return `Message from LibB: ${helper()}`;\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"7anchBsC2ObrMyHY0W5oIEJFSPQEOHO3II45CJ5su0PP7TwuWvxtStUvok5xsuQ+I9gvQPFgFShdTmRQparvbA==Gkm+3O/UDwYGu2snruuE8JCqk8v/hUxWC5SssVtUkP+8gIuEYajH722x+cuoIqCBy0VLCCr3goM2LkBN2CVxoQ==ivZ83ta2Hn/bh8Ua1Cd3J+PA0oZHIrBcGadkCpCzbTbgNEpWwFUOI2r2cfpUCNb/BBiJs0maBqc88BrE9vUrZg==MLc+ImXso2oX515hAkwr/kYWT336Jo7hwxPOH4lZE5E5X5d1U9JFSTqLZ4lIQNJaMV2viZi22c1FKwZ7SIqxMQ==sP5TSIdodQaiZN3wBT/fs/1ufzYKAuJF/milL6RgpBNhads28AEepSc/4cpNjTW65CotvYdOrZYPO3PuBA/zLg==iswQ2n2JIstTmCIAUZd2W552JsJauAIErbj9DzrWha+ewrQQVUvFzKMjEo34D3R8mzOTViQOE7Luo4WsrEDdlg==MVjTpWEKT3QNgVCCHVQcVh1dksxtn+0wMCDk2MdMoiMzKDtT8o8lzZ3o6cEYdiqRMnXUvOhkwZ7PX1XZ1rVQkw==hAQLmLKV9k6LfFCL7fzJ8PQNTzZtfppFIj7KBGDOLRxERPK3EH8ObMBxzuD3TEqXuMlFAYVRjTdY8qVswLa7Mw==vIcoGcPSpkO0h7Blh2TIOlCuUMUX96dVqeWMLHhbPFy2EAyqM73Ab40b3RGVot5TSnF2XkkbxD6/D518MjnSSA==kBvbGRUZuZUIQwKRNzaCSoeubYJmMgSLRN8ZWmoI7Z70fPkXvkBIFdHBQG3DwhdG5O7e1oHHUNFLXiTF9v71BQ==hBblbBDVJuMAWEZml1bbfbGhTMTD1TXOIByNQCgNEgDpKO//7ZXmvEI/d/hIaLtIaL5h5M4K3VHaioCvhxqzrw==80FREGul74N37MdWcEEfKNW0hsAYPk/qF3IdOPZJP4nQ6G2bPhNVSdlfsVMJrKow0xrEkWOm4bzNq0z4Je0jcA==vbBuYYeQDmSnelmE+IHq5tfIvBWkuVx3Ze9iF5ahp2xF3nSwLfA3uQpLVAzdWq8TO79iHTdTgjacOZa7UtJLbQ==9/zFj+g52P20GwD4n0b0uknFdZoMXji9bSqPcnqBEVs5EM/YNECVmoW6UQ5+dx93PDRmFRVIZucGingtT7UA2A==aYhXw8cJjPaQbvDOtZseQmjiIxzm1ln3RyVLEIHNbvEhGK59U2QliaBrmDj1yR4D7YgC2KEvZQw1gHDlvmlhSg==odcMjKojglcgEn/hH1Q2l23wlGf4AWXZekaUNdR8JuiNMW5VYencNVN/6/1LZHzRiBBFKCAxWxjIHS49EkxowQ==nHuKjm8s2q0/GlmM5zBvcKIf6ly4BN2grDXkvRm9LC107iETvBZZbBNjL25Kad5OZrcxcL263QMf4u59ISlOrQ==VlDCcsmxCZIbLwao7uiGdaAbFek5oBKqR7Kq3xUF47hyff4Gfxmvsr9uWwm5O1QYuhYBJhTnJHaBj1h5faYmEA==vNSTPpe0thG6Mn0N9qAp/mpd+EG2e5LPssWv0tX0busGFWgmda+CDLlClo/QwoUdiKTmolA+se4x/7DKM6Phog==MIHe66ygVPWQd7T/woB5E5EbvP49fzX+PDCfJiq50GBaK9Jlf9usIQ6rkASCa2YUgrXjhNBsr2RpzhmidwGtPA==r5aWrSABy+ucEF8ANLMsFJ8w3uuK+FfMYLbgf3XSsvBldjMK1Cwxsv3vQHNCQOi9R4JLj2iBGrRT5rwtJuH1gA==lXSTJElW2E2sPxKewi1V2Z1R2gEoF1hyyjH+jdf+8gxqxRldjzlSBvwDQIojA3PDkTB2Dtqt0kjGLWO76f3a3A==1Mpnzk5yPev3GSkysDpoJC16h4KHZcQ0CTmvy6r+tVQiN0N6jeiA6v/CxLqWJPWAeVdrFIw2qiyL7kYrRz+VQQ==FlDYGJcNc+aML/zVMuCprhQb4B4PMK3faZz75pFdFWPhSgzSKTSASCWJ9W4y2WNaNKp40ghxWVAoi0MDFR9mEQ==LswtzrhlI8OYrcxLdM2+RPcBM+8xm/6d2elm2nrLV3M+EnwIGfprFV0srCbCXWxPhF5vf+rP+O9GedVCJ6PVag==U7RLMCmP0cmbh2cj3SpDN1JbYN4gzRZr0xhsjuLmdiwALDS/cwczRWhwf+M3N8meOYA48LBg0iAcw8kk0DUR7Q==tM3HeFjer8AXMCa9n4j5JYIs+tGCLS95U52+rs5g/oMDq7VM0/3cTgeSt3enNCNTcQxADgpoM0F1w841uf7wzA==3YOOcbQxYXYjwui9hT8KU45XLsJNqOju+owwOLECfQ7FA1u7At1B50UGE6iaVlQQncBZlovApTw/p0i3vF/pog==47+dNOdggacCiDdya2KXWebMuz3VMJEbHTi2YXd9oV7+55ESaW8NOwdJarJqrWdwbYshGMuFJ1pSfTWzWLdtyw==VugWAFL2deKAnFxwq34ywrGFYTbO3PV8vglm9AVr6uEwSuXnMEo1bjzWmNLTOtDahU+f1tF9ZPlBpQqJQVuoGw==5j5P+3Jhh6krmpSW012inrffNuy/t173hHCKvlTqbEDjmP9EZ5rJHe8x0zeScwOGZ1VuR51vnyllTreBz3PD3A==d9+WgKwC4eHGwuT3WVQ6Bg3TfRaAJrSFzabXByDJ0ENg9lLPRZC/jFBwP/5wW2LfSZTOsOv0nHOBDbrLesPmJA==2DwXLKqJL7EAT/KxQdRFtLjP6vwsm3ba9Go6UmQxCOt20I5YYEBM75RBJlEc7m5yS5UyYC2bepgLwZl2RwUTaw==aLuJs0kLi+TzR5ayP3TrWH++/cvm10AsEFwA9Hlf4HkkwbKa7QvP4M+onoUEqzxZd5Sgp4xZG16hZjD1HQSi8Q==4rXgVvNNEYmXCWHimRkPgB2WsJA+Za3wrNQLrXh1SLrc9wZpX2lI97YqMP9bbPPabMMfRfgAstuWx6CFcWkJ7w==Em8mE9Q5AQ+rT2SCxIclwLDtMmFjz/zvg0Cj8otrfv8C8SzkVrjbReE9vgL1D1clBuP4CYycGxqnn/icFdoiaQ==bnuF2i81u2WYgt1D11nNiMbCdajuS30+qR8uKKfwLp/ChHKHdPnl4ALlhWUdrCfcWqkIMQVel+cZVuGZdp+HRw==h06G3ByGiNJUQ/T78NDVtrZwHQQ2KYjaCWXUotJSMEjEUkYKC17GGLEy+xkg+LRzujygRn7TzYGJCKoVlX6oYg==nxMZjpHR1C21RsOWvlhf/T+DDXqD1quP+b3hlyq6mZ6c+SX3aYp0ZyklFZMGyKVmNSlc3fnKzQLzD8+Ql7bgWw==QcXcVD178G0DBgMFoJtufKpHt1wGyi3XIE46hDFIRTEuDyRGp/kBAY1kYag5QhrJ6XRlc8FkyIDjL5OKQRP/XA==tOSyVWL3dUcl3sCPjfjl8z59HQ1ivXr3/+b6PTDJdRcm/X7c3hLVWnPK//8bgUUcex/w5YCs4OCY8mKTarwuYQ==wIJd2mfFEwTHKXrokQZFPyA3TQWlmdqQeH/969AS5n9TNBqfZN89fgLRTCkce2yAgnQkecoEzaMIW+U2LxaQ6A==VwtJzKDr+wo/AhfqkMrIxbsSS5LqW7hu/lyUjHZGmsD0yKkvqZYvnNX8zz8en/V0GHtw2JKefODmUBT44UGweA==D8dmkKrv2G3SuU7nsI8iy3u1aBhrx9PCfQnG5qxWq67iIt2mDE0V3KCe3YcxpITJZ5RQdk03HWeuSm/O0/lugQ==DUe/LXbWJt50FgP9MiqBO7MZ2mBYZwEs2QPVZjtEQJG5a9wbvwAHJidh/3Qy0TvPQaCTWPGG67yUY6UrHDU+yw==m4ZSZbX5XtE4fdUfFPgnW33tqirOyN0j3ntIzFxoTsNrwGQCUbSKKeY8xWc6/GLGaQr7lOq18DHF0z+eYUhQeA==q/TIdu/1LXn7cQF3+ZFtJgkjnEzitdTPjmkjYVRhw9dLVkodAOQbOh7z/R91xeeQ7LHL/9U8A6KFpa3qVYDcIA==X9NE/KCYuOWcUjpf4rP256YnhV4QNu5FlNlMwu6pdwXSllinndig4esOLvoDAaP1iBpfpUzXlx7TlJ8dzyLfVg==4AiugPm25saBZcnHZNlSxwH8VQTBIMUA6LGvBwPqVJYEHuUUmTA9bRHXPWiYWVjUxaSYKOAkNpiob6Durns1Bw==X6KubEhaU/5zjMjRS0u4aq+oueUFQI7A2PVN8FMZmNB8hlbWBqWVyQybWPu3HOsdBQuXo4V2ELpa4uM5FKSD3Q==dTF4ZgJOlUHqVK6CsLxWNvUzzdoBV2KG4o7oZ56xp7s0YGhLJ1LSPVsZsWYWj6XGExVjzr4ikxLdOmAMySOi3A==NnxCskU+orQbm0UNg9W0SVER2zQ49+32NbY96nBnxGxKZrDWBy", + "map": "", + "preliminaryFileName": "build/src-vendor-lib-libB.ts.js", + "sourcemapFileName": null + }, + "build/preloader.js": { + "exports": ["i", "l", "p"], + "facadeModuleId": "/qwik/packages/qwik/dist/preloader.mjs", + "isDynamicEntry": false, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": ["@builder.io/qwik/build", "/qwik/packages/qwik/dist/preloader.mjs"], + "name": "preloader", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/preloader.js", + "implicitlyLoadedBefore": [], + "importedBindings": {}, + "imports": [], + "modules": { + "@builder.io/qwik/build": { + "code": "", + "originalLength": 116, + "removedExports": ["isBrowser", "isDev"], + "renderedExports": ["isServer"], + "renderedLength": 49 + }, + "/qwik/packages/qwik/dist/preloader.mjs": { + "code": "", + "originalLength": 2839, + "removedExports": [], + "renderedExports": ["l", "p"], + "renderedLength": 16731 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nconst isServer = false;\nconsole.log(\">>> running\", \"/qwik/packages/qwik/dist/preloader.mjs\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik/dist/preloader.mjs\");\nvar BundleImportState = ((e) => (e[e.None = 0] = \"None\", e[e.Low = 1] = \"Low\", e[e.Queued = 2] = \"Queued\", e[e.Loading = 3] = \"Loading\", e[e.Loaded = 4] = \"Loaded\", e))(BundleImportState || {});\nconst bundles = /* @__PURE__ */ new Map();\nlet gotBundleGraph = false;\nconst high = [], low = [];\nlet highCount = 0, lowCount = 0;\nconst loadStart = Date.now(), log = (...e) => {\n console.log(`PL ${Date.now() - loadStart}> hi ${highCount}/${high.length} lo ${lowCount}/${low.length}`, ...e);\n};\nlet base;\nconst doc = document, checkLoaded = (e) => 4 === e.o || (3 === e.o && e.l.every((e2) => bundles.get(e2).o >= 3) ? (e.o = 4, true) : void 0), trigger = () => {\n for (; high.length; ) {\n const e = high.pop();\n preloadOne(e, true);\n }\n for (; highCount + lowCount < 6 && low.length; ) {\n const e = low.pop();\n preloadOne(e);\n }\n if (!high.length && !low.length) {\n const e = [...bundles.values()].filter((e2) => e2.o >= 3), o = e.reduce((e2, o2) => e2 + o2.t, 0), l = e.reduce((e2, o2) => e2 + o2.u, 0);\n log(`done ${e.length} total: ${o}ms waited, ${l}ms loaded`);\n }\n}, rel = doc.createElement(\"link\").relList.supports(\"modulepreload\") ? \"modulepreload\" : \"preload\", preloadOne = (e, o) => {\n if (!checkLoaded(e)) {\n if (e.o < 3) {\n const l = Date.now();\n if (e.t = l - e.i, e.o = 3, e.h) {\n log(`load ${o ? \"high\" : \"low\"} after ${e.t}ms`, e.p);\n const r = doc.createElement(\"link\");\n r.href = e.h, r.rel = rel, o ? highCount++ : lowCount++, r.as = \"script\", r.onload = r.onerror = () => {\n const n = Date.now();\n e.u = n - l, log(`DONE ${e.$ ? \"high\" : \"low\"} ${n - l}ms`, e.p), r.remove(), o ? highCount-- : lowCount--, trigger();\n }, doc.head.appendChild(r);\n }\n }\n e.$ || (e.$ = o), preload(e.l, o), preload(e.B);\n }\n}, makeBundle = (e, o, l) => {\n const r = e.endsWith(\".js\") ? new URL(`${base}${e}`, doc.baseURI).toString() : null;\n return { p: e, h: r, o: 0, l: o, B: l, $: false, i: Date.now(), t: 0, u: 0 };\n}, ensureBundle = (e, o) => {\n let l = bundles.get(e);\n if (!l) {\n if (gotBundleGraph) return;\n l = makeBundle(e, [], []), bundles.set(e, l);\n }\n if (!checkLoaded(l)) return o.includes(l) ? void 0 : l;\n}, parseBundleGraph = (e) => {\n log(`parseBundleGraph ${e.length >> 10}kB`);\n const o = JSON.parse(e);\n let l = 0;\n const r = [...bundles.values()].filter((e2) => e2.o >= 3 && e2.$).reverse();\n for (; l < o.length; ) {\n const e2 = o[l++], r2 = [], n = [];\n let d, t = r2;\n for (; d = o[l], \"number\" == typeof d; ) -1 === d ? t = n : t.push(o[d]), l++;\n if (bundles.has(e2)) {\n const o2 = bundles.get(e2);\n o2.l = r2, o2.B = n;\n } else bundles.set(e2, makeBundle(e2, r2, n));\n }\n log(`parseBundleGraph done ${bundles.size} bundles, will process ${r.length} bundles`), gotBundleGraph = true;\n for (const e2 of r) preload(e2.l, true), preload(e2.B);\n}, preload = (e, o) => {\n if (!base || !e.length) return;\n const l = o ? high : low;\n if (Array.isArray(e)) {\n const o2 = e.map((e2) => ensureBundle(e2, l)).filter(Boolean);\n if (!o2.length) return;\n l.push(...o2.reverse());\n } else {\n const o2 = ensureBundle(e, l);\n if (!o2) return;\n l.push(o2);\n }\n log(\"queue \" + (o ? \"high\" : \"low\"), e), trigger();\n}, loadBundleGraph = (e, o) => {\n !base && (base = e, fetch(`${e}q-bundle-graph-${o}.json`).then((e2) => e2.text()).then((e2) => parseBundleGraph(e2)).catch(console.warn));\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"821rIqpXvbBXSzURRvqCa4rVC6mSV8OCZWULCxL074qffNSdNfoiD+fXyoptcPewnewMuakxi3RR99+9UCzr8g==FZSy7UIoOJ1CGsnzofgAdAB4vfR/N1j5s4WNrF+rv9u+2+abNRM3pXcCVaw5BInTtj2e59VqUNCAAY7fJ5c+NA==uRDetltA3HzeUzTslhvVu7JEichFfojov29DmSod9e3gzcSh1tEG18mA32FpmrpJ6rrJEgHFa1N+1FmE4xLddg==dcUMG84Y+66tm6e13EB8wNrfRaTGAUsyDitvgUzB2gdi2HxgUBcd5b2X/lRY65ASejfDhWGCbNI98Wn8eAZB8g==wxT6Jf7Yt70guP3jgqEqsezgKpaKzaeitWaKUed47bEAqIluBS27j5txmTcKnkAH+7z3pzcVbgI/qco66SsBbA==rjLTgRL8grdFwRVm8ggDaAGNVNUjlV06MmSiFSWNUjgqWyJBgAGYFAUlZ1esm23T7MQ9HhPb9NYzgb7g+D2zZg==TAX+f1Um6OF7jiM+hkE20MtDE9gldPrh/q0AkluDBnkZwZSn9uJln83zfj6uig89ApegK1QZO02p3grMqvek6Q==VOzyHmBVFW8oy7XQKBoMxAq7JJu4DkAk64QRDZjFgzwuuR3n6dwHS6z2TAyNykgoOBOy3Q5JpZ2o7q9S05BKtg==w+fA5oyN9fWcR+4mPPCmeqcaZ5ZcWPjp+IPbd/4Kc+OKmDxyS7byJWtgDhCxB2tYzus8+6X/iuIN+wl/JUVzPw==smB4pyoQsyPHAaf3kAttRvou+/UQdiREu2SKHIi2Dh6+sqrjttPvnqksUACRKY6JjckWNJEdZAqPuumw9WliSw==27L3VptYMUW/nIuszO+N43vmuggreVEt2LjpKI0MCf8s4bhgv9boan9+I0kGhhVu1eY4qw+Rcu0ON1F5dsizcQ==v38VEFXGbzEHgEvxINwi8cUE1JgDPCfqPUFAHiwBbHXXgD+9lfSSAw2Xku/WQX7b3pAb6xmBh2izf4l2cqgqiA==1ufHYFu81UjAk2R4kn9tfrWzdIqr6GSMZZQxGBZvAMPZ+J7zz+/YweYOPDJTA6R1PmqaigQVfap/fUBqSh6WqQ==SN5Mq12kXeU/RkwwF+uoho72X+ZWFwa0bbrVxTAVsDPRJ+NjqxcJ40Vygqni7NHKjFH05fn88pFIUYj", + "map": "", + "preliminaryFileName": "build/preloader.js", + "sourcemapFileName": null + }, + "build/root.tsx_root_component_9PcKHFjikV0.js": { + "exports": ["s_9PcKHFjikV0"], + "facadeModuleId": "/src/root.tsx_root_component_9PcKHFjikV0.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/src/components/router-head/router-head.tsx", + "/src/root.tsx_root_component_9PcKHFjikV0.js" + ], + "name": "root.tsx_root_component_9PcKHFjikV0", + "type": "chunk", + "dynamicImports": ["build/router-head.tsx_RouterHead_component_dAo05yeFq1I.js"], + "fileName": "build/root.tsx_root_component_9PcKHFjikV0.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/qwik-city.js": ["R", "Q"], + "build/preload-helper.js": ["_"], + "build/core.js": ["c", "q", "_", "a"], + "build/preloader.js": [] + }, + "imports": [ + "build/qwik-city.js", + "build/preload-helper.js", + "build/core.js", + "build/preloader.js" + ], + "modules": { + "/src/components/router-head/router-head.tsx": { + "code": "", + "originalLength": 1136, + "removedExports": [], + "renderedExports": ["RouterHead"], + "renderedLength": 14343 + }, + "/src/root.tsx_root_component_9PcKHFjikV0.js": { + "code": "", + "originalLength": 1037, + "removedExports": [], + "renderedExports": ["s_9PcKHFjikV0"], + "renderedLength": 3712 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a, _b;\nimport { R as RouterOutlet, Q as QwikCityProvider } from \"./qwik-city.js\";\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { c as componentQrl, q as qrl, _ as _jsxC, a as _jsxQ } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/src/components/router-head/router-head.tsx\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/components/router-head/router-head.tsx\");\nconst RouterHead = /* @__PURE__ */ componentQrl(/* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./router-head.tsx_RouterHead_component_dAo05yeFq1I.js\"), true ? [] : void 0), \"s_dAo05yeFq1I\"));\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"9JhdgmUnyvKRU463dATo/SoOJxRYY9JZVaZZJOzwYZp8kz3kzJ09H73qqhHlGZCZZKltCFAPRNy1wNLle5VYeg==XxfVm3RZ2kCrQovaJGBUv17HUAgn9QqU3mJ6/Cpq6+zy9s6jsP6/jFImQcfve8JiNDZ16eu0/IUDOJFPj//SHA==MI+9W0QTTOSwhrOEhzYrAG70j8zV7zN3sBzjjHMDGg4RkdsvwSn4Oj5MZV/mL4QUmVbly9lPw5vKhbd5I3Lrgg==q4pRdWvsW7KbXE8Azx9ZX7fLmBk/xFEnxxzc/71QyBeGtXwB1mqfa1whnj+auqcgHlW6wx5MILcT7L5SFkjN5Q==sVEKGcY8JDSunqcfRVarPEBFUUQPDRPPZ6P8ZJChw/Dz4hEiupA6pkP5b8wGOOQjIBWzDRvzNCn3mAW7GYRgXg==nrBPDFNqO5PLDCyZwXkMYgbjssRSCGsKu6+gnAd5W6r/SK2hxYld2lGZBNPAosQMIE8WPdwvn6YtPiFr0z6x6g==0PKyv9omety4zn89uEq6aEaInS0b8dpnq8fGRLP9uZO0qUFrh0sTs0P4kC+Az5wuBpvJMaFlpIknOS7OAWMtWw==SqYkj5p87ZDW+pdPekWZdLzVs0T9cNGOyD2oafuEPu0tIphLeteJm0vlLtvgqkTdYhd77HKsbf7pBynAUVMs5A==XTPla0nfq9ejtNgYdbgHvu/8zQ8mLcmQdr0KZVh57n6lp0EZUj+xdqbOF0PpxA/R7PMUqdPTqe/LqI4YplL5Vg==buv8kUnD3PYmBTjfn4W/KlW2lPZIsBE5ZdoMszmtJjEP/7o6DrXlqEDARJMkqJfM+xMec4iRuaHlnDKut5t2bg==5P2Z1fB2kT3rANkR6XRnJ76Jd/qMMfTIHI8Y2ZSc1W2sSXQ7CUlerpW2mj+WiR3o6UmTJupomEQ2w0pfWs9WYQ==x8xMJSTJJmOIgGhbcOi9+goNNy2O+1ncC/uuHWvtEa2cnixZygBiilNhUe5lo8wkO9rCLxCAReZSoloTwA3r+w==26QjkCjs97KRbBdNKGZJJwOGigTdIUGYCmbhWMKSaZThp1+lIyWUFWNit6ablmHN8RTBe4RumTeoGn2NB6g2KQ==nn0oXF6jk69EQDnjP5B14WUQe6ECWp8MQayXUnLtrHNbdfA9nu9Ent4G0E4IAVl5mMZvAZQFT/KgDMkGlRhojg==Nx+Hlxh9BFXuex6Nmo5rrmsO4M9EJVaGRibIgJbDSYaGE+sPrs2k/ruWdAF5yTEzRMXza+f8+afNOA7epAtCLA==AZv8TTp7aSPgw/6L8hvQLHSKiHoWUWoE9kXGt5tGCuSFbk5VExjAVVJLm4cagG3r6egVjoRBYrSt2NwZ3b3HOw==3TGa6mmAC+OHNhCL4y8Opxv7IxH3GRzdL0OYt9DbVmsnD8WT9/Wufcw01EEse4KFMzHhwgjHaUG1RQGv8cNvfQ==57226fH579CFnXq4btem7JBf6dGuroTvvM80VzwDg5SK/HkLmVpxAo0pdxGqBVr+NUhWwEyhLKeIHbPAh+ZM2g==q58mX4lLEOueLo9c1Sow0x0+6XHlhIQyFOIuU1nibvIiND9xdP+ZS5Ib2oKQ/PWWedED54WpGW6DeIMqhaL7ig==KggTZ+EkmRtpzQAU0ZgyB/G53WmBwYZHmDmUtwgFc5fE8k4zXXoiTnjcG9h6XV8vKUnuEqtVBWBWXf6FURW1cg==Wtyc8l6kReLyzeYsy/jjZaSJeptAP8jdzi7U9VeonclbHL6Ut7rW5KX4tMQkfl/4YD6WX2LJR15GeUtmYRQi3Q==Hnzd1wNZRPii6Z21/UeQRPuxqT07JaR68d9OxrZ3ED0t+AGj+t6xjspEcnQ5BHVz1JCPlLQQJTRKXVck6Dz5dA==NNYs9SaFCF3WUr7P+cE2gJN5Z8tUKXk41AXRfMclIZMVI96iJ3j40IMzEF3Abb9WOgwC6sBJLJVs+dvahhouJA==qRcEhEDVzqVs0z9FVpin2DqyCigfCdE0g0heGENmLb0dnl2FLLE6ui2v8HUvk1eO4d67vkHAtUW3ioR8z073fA==gQVzoH5U6Cgdt3+QI6O/g9AJXX4L5VlPExr73RyPNr88RwW3prBwVXY7dOyG/HsHpsiCSvdmzYJbG7kvaGyY8Q==7H9yNwyw7q2KwlSd9umti70iKLT7grUot9hClYt64ptew1aMPjJpC0x7JSaZ8gzvLQdrrZR/MI2SMifoVGxyYg==Hd9l1T3wNy76vUXgxDpx01fMYm0EFNEAS5EHWQm0bR1UMNtCGV6TyWIS4yu13WqpsOpVcE6hkuaX2KjrE8FNOg==c/L4fmPd70W6mvfMyYoD2aYPgO1xUrJkxBG3dYSXuC4GykRL1DWE18RamLd4G56eZIR+43yZp9Fa1cA+5BYkZw==74MNve2b5vVjEdkGY3MvAeOQNLyrfTdWS2NQ+KF+SCzS+fLiBPlJ3zpBNjvKV/4xBVRpQfpJXpfy47aoh3QGvw==W183+ZQ+M+Pbs4pdRIzt/jv78yw/VcNtULOkUe/EFs42X/YZnFxRko/EOqnzUTKMio2QKQuq4RQ+9EvxTV4AJQ==9cC9k1bQUA7Y2SjRYsABtW7bMvlupW9ixOQzdd7Ol50q+SKSCl96nhbUCUwouWmuKsNmYTxQ3M6rlOkJoViHtw==JkMbKPDIhapOB08U2LJgde+NEPHygILTPfpjbC51bqe6eoUnCWWqZVWOU3QGIsLhu2dUiNj8Vw+EbbLGshQI5A==tHPMSufZpzal2d8agQ0clUH0F2+r+zIRpLMk10vmsK77Jo+aee0DZ0y0IQ/5zjAiZ2Lh6l68DnrtgZbbKSxXmQ==rzIfF2gvUXyjG+IbuDQyhpRrllBaKYjXTyybrWGJrpnw938/jk+fKYxv+zoczn3DdhW9oc+CcK51IrqmFr/80A==zWmoFYFo3a/GjjxxYs9f/lnjsQ51ituCx7NDsc0pLyyX280bO3MbbQkD4+rC0Koj6kETFF4TsT32k6omKUApgQ==pxOUwzD+nrFVANvpC8/QgD8M437GL1MFuVeTTKeu54QAtJ+FFD8HRD1vxyiCjngTlFlOAFrv/dt7TnPuhmIiJw==y3bV+mhG8x3uZF+vcx02vcnOoAtVJhDpO1XyExoqM2sKKnIpaxp/tT+3D7ae4YqjFFWBdtTienx3hXINNqfh1A==LfmR7Xne5v+EYEkKHj9VzgkTSNcRZiR0L3p3M6O6BBBuL+sTX5fPy9UfrXqcKKdcNDozaokLiwLeRVSz/VD6HA==z3sBn0ZBNrAwqtv7AliQ56rOvlPhWC4W5wsAjcklVQSLEBBteBnALmQGW8fNoY9P2hI2MBfGLQa4TiiAjCAMzw==SwBX0WuwKB2P5HBH9YccuWq6xjMSqfPMf+Lv3wwYa481jJzhZFHR2os6Ndq9j1QqoM0ZouU+dbdHz6Y82+R7Sg==FI6531S7kZe0ihUtVf+25EqP3oJBm3HbNUlCDwYirldZL+t5pJsRWt1dnx9wKf1ahOeu6SGVriV0qSTMHmAAlg==q5Kj3+op1MiCufWZcVBGp+EZeiOenHbS+D3GvSEYM/9rEChmbaVDTWOZv1BFFVsAa3f7jPw9eajRxzCScnvZZQ==wH6wnE4kDqI61WHSClrdUz2Xd//dazK5TX8dWg0yPg0IK3jh4HPTXzOLorV2l1DR5ZrS4RcL1YIq1QT7PKc8xA==g1imEiA4orW830q1OzZVY9Ta+9Z0zkEisXAdhFyKGs5vz8qbYGowZEsrRnOn9vs4pW/XZMHnLEOeH5uWIh4v6A==X0bbqaT0Pyv7dor1T4hGWqhfOmN+V4K0m5b3Qh+0LOkwEoq3X88sIcnzdYysVxk1fed2PYnq7l6cUWVozaJbCw==G5bG2vHgatUZNVfMBZKxKjNOfVumloyG6mgXWauiPYxGneQ9Bo2sg/gcIWm7VqKrDvybHrvRcrofsHLmFvhfCA==dTswVm+wfTx/Pwtq2c4XcwptGi4qmF1hmhq4xKwsBndXY+TqGkPsEEXw61KmbcB2wAhmA2GcnGGYS2iDOOq0qg==wCtpx4+2NgTop2EfZL1w9aCNCJGMiDpPtYzl0lzuGhWV8j/CzmquoA", + "map": "", + "preliminaryFileName": "build/root.tsx_root_component_9PcKHFjikV0.js", + "sourcemapFileName": null + }, + "build/preload-helper.js": { + "exports": ["_"], + "facadeModuleId": null, + "isDynamicEntry": false, + "isEntry": false, + "isImplicitEntry": false, + "moduleIds": ["\u0000vite/preload-helper.js"], + "name": "preload-helper", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/preload-helper.js", + "implicitlyLoadedBefore": [], + "importedBindings": {}, + "imports": [], + "modules": { + "\u0000vite/preload-helper.js": { + "code": "", + "originalLength": 2271, + "removedExports": [], + "renderedExports": ["__vitePreload"], + "renderedLength": 2279 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nconsole.log(\">>> running\", \"\\0vite/preload-helper.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"\\0vite/preload-helper.js\");\nconst scriptRel = function detectScriptRel() {\n const relList = typeof document !== \"undefined\" && document.createElement(\"link\").relList;\n return relList && relList.supports && relList.supports(\"modulepreload\") ? \"modulepreload\" : \"preload\";\n}();\nconst assetsURL = function(dep) {\n return \"/\" + dep;\n};\nconst seen = {};\nconst __vitePreload = function preload(baseModule, deps, importerUrl) {\n let promise = Promise.resolve();\n if (deps && deps.length > 0) {\n document.getElementsByTagName(\"link\");\n const cspNonceMeta = document.querySelector(\n \"meta[property=csp-nonce]\"\n );\n const cspNonce = (cspNonceMeta == null ? void 0 : cspNonceMeta.nonce) || (cspNonceMeta == null ? void 0 : cspNonceMeta.getAttribute(\"nonce\"));\n promise = Promise.all(\n deps.map((dep) => {\n dep = assetsURL(dep);\n if (dep in seen) return;\n seen[dep] = true;\n const isCss = dep.endsWith(\".css\");\n const cssSelector = isCss ? '[rel=\"stylesheet\"]' : \"\";\n if (document.querySelector(`link[href=\"${dep}\"]${cssSelector}`)) {\n return;\n }\n const link = document.createElement(\"link\");\n link.rel = isCss ? \"stylesheet\" : scriptRel;\n if (!isCss) {\n link.as = \"script\";\n link.crossOrigin = \"\";\n }\n link.href = dep;\n if (cspNonce) {\n link.setAttribute(\"nonce\", cspNonce);\n }\n document.head.appendChild(link);\n if (isCss) {\n return new Promise((res, rej) => {\n link.addEventListener(\"load\", res);\n link.addEventListener(\n \"error\",\n () => rej(new Error(`Unable to preload CSS for ${dep}`))\n );\n });\n }\n })\n );\n }\n return promise.then(() => baseModule()).catch((err) => {\n const e = new Event(\"vite:preloadError\", { cancelable: true });\n e.payload = err;\n window.dispatchEvent(e);\n if (!e.defaultPrevented) {\n throw err;\n }\n });\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"qQ5FQH592ZMJM7B1od3IgysH7RfqiGGdpURgoZyKPaIJLg69swMpgxQbMqasP7j0Rdv0IzjqMh0uNdVaQyLgew==zDeuOWWWnzw1BuF3lakADami2WrzJILOhPNozyIsnc/Ndi8ZBAnOq7RCwLaGqbQwLwiGbY8RSsxRDcsfd5d24g==\");\nexport {\n __vitePreload as _\n};\n", + "map": "", + "preliminaryFileName": "build/preload-helper.js", + "sourcemapFileName": null + }, + "build/core.js": { + "exports": [ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "S", + "_", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z" + ], + "facadeModuleId": null, + "isDynamicEntry": false, + "isEntry": false, + "isImplicitEntry": false, + "moduleIds": ["/qwik/packages/qwik/dist/core.mjs", "/qwik/packages/qwik/dist/core.prod.mjs"], + "name": "core.prod", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/core.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/preloader.js": ["l", "p", "i"] + }, + "imports": ["build/preloader.js"], + "modules": { + "/qwik/packages/qwik/dist/core.mjs": { + "code": "", + "originalLength": 255024, + "removedExports": [ + "HTMLFragment", + "PrefetchGraph", + "PrefetchServiceWorker", + "RenderOnce", + "Resource", + "SSRComment", + "SSRHint", + "SSRRaw", + "SSRStream", + "SSRStreamBlock", + "_getContextEvent", + "_noopQrlDEV", + "_pauseFromContexts", + "_regSymbol", + "_renderSSR", + "_verifySerializable", + "component$", + "createComputed$", + "createComputedQrl", + "createElement", + "event$", + "h", + "implicit$FirstArg", + "inlinedQrl", + "inlinedQrlDEV", + "isBrowser", + "isDev", + "jsx", + "jsxDEV", + "jsxs", + "qrlDEV", + "render", + "setPlatform", + "sync$", + "useComputed$", + "useComputedQrl", + "useId", + "useResource$", + "useResourceQrl", + "useStyles$", + "useStylesScoped$", + "useStylesScopedQrl", + "useTask$", + "useVisibleTask$", + "version" + ], + "renderedExports": [ + "$", + "Fragment", + "SkipRender", + "Slot", + "_IMMUTABLE", + "_deserializeData", + "_fnSignal", + "_getContextElement", + "_hW", + "_jsxBranch", + "_jsxC", + "_jsxQ", + "_jsxS", + "_noopQrl", + "_qrlSync", + "_restProps", + "_serializeData", + "_waitUntilRendered", + "_weakSerialize", + "_wrapProp", + "_wrapSignal", + "componentQrl", + "createContextId", + "createSignal", + "eventQrl", + "getLocale", + "getPlatform", + "isServer", + "isSignal", + "noSerialize", + "qrl", + "untrack", + "unwrapStore", + "useConstant", + "useContext", + "useContextProvider", + "useErrorBoundary", + "useLexicalScope", + "useOn", + "useOnDocument", + "useOnWindow", + "useServerData", + "useSignal", + "useStore", + "useStylesQrl", + "useTaskQrl", + "useVisibleTaskQrl", + "withLocale" + ], + "renderedLength": 159615 + }, + "/qwik/packages/qwik/dist/core.prod.mjs": { + "code": "", + "originalLength": 28, + "removedExports": [], + "renderedExports": [], + "renderedLength": 2427 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a, _b2;\nimport { l as loadBundleGraph, p as preload, i as isServer } from \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik/dist/core.mjs\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik/dist/core.mjs\");\nvar __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => {\n __defNormalProp(obj, typeof key !== \"symbol\" ? key + \"\" : key, value);\n return value;\n};\nvar qDev = false;\nvar qDynamicPlatform = globalThis.qDynamicPlatform !== false;\nvar qRuntimeQrl = globalThis.qRuntimeQrl === true;\nvar seal = (obj) => {\n};\nvar isNode = (value) => {\n return value && typeof value.nodeType === \"number\";\n};\nvar isDocument = (value) => {\n return value.nodeType === 9;\n};\nvar isElement = (value) => {\n return value.nodeType === 1;\n};\nvar isQwikElement = (value) => {\n const nodeType = value.nodeType;\n return nodeType === 1 || nodeType === 111;\n};\nvar isNodeElement = (value) => {\n const nodeType = value.nodeType;\n return nodeType === 1 || nodeType === 111 || nodeType === 3;\n};\nvar isVirtualElement = (value) => {\n return value.nodeType === 111;\n};\nvar isText = (value) => {\n return value.nodeType === 3;\n};\nvar isComment = (value) => {\n return value.nodeType === 8;\n};\nvar STYLE = \"\";\nvar logError = (message, ...optionalParams) => {\n return createAndLogError(false, message, ...optionalParams);\n};\nvar throwErrorAndStop = (message, ...optionalParams) => {\n const error = createAndLogError(false, message, ...optionalParams);\n debugger;\n throw error;\n};\nvar logErrorAndStop = (message, ...optionalParams) => {\n const err = createAndLogError(true, message, ...optionalParams);\n debugger;\n return err;\n};\nvar logOnceWarn = (message, ...optionalParams) => {\n};\nvar logWarn = (message, ...optionalParams) => {\n};\nvar logDebug = (message, ...optionalParams) => {\n};\nvar printParams = (optionalParams) => {\n return optionalParams;\n};\nvar createAndLogError = (asyncThrow, message, ...optionalParams) => {\n const err = message instanceof Error ? message : new Error(message);\n console.error(\"%cQWIK ERROR\", STYLE, err.message, ...printParams(optionalParams), err.stack);\n asyncThrow && true && setTimeout(() => {\n throw err;\n }, 0);\n return err;\n};\nfunction assertDefined(value, text, ...parts) {\n}\nfunction assertEqual(value1, value2, text, ...parts) {\n}\nfunction assertFail(text, ...parts) {\n}\nfunction assertTrue(value1, text, ...parts) {\n}\nfunction assertNumber(value1, text, ...parts) {\n}\nfunction assertString(value1, text, ...parts) {\n}\nfunction assertQwikElement(el) {\n}\nvar codeToText = (code, ...parts) => {\n {\n return `Code(${code}) https://github.com/QwikDev/qwik/blob/main/packages/qwik/src/core/error/error.ts#L${8 + code}`;\n }\n};\nvar QError_stringifyClassOrStyle = 0;\nvar QError_verifySerializable = 3;\nvar QError_setProperty = 6;\nvar QError_qrlIsNotFunction = 10;\nvar QError_unknownTypeArgument = 12;\nvar QError_notFoundContext = 13;\nvar QError_useMethodOutsideContext = 14;\nvar QError_immutableProps = 17;\nvar QError_useInvokeContext = 20;\nvar QError_invalidJsxNodeType = 25;\nvar QError_trackUseStore = 26;\nvar QError_missingObjectId = 27;\nvar QError_qrlMissingContainer = 30;\nvar QError_qrlMissingChunk = 31;\nvar QError_invalidRefValue = 32;\nvar qError = (code, ...parts) => {\n const text = codeToText(code, ...parts);\n return logErrorAndStop(text, ...parts);\n};\nvar createPlatform = () => {\n return {\n isServer,\n importSymbol(containerEl, url, symbolName) {\n if (!url) {\n throw qError(QError_qrlMissingChunk, symbolName);\n }\n if (!containerEl) {\n throw qError(QError_qrlMissingContainer, url, symbolName);\n }\n const urlDoc = toUrl(containerEl.ownerDocument, containerEl, url).toString();\n const urlCopy = new URL(urlDoc);\n urlCopy.hash = \"\";\n const importURL = urlCopy.href;\n return import(\n /* @vite-ignore */\n importURL\n ).then((mod) => {\n return mod[symbolName];\n });\n },\n raf: (fn) => {\n return new Promise((resolve) => {\n requestAnimationFrame(() => {\n resolve(fn());\n });\n });\n },\n nextTick: (fn) => {\n return new Promise((resolve) => {\n setTimeout(() => {\n resolve(fn());\n });\n });\n },\n chunkForSymbol(symbolName, chunk) {\n return [symbolName, chunk ?? \"_\"];\n }\n };\n};\nvar toUrl = (doc, containerEl, url) => {\n const baseURI = doc.baseURI;\n const base = new URL(containerEl.getAttribute(\"q:base\") ?? baseURI, baseURI);\n return new URL(url, base);\n};\nvar _platform = /* @__PURE__ */ createPlatform();\nvar getPlatform = () => {\n return _platform;\n};\nvar isServerPlatform = () => {\n if (qDynamicPlatform) {\n return _platform.isServer;\n }\n return false;\n};\nvar isSerializableObject = (", + "map": "", + "preliminaryFileName": "build/core.js", + "sourcemapFileName": null + }, + "build/router-head.tsx_RouterHead_component_dAo05yeFq1I.js": { + "exports": ["s_dAo05yeFq1I"], + "facadeModuleId": "/src/components/router-head/router-head.tsx_RouterHead_component_dAo05yeFq1I.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/src/components/router-head/router-head.tsx_RouterHead_component_dAo05yeFq1I.js" + ], + "name": "router-head.tsx_RouterHead_component_dAo05yeFq1I", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/router-head.tsx_RouterHead_component_dAo05yeFq1I.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["_", "a", "A", "z", "F"], + "build/qwik-city.js": ["L", "H"], + "build/preloader.js": [], + "build/preload-helper.js": [] + }, + "imports": [ + "build/core.js", + "build/qwik-city.js", + "build/preloader.js", + "build/preload-helper.js" + ], + "modules": { + "/src/components/router-head/router-head.tsx_RouterHead_component_dAo05yeFq1I.js": { + "code": "", + "originalLength": 1815, + "removedExports": [], + "renderedExports": ["s_dAo05yeFq1I"], + "renderedLength": 34304 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { _ as _jsxC, a as _jsxQ, A as _fnSignal, z as _jsxS, F as Fragment } from \"./core.js\";\nimport { L as useDocumentHead, H as useLocation } from \"./qwik-city.js\";\nimport \"./preloader.js\";\nimport \"./preload-helper.js\";\nconsole.log(\">>> running\", \"/src/components/router-head/router-head.tsx_RouterHead_component_dAo05yeFq1I.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/components/router-head/router-head.tsx_RouterHead_component_dAo05yeFq1I.js\");\nconst s_dAo05yeFq1I = () => {\n const head = useDocumentHead();\n const loc = useLocation();\n return /* @__PURE__ */ _jsxC(Fragment, {\n children: [\n /* @__PURE__ */ _jsxQ(\"title\", null, null, head.title, 1, null),\n /* @__PURE__ */ _jsxQ(\"link\", null, {\n rel: \"canonical\",\n href: _fnSignal((p0) => p0.url.href, [\n loc\n ])\n }, null, 3, null),\n /* @__PURE__ */ _jsxQ(\"meta\", null, {\n name: \"viewport\",\n content: \"width=device-width, initial-scale=1.0\"\n }, null, 3, null),\n head.meta.map((m) => /* @__PURE__ */ _jsxS(\"meta\", {\n ...m\n }, null, 0, m.key)),\n head.links.map((l) => /* @__PURE__ */ _jsxS(\"link\", {\n ...l\n }, null, 0, l.key)),\n head.styles.map((s) => {\n var _a2;\n return /* @__PURE__ */ _jsxS(\"style\", {\n ...s.props,\n ...((_a2 = s.props) == null ? void 0 : _a2.dangerouslySetInnerHTML) ? {} : {\n dangerouslySetInnerHTML: s.style\n }\n }, null, 0, s.key);\n }),\n head.scripts.map((s) => {\n var _a2;\n return /* @__PURE__ */ _jsxS(\"script\", {\n ...s.props,\n ...((_a2 = s.props) == null ? void 0 : _a2.dangerouslySetInnerHTML) ? {} : {\n dangerouslySetInnerHTML: s.script\n }\n }, null, 0, s.key);\n })\n ]\n }, 1, \"r0_0\");\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"2heiE2ewDXMnoNkpMltxMYcJn2QW1ofCKWkr7XdqVHbZ/fp7BMFfouOzKeTjC3x6aP7RP7MqYHJOajWQDaTGOA==v88cmTFIZnxnFOvMdnc0HZP6VRAxb9PXTai6aza8QZnGMvsYit85wuDHPJZPcLDr2jl51nPejCotcy0eMRO14w==uNN7IZFpURHtCtwZT/KWK1JK2v/rCPjTVAwHfEjVQRlBfiNx5z2He2jscB4vEpKrRiBWD1aSTWZ1wME0vR1tbg==gtxh9TG5Im6U4af13LO/pvmVcCED2SVl7Dn4uaJSwlOkjlrcZOpu528oJw0dpIYeERNJUOuinUfZzsBcTzKPxg==p0OlqM3l2ooKvQ124GC1HnZ37W02OS4x9CtYbw67JlfV3Wl/aTBTgj5wsdKfSNnJDlY0GynPGZc8d4cv0EH0ow==tXbV7gSpBHfLCx4Y49ypfnTGGidhYpYqypAXXF/38jBqR0SZPRlv77SgEw4O5dcWn+7Zcv697RPEXlYe7wOMEQ==c59ThnPfbA7hileV19M8EeRDyKtL5QZXg0OGFX1/4w9RlD2sz3UF8XCovRiX1V2OnA8AjysdujPM1guhdnkjRQ==IY2yTP8aT4qIcw+Gi3ya2mBlZQp+nEGng0SWsxBwOa8x4432e9543o3Upwe3kYbdnNQ2rqgtMK8KmeDvyeph0Q==JTXapfd/sNXQCVrBsLjALGoj4enuIPqzHtamyLWdmJLqi211DDztviegoREyOlSTJiU7KlrOdNB63oeWdG06jA==ydwm8BDOj1wOZIIiZdJfOQHHCBTCtuU5oj/4AZOGeK5xJBFKT3Uuj+kTp98/d8iQQDKvOtPWPChmBHX0cI70OA==6cMXr5bHUqgXhGZH+Y0JBLF8qjxP6l8KKg7Ma744nqO6dkBaNsoQxppCJZQH9tzFNDJSFkos7Qn6J82eMHpvUQ==WZRp1agTbuaxKwj3O7UJ4qUGGEnbC2gnhyX+4bq12qQRQiQjXXAN+wjGpx2Io3hPzbV1JdUR5n8jsOFEjaU3JA==DagmRh/BQXg43YAH1BoEM+3mimHuq2sSEULRwz6c/cg4xRZp7u5knzIuN57w4EjEuGgevOgSdOLRaE+tpwL3xw==qnoYK0VFGmwudAGTx6hWpIbqTGljFMXuqAReE0uwSj13r/MubhK4NhgFQX49BcgBDz6ywUdcmbty3oagmyfB7Q==A5LZL0ssKWkri9SWfHegpsTDSeA6sEe3q/yZFg/3aWk4bCUul10AzRgC0kfg6VkAyJ5qyXVxHtCRkkM1ZHnDVQ==3GLntd8jqf+cNmxXDMGbCJ4UEMefvo/T0WKY/ia+fjfuPQsAuZbgH8r3DfIbDnq6qyLHr4jvvS3ONN1dsw5qYg==J7/GfH2t0r1qOEpIS+zO6OR/lNDo9P2ya6OtFUGFJAdkh4xbETBGUdWyJBO9heEtATTTYPSZeyuqYazlZVJBMQ==BymYNQbGm5TBLbciAq/UQWodcgI0lDqomf1kHVO4SkdSlDXUKUJgX5/xc2ChuVdIMbSuearOKlPSYrIA0mo7bw==MwmQg2oZ0z1IQ6NYqwmICvuShQozUJfKYo3Dh53uUaeflqlLI1tN/Mzambs1fhUSN0rDGUCDUSigKPpkF/ylWw==7kL0lY0y8t9BF/RIWG4mUjtr+3aoVs5o9jhEJWmIlrKi5PK1mZhmWMvj/x2+TyZGKOTSWft8hMaycZ+jneIN8A==MY15qPcRo3Jqvf3M+uR/ts9uw1EmVqtnDDFyZcAgIsrdQRVTSSANhVHxKdFpDiiVmIXdzWHPiyxXqwkmQLsI4Q==eynRHIF9Xu6a5++zzqFxU8G0QjOI98YTpkS7RLpxFj9qZ4Ue4ztmhDmODHeWEwwn87oJfd9aHyYWnHrbZD5qMQ==yx2tKGe0d2pe5+UkG0PkX8tjtd1ETytA4GLoECNvdDaDbtoIKSBydTgOtZSVn9glTBALv19sv/cOE8uaYUeSuA==zbkyDJdEqZdQlB3M4AGJEhy0pA/cVamviEV/+iw+sxglpCecBVbAiwlMxZIaqeIiROJ/dJ4mxnsU/o1zCIiB/A==zp/ly7jB+Ax+4FAKEvmiJqsNLKnHtHTE6lZK0kmY96Ck1M82TdHtN1O4tgexZStgRR9dj0bB0qB2/YojINr0tw==lHaHxsh+rf7KQkLgpHdOVWeuP9IWDOuAP5v3mRnc7LkQ5EYftxUUNPQFH5syHc0oWQ9w+9W2d27Fc+tH/hDVPw==QS+z6ydwcrLgMojl8BuM+uqXdj2zxIgFcVDrz+D3z/odfqDZGGzpz1PHZiNPVL3LoCQw8j41KxZdp3mFiRGp1g==KMZukSp+AenaqJdInNhMYLNhxBTYBlQ29nUce2QEy1aCUQQJ6bJoGPzF75WA9wWGSQsZSxTTQ0rUGNYRmgoOfg==G0dgQ4koPIyjEPPOeXx8F8AxKg7arFTnEbxTOgoQzlri8wv0sfWA19sfgZ4okU3bO5p4v29GzOi4wTS9XQrvcQ==PS6oyNh9KQPKSuLwNYkDOKQWdMfFBbO+XPmlo+io+bNbHUXyucJcbeCCReNmSxdNEQMfEczse8ACdMVW7ez4vg==AorbSHhmPAY6+u+ptJXN+lSIwZBdw40NiRYlNXB79+W9k4fJc/98kgAy/DG4zEr8QMMamqXyEW0sp6Nrujq+mA==jRmozK9ueGPzRKEcLM35mjbvKFZgjgplAtVEfk9nKPIqY6nxfFgPIgIludW72Jp6cIioy7XcYGcOwlbwDdLNgA==nSL9Frk4vaMO0EBWphZjNI81e4Cw3tWTNaRZL94papMBlVa3ngfVQxWfoiGtzcGy7rOtWnYTcJTvMs+cHNWksw==eKeoR8IYXEyQ27OHTkjkMp+hpZQK8eyD9GTroBSdHT9Vj8W0/0BxPx4t9DesoYc6", + "map": "", + "preliminaryFileName": "build/router-head.tsx_RouterHead_component_dAo05yeFq1I.js", + "sourcemapFileName": null + }, + "build/qwik-city.js": { + "exports": [ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "Q", + "R", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z" + ], + "facadeModuleId": null, + "isDynamicEntry": false, + "isEntry": false, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/preloader-DnBPyedC.js", + "/@qwik-city-sw-register", + "/qwik/packages/qwik-city/lib/index.qwik.mjs" + ], + "name": "index.qwik", + "type": "chunk", + "dynamicImports": [ + "build/index.qwik.mjs_spaInit_event_EMGw8L1tB9Y.js", + "build/index.qwik.mjs_RouterOutlet_component_KRtEZRf6Xh8.js", + "build/index.qwik.mjs_QwikCityProvider_component_OzSZb6UyaCs.js", + "build/index.qwik.mjs_Link_component_bp3n7NtzXfs.js", + "build/index.qwik.mjs_routeActionQrl_action_submit_WcRKLKMW88U.js", + "build/index.qwik.mjs_Form_form_onSubmit_p0qDGZV34Qs.js", + "build/index.qwik.mjs_GetForm_component_amqstTwiNo0.js" + ], + "fileName": "build/qwik-city.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/preload-helper.js": ["_"], + "build/core.js": ["f", "i", "q", "E", "e", "z", "G", "_", "c", "x", "H", "r", "d", "I", "B"] + }, + "imports": ["build/preload-helper.js", "build/core.js"], + "modules": { + "/qwik/packages/qwik-city/lib/preloader-DnBPyedC.js": { + "code": "", + "originalLength": 4294, + "removedExports": ["l", "p"], + "renderedExports": [], + "renderedLength": 22144 + }, + "/@qwik-city-sw-register": { + "code": "", + "originalLength": 194, + "removedExports": ["default"], + "renderedExports": [], + "renderedLength": 264 + }, + "/qwik/packages/qwik-city/lib/index.qwik.mjs": { + "code": "", + "originalLength": 60672, + "removedExports": [ + "ErrorBoundary", + "QwikCityMockProvider", + "ServiceWorkerRegister", + "globalAction$", + "globalActionQrl", + "routeAction$", + "routeLoader$", + "routeLoaderQrl", + "server$", + "serverQrl", + "useContent", + "usePreventNavigate$", + "usePreventNavigateQrl", + "valibot$", + "valibotQrl", + "validator$", + "validatorQrl", + "z", + "zod$", + "zodQrl", + "_auto_deepFreeze" + ], + "renderedExports": [ + "Form", + "Link", + "QWIK_CITY_SCROLLER", + "QwikCityProvider", + "RouterOutlet", + "routeActionQrl", + "useDocumentHead", + "useLocation", + "useNavigate", + "_auto_CLIENT_DATA_CACHE", + "_auto_ContentContext", + "_auto_ContentInternalContext", + "_auto_DocumentHeadContext", + "_auto_QDATA_KEY", + "_auto_QFN_KEY", + "_auto_RouteActionContext", + "_auto_RouteInternalContext", + "_auto_RouteLocationContext", + "_auto_RouteNavigateContext", + "_auto_RoutePreventNavigateContext", + "_auto_RouteStateContext", + "_auto_clientNavigate", + "_auto_createDocumentHead", + "_auto_currentScrollState", + "_auto_deserializeStream", + "_auto_getClientNavPath", + "_auto_getContainer", + "_auto_getScrollHistory", + "_auto_internalState", + "_auto_isSameOrigin", + "_auto_isSamePath", + "_auto_loadClientData", + "_auto_loadRoute", + "_auto_prefetchSymbols", + "_auto_preventNav", + "_auto_resolveHead", + "_auto_restoreScroll", + "_auto_saveScrollHistory", + "_auto_shouldPrefetchData", + "_auto_shouldPrefetchSymbols", + "_auto_spaInit", + "_auto_toUrl", + "_auto_useQwikCityEnv" + ], + "renderedLength": 22892 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a, _b, _c;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { f as useContext, i as useStore, q as qrl, E as createContextId, e as _jsxBranch, z as _jsxS, G as _wrapSignal, _ as _jsxC, c as componentQrl, x as _deserializeData, H as eventQrl, r as noSerialize, d as useServerData, I as withLocale, B as _IMMUTABLE } from \"./core.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/preloader-DnBPyedC.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/preloader-DnBPyedC.js\");\nvar BundleImportState = ((e) => {\n e[e[\"None\"] = 0] = \"None\";\n e[e[\"Low\"] = 1] = \"Low\";\n e[e[\"Queued\"] = 2] = \"Queued\";\n e[e[\"Loading\"] = 3] = \"Loading\";\n e[e[\"Loaded\"] = 4] = \"Loaded\";\n return e;\n})(BundleImportState || {});\nconst doc = document;\nconst modulePreloadStr = \"modulepreload\";\nconst preloadStr = \"preload\";\ndoc.createElement(\"link\").relList.supports(modulePreloadStr) ? modulePreloadStr : preloadStr;\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"3vRGrJ7m4H3LPJiHyXrOpuhles+pyvA1GyPeR3eR8qQAdbpIv0DlHK7eyFg8FO46ool66wYZKOrFMJvbvL71hg==LzCRMJvgGX0H3g9yBP3Z4dM61WnXeUX6BwAozmZUiMGuI1XCl9SeVNRO1aEKyUcubJ27+aeWcKebX7QWcvc0GA==REvcZ/QBktVIAEF4ZnCsZjwxq1RkfnEN6TM/00TYZdqB3vj2Bs3TA8WNuSoF80a+zgRq1bAUSg5y63SmGPzZ5A==zPhWMk/g9M83vb58SWMf6y47yVjv2VfGy67pV9LG4KZGg1bYoc05g2vJ30yWUTUpcbglC0FWRid632stCFGm4A==1NOaZzOQ9F/xb9I0WrTZmOKGqnQHzKB636+vX3UfeQ/uU/qAr9cRZAinFENQL8UPdFsjHbk8MAHN9/pfI8aJUQ==ixnteFu6zDHQXSVOd7KHoP5s5rXE70NvEmeon7d93saQNmR4ulTZTzTZ9QFQeR3Qq6vU3tMEIvfQ5dS9N+DqfA==tdJ+bNzGVaz1+lQD4ksl5QYek9Gl+IMqn0ZkKTdO8Ce+b7aMulH2+RNIos16MmoQvPL9hPMef5fLWC5lhpz3Xg==ZHhMcqqdzjmnG/QKfF4nh0vZ98WcQIbV7dCXBYQb7bKr3xd0wvvYyB9/MotygXXIXWLZ+osx0ym1e3A56F0L5Q==59Nxg25WN/nbNxUNL0Zi90VhJR0i2aJ8gwxcLp/CnloUgjTcBgQdwsAC0YMaipFK2W3+pDdAaUfeL9f/ql58Tw==ZMeopBYlyEqgmIhK/mWft41ZHIuyt6GYBBs7WWZG9wJ0Cx+qLQeFn2DF+51NtfHToeDIWcFrepY0eEW8Lkm21A==IYoJIitQ8Q4vbm1/95R0NqYQPTsBBhIF2q5UCbw4Ky+NNyiS7V92sSo1o3JFilCFrZyZwc5j8LJBOKFnjzoJxQ==Or57x0t7sb321jOR4rjFyh28Xd9uHHlHIDXqjuBBgfcOG0/EEG8jN8DHvkIPqaSIAEB36nZDHoT23OoSwH4rEg==oecE1Ax/XWiCPEwOfX1wYrC9yqZTj6xdW/ZSp+zAuVMA2sUMklCJ+pbIzjO/724R7P459hKJgX0Ql5cY+XAdAA==/+m/RMIfjiPsJa4qOld5la7sppyl93kjoZ6xxKNn9872m/joL4Tfv7lUoOxn2zM55VPKOv1wuD3z+9ECOwxcQA==bZgt9IMd5VtbkfkeWx8nb2lh6i8vyXWUyyJg/QCEc6oQvK0yxNABIIepidELQynI3990fSXFmk3Z0z0u4WGuQg==UVp0vd079TFKQnenNeglcupRUUt4vLrI1eS0BzMhKUCFpBJTl8MEZseuD6MK5p8zMLE0TBBERTvb5IKNYKqXbg==F4KAEZU8YFhCgvGU059Y9BpbX59uaUjhsqtXNKyWf94eHSisFf608Hfo0qRIR3y2jpPI7AbszBVxehrzdu2pfw==iq++SmEyIqKIn4MYKhdCGGMPEnK3YBIwFJ/1UgzaB32YkPcGtBfmwd9nhgOltWC/dv3pW0ju7efH4yYo4EU+Aw==mMX98d2OqcmR5GQHpBXcUGXFPrf+qABStTB30C83J2Hg4Q3LTbSQU2fvsQimJFikQrULDYey5ih389tFH+pwkA==5HmUA29JL0vX6CoYq40LcDuwPEWvejSY0V+j+v/fV5FxwU0zQxVI3XQNp/K4mCUW55nY9M0oxhckHc2Um5lHOg==v6kNzo2oJUVI8cL/dnZo7HomfIUwooIpciiLtfSclpbpmgMv1YC7CMTN8wy+ZcR8kX7teB/tqKaOMpZrPS/p+w==6nWCzuqhoASVqm6wZQ7S5LNGcM6rYlyJWctOfhvIFE1k90YMt1twOkYVVse5PKzq8P92mrF6zjVOg5sRLAKVkA==8P3ie+/aL2ZxnuwQbKAvySda2COMdbx71tuslurgkaXmk7AtiFKrtkMBsXHYtMdYjMNuc/GOPkSctF/bdzNmUg==FyjLfv85bqAN8Vt2R7+GjCJ8gS89GrGqU6g7Pz8dkxJa2aDkEeBWNN+yBa8j7L/PKsJPv7KjRZkEz/I2uiCWww==cTJWLydG7hfN+OLEysBdSKUs0Jijrj0MEISZKeMN/WA2iiA7toIBF6YHrACsrDWcFRVdEQ5VoKuRJxZJ4KBMLQ==dr36XLFX3+av1wq/s4sjXZT1RdovQfzFZWhaAG9oLSHW5Kg3gUUmTdtTUVPc+8hpUCzn1ZyjDu2r+1Bk0+MdFg==40UdFe0QMsdJB4yq+Sy99642I8BFrc4QrcHc2sk57q6t4a9AQ5fvOGKPIrR5L43lOfdQsP0QOr38u2xyPneaDg==+Mu9NaqswAg5Gi23Hr5qlIpQCN0UCwGZlqu8QGsWUtTOwAqOzqBxov4j655GVWtsU8dQV+0Q5F74PLJ+GEfP0w==/BTFActRM+DzDltv/AEZ7L7Iai7S0Xpa8eSAwG8UE9ezyJfftWV2t/gICElX05ec8d81/fjuoj1Ns2YeLZxzwQ==4mnZea7+KC11mhltqavNp9MeruW9ArPxnck59DM24hu0GRpUW5cXYPfJzbcxDCIPu7hYSEK6WeZXnWYMgGeLWg==z9wfyBm1CjRLjRxznhgeFUx9oKeALbaaVEySkBc5sMhaWxDAQL4NXUh69UqsaMr9qmdWv0EmJ/wfmc3065jSdw==nABqT4nqWG0ovM/0Hj5QcFehHUfI8P4Xid0IllbinSma/ZXCr4odSgzaHHWrudkDgKiMgpE1WXNLVIbrryHMsA==CrHr29yltSEtP0WUMxir/ujON5ypHUWxHWeaY/VWEK4W4xK59o0vHZkoRtZHjJs1UOVu8l2W236EGxJ3AwhxbQ==CluU8Sfx3vW2BUUPBZxSLQRW6zu7uoUhIchEPc0G431AvAqj3aiSMzWE4TUHURdRI/dl3oyPg945qWZPzsqjkA==Qinc5XWyDZg+iMYexV6MMxye+91/+GzMsM3lXJI+hnKiRhzVs6ZTXpPRgLWUIjYqHvQORfYVEGGDCdc3ag2kBQ==Ajx12D7SG/34Kn70cgbeJmDmYcgR50anTjsiKOaOfE6eC7MySYOMFv9gv3Qs6Ao8O061+gvZNMofR8D2mTS92A==iIDjmmnyrdgXB90jHquuLeeQwvizowDi13yJQxBXYJlsSC1BqV31GB8UN7f76N6DCFZW5d1yfrfVR29a7wklTg==3jXOwTW6lxY4/jlm6CF5jNZW/op55v/5sKFnyks4tOdgMBEo8DPetn+AHRxwG9U/nLYUCt+gAiFN1fTW5wDrkQ==OAuJRcPWqmAtrPqobMuq2RuBB9Ho1Rhgf34YP1p+V205aiJiw2QEXWU0A4/A6VRtXWbdUXeL4oOmaJxw3QQ6dw==f55p6UHy3JrE6x81ic/qh/zmJMF/oWshGqiR28r+i1h9K7x0V5WYOJ8RopDn9EMsD8GqjNeGENZuO/XV3EkUQw==zGN5E4zAjCGhNcwNytgi+n2WnLh7gZkMo63AucKa9YKdiKVjvQd8MIXvF+buLPCV21aOxWvsioEx+lCuoQtz5w==BQpYHtjgH0sKX5YSBC9jvkkZ/MlteweoEsZTRZ5NaO5+QvKKr5CUUsBYU1Vhn4Zg7DMGhJbjxdIHx6Fi8+gcMw==hNHkLamD2W2GPD96eRozvHC/e+y8e9eA9rUqoLzF2zVsC4unGdzXktVRmOrFoxNKzBIAOSVh//n9b/LPC5gh0g==axKD4qPqIWM0KCuyUsOq83dECo5ixYQeHmZh/6VxeHGCmzl1j/Sv1Azf6uAWiP0GPPp7aTMBG++10YJ8F5qnkQ==KN1qBtPcCpaeWmUgk0fplFQThw2xMRQ2B+XkHLXCbZjS2kBz", + "map": "", + "preliminaryFileName": "build/qwik-city.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_Form_form_onSubmit_p0qDGZV34Qs.js": { + "exports": ["_hW", "s_p0qDGZV34Qs"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_Form_form_onSubmit_p0qDGZV34Qs.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_Form_form_onSubmit_p0qDGZV34Qs.js" + ], + "name": "index.qwik.mjs_Form_form_onSubmit_p0qDGZV34Qs", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_Form_form_onSubmit_p0qDGZV34Qs.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u", "b"], + "build/preloader.js": [] + }, + "imports": ["build/core.js", "build/preloader.js"], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_Form_form_onSubmit_p0qDGZV34Qs.js": { + "code": "", + "originalLength": 227, + "removedExports": [], + "renderedExports": ["s_p0qDGZV34Qs"], + "renderedLength": 433 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { u as useLexicalScope } from \"./core.js\";\nimport { b } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_Form_form_onSubmit_p0qDGZV34Qs.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_Form_form_onSubmit_p0qDGZV34Qs.js\");\nconst s_p0qDGZV34Qs = (evt) => {\n const [action] = useLexicalScope();\n if (!action.submitted) return action.submit(evt);\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"\");\nexport {\n b as _hW,\n s_p0qDGZV34Qs\n};\n", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_Form_form_onSubmit_p0qDGZV34Qs.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_usePreventNavigateQrl_useVisibleTask_0OaA1Rw0yxk.js": { + "exports": ["_hW", "s_0OaA1Rw0yxk"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_usePreventNavigateQrl_useVisibleTask_0OaA1Rw0yxk.js", + "isDynamicEntry": false, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_usePreventNavigateQrl_useVisibleTask_0OaA1Rw0yxk.js" + ], + "name": "index.qwik.mjs_usePreventNavigateQrl_useVisibleTask_0OaA1Rw0yxk", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_usePreventNavigateQrl_useVisibleTask_0OaA1Rw0yxk.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u", "b"], + "build/preloader.js": [] + }, + "imports": ["build/core.js", "build/preloader.js"], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_usePreventNavigateQrl_useVisibleTask_0OaA1Rw0yxk.js": { + "code": "", + "originalLength": 221, + "removedExports": [], + "renderedExports": ["s_0OaA1Rw0yxk"], + "renderedLength": 463 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { u as useLexicalScope } from \"./core.js\";\nimport { b } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_usePreventNavigateQrl_useVisibleTask_0OaA1Rw0yxk.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_usePreventNavigateQrl_useVisibleTask_0OaA1Rw0yxk.js\");\nconst s_0OaA1Rw0yxk = () => {\n const [fn, registerPreventNav] = useLexicalScope();\n return registerPreventNav(fn);\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"\");\nexport {\n b as _hW,\n s_0OaA1Rw0yxk\n};\n", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_usePreventNavigateQrl_useVisibleTask_0OaA1Rw0yxk.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_Link_component_handleClick_gZBt5yIBEB4.js": { + "exports": ["_hW", "s_gZBt5yIBEB4"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_handleClick_gZBt5yIBEB4.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_handleClick_gZBt5yIBEB4.js" + ], + "name": "index.qwik.mjs_Link_component_handleClick_gZBt5yIBEB4", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_Link_component_handleClick_gZBt5yIBEB4.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u", "b"], + "build/preloader.js": [] + }, + "imports": ["build/core.js", "build/preloader.js"], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_handleClick_gZBt5yIBEB4.js": { + "code": "", + "originalLength": 642, + "removedExports": [], + "renderedExports": ["s_gZBt5yIBEB4"], + "renderedLength": 864 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { u as useLexicalScope } from \"./core.js\";\nimport { b } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_handleClick_gZBt5yIBEB4.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_handleClick_gZBt5yIBEB4.js\");\nconst s_gZBt5yIBEB4 = async (event, elm) => {\n const [nav, reload, replaceState, scroll] = useLexicalScope();\n if (event.defaultPrevented) {\n if (elm.hasAttribute(\"q:nbs\")) await nav(location.href, {\n type: \"popstate\"\n });\n else if (elm.href) {\n elm.setAttribute(\"aria-pressed\", \"true\");\n await nav(elm.href, {\n forceReload: reload,\n replaceState,\n scroll\n });\n elm.removeAttribute(\"aria-pressed\");\n }\n }\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"\");\nexport {\n b as _hW,\n s_gZBt5yIBEB4\n};\n", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_Link_component_handleClick_gZBt5yIBEB4.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_RouterOutlet_component_KRtEZRf6Xh8.js": { + "exports": ["s_KRtEZRf6Xh8"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_RouterOutlet_component_KRtEZRf6Xh8.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_RouterOutlet_component_KRtEZRf6Xh8.js" + ], + "name": "index.qwik.mjs_RouterOutlet_component_KRtEZRf6Xh8", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_RouterOutlet_component_KRtEZRf6Xh8.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/qwik-city.js": ["C", "s"], + "build/core.js": ["d", "e", "f", "_", "a", "g", "F", "S"], + "build/preload-helper.js": [], + "build/preloader.js": [] + }, + "imports": [ + "build/qwik-city.js", + "build/core.js", + "build/preload-helper.js", + "build/preloader.js" + ], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_RouterOutlet_component_KRtEZRf6Xh8.js": { + "code": "", + "originalLength": 2119, + "removedExports": [], + "renderedExports": ["s_KRtEZRf6Xh8"], + "renderedLength": 2776 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { C as ContentInternalContext, s as spaInit } from \"./qwik-city.js\";\nimport { d as useServerData, e as _jsxBranch, f as useContext, _ as _jsxC, a as _jsxQ, g as _qrlSync, F as Fragment, S as SkipRender } from \"./core.js\";\nimport \"./preload-helper.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_RouterOutlet_component_KRtEZRf6Xh8.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_RouterOutlet_component_KRtEZRf6Xh8.js\");\nconst s_KRtEZRf6Xh8 = () => {\n const serverData = useServerData(\"containerAttributes\");\n if (!serverData) throw new Error(\"PrefetchServiceWorker component must be rendered on the server.\");\n _jsxBranch();\n const context = useContext(ContentInternalContext);\n if (context.value && context.value.length > 0) {\n const contentsLen = context.value.length;\n let cmp = null;\n for (let i = contentsLen - 1; i >= 0; i--) if (context.value[i].default) cmp = _jsxC(context.value[i].default, {\n children: cmp\n }, 1, \"uY_0\");\n return /* @__PURE__ */ _jsxC(Fragment, {\n children: [\n cmp,\n /* @__PURE__ */ _jsxQ(\"script\", {\n \"document:onQCInit$\": spaInit,\n \"document:onQInit$\": _qrlSync(() => {\n ((w, h) => {\n var _a2;\n if (!w._qcs && h.scrollRestoration === \"manual\") {\n w._qcs = true;\n const s = (_a2 = h.state) == null ? void 0 : _a2._qCityScroll;\n if (s) w.scrollTo(s.x, s.y);\n document.dispatchEvent(new Event(\"qcinit\"));\n }\n })(window, history);\n }, '()=>{((w,h)=>{if(!w._qcs&&h.scrollRestoration===\"manual\"){w._qcs=true;const s=h.state?._qCityScroll;if(s){w.scrollTo(s.x,s.y);}document.dispatchEvent(new Event(\"qcinit\"));}})(window,history);}')\n }, null, null, 2, \"uY_1\")\n ]\n }, 1, \"uY_2\");\n }\n return SkipRender;\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"ixcXlBqovoOHTe1baR/O46mqLGFINz4JJRJwCI+iRDW7OhPpwaOjEv/66A4kM7/KER9oZruaaD6Rk7QM7w4uhQ==6UZqSn5J0z0VLfWSBFI/r3GpPjfX/I+E7f4lhUwuWtz8/5GrVMWYa0tcPpditsNxgrAgCtkSxI09a10MxIdNYA==8C0TbYM59PAFAPZ54kSkrpecv/l+/BECil1sMkj9J+nSt/DKrh0ZKBviqFDwACDr/2jeF5S/rD9CzhnXZTvQoA==8p+OKgJtsEbkS8x6t5RrM7DwsHhBNl84JKrOzYFuxl5EmzUnkMHCqvFYaOd2mRY6lUKRIRlNrWjxFICNchMRWA==TTeBQvrM4R7+ynOyboFuBg87+uxig5MRW8Icf5Chw+Le6v9ajFuHYxcP8GOroV/AfzPnEe8dNJg3QG93FlAlkA==BAW5B636FDXU6na8cLEhSGdSO9meD4HD65f2qvxhjorAD91VIv8euNsNPVne9mMpfEDDQfsbMLta77G6YR8cKQ==8J/QEMLpwLy8smrZM3ujFhoOUqi396o4Zjt+qPa4Y1ZgGlCOly7NEm2aEmJ2auTLNMqbI1y2ecwPG+us8oAlGQ==p8iJn+lKNOIJDoSZqqWD0rfR5bCn565Nkaw96mhbhWapbLp29K5OE2lpo9/InzqYXgm0oBgVziEifLVPHXc2/Q==Lj3kXj1rAOryHTQxVvH3peqHmjKnfz4rTcXFweZQFL1Jv8nAt/xriBcrS+S7p2gR6jA7ZpI34wEyDuPyjivRKA==aDSWE7y7nvazPivXva8gtOxxttSD+q70MmYD4Kxypsxaqy/sjDzGaZhkzc5Sw73asCx5AXZcIHZaxbIi5reEtg==\");\nexport {\n s_KRtEZRf6Xh8\n};\n", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_RouterOutlet_component_KRtEZRf6Xh8.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_ErrorBoundary_component_useOnWindow_0s3bilOgUys.js": { + "exports": ["_hW", "s_0s3bilOgUys"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_ErrorBoundary_component_useOnWindow_0s3bilOgUys.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_ErrorBoundary_component_useOnWindow_0s3bilOgUys.js" + ], + "name": "index.qwik.mjs_ErrorBoundary_component_useOnWindow_0s3bilOgUys", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_ErrorBoundary_component_useOnWindow_0s3bilOgUys.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u", "b"], + "build/preloader.js": [] + }, + "imports": ["build/core.js", "build/preloader.js"], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_ErrorBoundary_component_useOnWindow_0s3bilOgUys.js": { + "code": "", + "originalLength": 206, + "removedExports": [], + "renderedExports": ["s_0s3bilOgUys"], + "renderedLength": 25174 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { u as useLexicalScope } from \"./core.js\";\nimport { b } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_ErrorBoundary_component_useOnWindow_0s3bilOgUys.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_ErrorBoundary_component_useOnWindow_0s3bilOgUys.js\");\nconst s_0s3bilOgUys = (e) => {\n const [store2] = useLexicalScope();\n store2.error = e.detail.error;\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"Bnl8dv0BUTAVkrUP7WY3GYLiwQNmxUTGbUxf9C9bVy66HmEfFchHMSI2cN01zYHaTDD7qenxX9PS1GcCuEGsOQ==MHZajRJDgG8EDXt56PEk/kByVIrMuaG2isvTccm7hWzinZuphR2aVOuwsnIA9JB5yS/2T9yoBv5UhSd2suRS3A==gEWouRcRlmoAz1c1b+qBtsSBAFFcX+r4DbRz5/ytcH/jiTVxZchDGpHL3xndSzGuTy/dLHRS9XKZY0IAxijISA==paeQhCB2O3x6oxCHbH4Z11N4BSAbB0rF7Rti5wvBUzp1t3LS/C7KMGV2eyl8dbkDKM6BSH+oLCSXmpdsOs9Niw==vNuFkw1hwWYYLLwnb3KMC9hWm4Or1ZtHjZX++1MbamwKRhm+Vm9sJTJr/IgeiVL0i7/MlkgiwgfOec6Tsjc+1g==UCkACwoNCLcw9XqZmSliINm4p21/raauRJJkS6UmMq7/dESGQO/GspP0MEzvU8QMJkoFmOGjTEWucsT0+y/HOg==v9Xwp2eHmKPV33aURf+DRTQ9e3Dfb+muk+xskmIr9I+2KLh3+lWWnMk40TeNq7PSx7fGj5PPLuSYE6+WKsEhLg==pWxTsUVzeHgcn0p9UF1mEnDk3x+uFhSpcjrwyqo/9ogzf0VXna6nXRmrRPoHo2PqzMSpJbtEou+wgb4ivRxPfQ==STBA/+pyAXwUDcp+PRyGCEuv1zzLyu64QI1Xi/ii4dwXd9RRqZD0b/NEOLEUMx6rZjdp94w5N0ePV8kVXie9Bg==cMIal+U/tu2xJ+tri7sXAnw5YGjUjIwtRgRNJtePgopCHy5R5OC9+DNTex4vQweMnBzWdP53c3Qqes6V2BdYrw==8lV+fqJ+Vl/vU/qhX35Bm4SIpGHx1fmvPyYcSGdKEPli3sPE/i+YK9iCuEGH4QqaR702vJGtqSaf3RsCIRlzsw==vT9UuEyvhJgK9Gq++mj6tWMnlucKKktd8Z/1+tunZr/RPeoFM1JYK+45P1SmKoj4vrgGswInkuEjFR0S0BhYqw==DR+tijutN1BmbfuCTSShTc95fWkCO13E19CLpExXYEbmGRTwWVfUwDn1+A2VVpRtK1rmtPz6z1wnCHeFROx8OA==qleyJQyYbKn3rAmxb09uky99N59MI1asMOyk8IJS/ylZVyCsmcY4A5NaRiefeEZdiZCDp382eiL1KBkVTgneUA==IE3DFxZrpkYG6vmoW967SWagAma8G5koXhlZ4Ay2PVguDdsE1S/zIRdfitX+w0/mCGKECbzyxBwmFwR7IydlHw==d7plA1w7nv+fiV09sjaGekpxxOV0oKh9Fqc1hA6f1zlWBG9h9nJ3msGW+0SlxYKmXCmGUmQNxW27BzOv7/+edw==zvgcTtmFdb2Uf5CaUC/OfpaKEC1tkhrfHAYWic0Y61D6tZ6SjngjqMsLGorBYcNT8wJca4+6dCz2P6CcOC30jA==b1dbHPIAGnjbfheNJ1ON2TcfjILpCDTB52B/DSL56o6U/+MV2Gf7zWF2bYXvdUzZ5ws0jlLtjEQUOWgWtWc9xw==PwQUIRAsaVRASpVU1Q4PnpFpzcMwKk+wRkGcaau9gFyYhLSBMNvunHDYuczyOZotKy848nQT9a7v+J1L4B3NVw==L07mYFVKN+ElO7iF/MlPe6TXuEMrB/fBa5JdQDqzvUWItSi26gaPoo4oo6jEDE7C/Rn35PKCqNlcwEpkWdmvtQ==q3k2ER4HOuElEo7QEeMDeDxa54HDoMbewo+TvHidgT7t3RFn2bQwrwAMhtIPT1HlUBnvsGhfYLyPGXXmBnXvqw==+8VfrOCHG6cpNJ5aEeC5SaID7xahX2A2KVVAQYqqlN44TcpIjk6VrKPzPFX0g3ot923Fn/LMi0p8YWlTCBck0Q==mltqFiIzzkhiD0Zhjb/EFZjWnqi1FzwU1twh1p31OTWYTZUpi4t2rsuGzJK+HsyenlRQWY16vTg/6y+AKB9GZw==DCjAQKgrzNyYrc42iqXZI3Gr0FLNEQav9FQNzG/UY5+fxlv2r450buuguu6GOFyZv/x8ZwWCGzw8OSIvgRE/zw==7mO0NhgYMQhRXSyhXyTzB0xAaPna1Kr2oKy86KvmHf40rQ45dDFTZ4xLx8wuoDpn/Q4MN1bSfEcEnTjzFCH6Cw==eVzeJqihb+n7Kp3zpuPSFNHZX1Z8gi/koA8P6jjVz0uVfiuIWsS/u9dIUCIdreUaS0nV3ZlGWEH0RCqnk7arIg==SyyZLvG02xB5YkHC9Y41eX2ZXE+Y9TLWMCYiqRk9Y7siIkxWE4nWPS5L3lJLLWb6LiZxzgEXLeRSlfTeTIFTpA==zUbWe12hspO4hxpgo6oTqzTNXaWcZPeEtMdkMZyqDhEEjs8fmn/CqXKC6JLRGQzz9+ioReufi02T2ni77HIpRQ==QWXrNL730nVA5D/Y6LmBQcrRfI6K0sIknozg3FDMRFBQNnSw9utgNClPuFNXZJXrusRG3eGCAnPqS9YKoG8KgA==GryZ79zjDapAiBIxfMS+OYGitS9v5130ABvMJyqb9Bz+8p9Fbfk4owOSa4Eoc7gkfduYb2Go8USWY+pLrmcK9w==ugWlvj8I/501nC5QqvJyFeJxtwLu32xcc7tb7oVAFoxnUcS2HExKEusfGvVsEcx7FjZZ2RpbmmpLHHj5zit81w==AvgSN7emzJWwk8WNJQxlkIv6NNW4k2hW0nLfG5s64ZZC8xh5zdk7wjx9ssN4brzldxaEe84NFOLC3Us4tIoXkA==eEZjNrgYl+m8XNkoA9O74ajOGrsdVU9Xoo9cKSXhehPq2EYp8hWxQ+BUZ2JWtWdNpbqklY7e5tFlBTD3YJ1PvA==LHpqNQ2+pjMlOmFghV9BoSsNHy3owmgLQmdEWctEFQTdrfts5pkdcZME+jAXSxLUYZbRXXy330ur3D4aoXX3WA==iw5aOsVF+kIuHpJ53VxW6PX+Ej1mqvEwECyyWbQHhYgbbIUPWm6uqgiBDmzgkJ5l/Ut2DoLAj/tcyeonrkNI+w==cHcnF1E3oIF0jsJHlsh1e7nVdy2r3V8YSmT2vEd85FxeAWqWlWmaW0dtO/OhsypGmvBfbI9BNpsHMb6I3uwjNg==fH8FmUSmI4cfoq6TmCuT/G/mJZRROApuCX2rP/fR0wixLR2VkrLBz+Gf8/dRNt0cP6Pv1UVC/b5p+oseQ/HE+Q==ZnZcDdw36Y6eiVMTKfZOgRAxn2B+WfqFro8lzmfrWN8gA4q+MTPfIsJoZCdKogZUPMVmNB+Eevkzqre6ni/TGQ==CysX8UjsNY/YUVy5bjAQICeLxabp1D0GvO8/js+2DYlUO4o8ND9QPT9XI72A9it4CIlqMIfe+7oS6bgLRnLMfA==ldget7yj/RWfqKRDq0IelCD9uMiHlOhAUzk2ASodq5qdmfi6PBOaPEsn+4rTud567kGLIqH9p9jIbCXnDnDOSg==uL5ROztYpNpFm8ofEi9iWd9czZiQaAWc4jnERT4f1Szb8RpenUsyhxWhCxP+7/eODdG+Wk2zCtdAfSksRYL0aw==dBrblUIpYtAptaLe50yIlha/Bmvkt4KtVMeLaIj5Lz4ARc1ONr2srkGtUbglT0dGj065s8Jgd8Uw0ALdXaKl8A==CVCxyA1/itaxTJqKojKbglLS5xe9zyF4h32q6nmGjD7Ij+4EFAO/Ifrcotl70dTvBhiSGLxcrRLD/vrm7xOraA==mrTuqUFNxQvfpJTQ3/8CkUhi/6+rXYYjVr4sQ0ODKtBWDwM2NyVdLl2lyikhfUNduK0JnJXPuZgoSgNrvZQXsQ==B4dhbnwTwhv1zkqQjPtjo6JypDK7LlYBwMZMO0oROr6guQKJVx3lk+jJVL//NOM5mm5ZhfLjYRahu8VH5Bcegg==0RyqePrv1QvYLCSBesMZixrZ/huo2sN9LzeNxVYfxpTa7ghNuz5iC0R91zQ5MrqumqQmEd8AGqlXOVgxDoIkNQ==g0nQ8l8Fn1Os3avnLhpK+GZR1Sxt8iGf1pw6zGkFppD5BAYx/p7C3zF8AqAdmbr1FLpQqUW4RGLHFw8GezVv7w==2YyrJ31I7pJ1JXjj/GFcLPtvZX/Ao9t6JJhrbhOFKpBJTe8rzl5KfU9DX6G1P02uihbEUhAzA076wPhOxhsFoQ==g/AntfnQkmDO5rik2SUJjR1Ql5dRYIqdnbv8yBcfzcWRxFNEI7NeFj8ufmJp3qxkIB40xpqbgj6bsVMPuGQODw==/RQBCQS3wI8LmUW71WbWp4rLQ9gOclqvORoVd1rhjF5ybsh9TcCHRk0nTwiV8r/vd4k4RMe4Vw0", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_ErrorBoundary_component_useOnWindow_0s3bilOgUys.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_QwikCityProvider_component_OzSZb6UyaCs.js": { + "exports": ["s_OzSZb6UyaCs"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_OzSZb6UyaCs.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_OzSZb6UyaCs.js" + ], + "name": "index.qwik.mjs_QwikCityProvider_component_OzSZb6UyaCs", + "type": "chunk", + "dynamicImports": [ + "build/index.qwik.mjs_QwikCityProvider_component_useStyles_OC53PuG02nc.js", + "build/index.qwik.mjs_QwikCityProvider_component_registerPreventNav_zQJLLuPn6rA.js", + "build/index.qwik.mjs_QwikCityProvider_component_goto_IDVXhQLfmNQ.js", + "build/index.qwik.mjs_QwikCityProvider_component_useTask_n397ZlNS8UY.js" + ], + "fileName": "build/index.qwik.mjs_QwikCityProvider_component_OzSZb6UyaCs.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/preload-helper.js": ["_"], + "build/qwik-city.js": ["u", "c", "a", "C", "D", "b", "d", "e", "f", "g", "h"], + "build/core.js": ["h", "d", "i", "j", "k", "l", "m", "q", "_", "n"], + "build/preloader.js": [] + }, + "imports": [ + "build/preload-helper.js", + "build/qwik-city.js", + "build/core.js", + "build/preloader.js" + ], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_OzSZb6UyaCs.js": { + "code": "", + "originalLength": 4242, + "removedExports": [], + "renderedExports": ["s_OzSZb6UyaCs"], + "renderedLength": 3411 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { u as useQwikCityEnv, c as createDocumentHead, a as ContentContext, C as ContentInternalContext, D as DocumentHeadContext, b as RouteLocationContext, d as RouteNavigateContext, e as RouteStateContext, f as RouteActionContext, g as RouteInternalContext, h as RoutePreventNavigateContext } from \"./qwik-city.js\";\nimport { h as useStylesQrl, d as useServerData, i as useStore, j as _weakSerialize, k as useSignal, l as useContextProvider, m as useTaskQrl, q as qrl, _ as _jsxC, n as Slot } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_OzSZb6UyaCs.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_OzSZb6UyaCs.js\");\nconst s_OzSZb6UyaCs = (props) => {\n useStylesQrl(/* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.qwik.mjs_QwikCityProvider_component_useStyles_OC53PuG02nc.js\"), true ? [] : void 0), \"s_OC53PuG02nc\"));\n const env = useQwikCityEnv();\n if (!(env == null ? void 0 : env.params)) throw new Error(`Missing Qwik City Env Data for help visit https://github.com/QwikDev/qwik/issues/6237`);\n const urlEnv = useServerData(\"url\");\n if (!urlEnv) throw new Error(`Missing Qwik URL Env Data`);\n const url = new URL(urlEnv);\n const routeLocation = useStore({\n url,\n params: env.params,\n isNavigating: false,\n prevUrl: void 0\n }, {\n deep: false\n });\n const navResolver = {};\n const loaderState = _weakSerialize(useStore(env.response.loaders, {\n deep: false\n }));\n const routeInternal = useSignal({\n type: \"initial\",\n dest: url,\n forceReload: false,\n replaceState: false,\n scroll: true\n });\n const documentHead = useStore(createDocumentHead);\n const content = useStore({\n headings: void 0,\n menu: void 0\n });\n const contentInternal = useSignal();\n const currentActionId = env.response.action;\n const currentAction = currentActionId ? env.response.loaders[currentActionId] : void 0;\n const actionState = useSignal(currentAction ? {\n id: currentActionId,\n data: env.response.formData,\n output: {\n result: currentAction,\n status: env.response.status\n }\n } : void 0);\n const registerPreventNav = /* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.qwik.mjs_QwikCityProvider_component_registerPreventNav_zQJLLuPn6rA.js\"), true ? [] : void 0), \"s_zQJLLuPn6rA\");\n const goto = /* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.qwik.mjs_QwikCityProvider_component_goto_IDVXhQLfmNQ.js\"), true ? [] : void 0), \"s_IDVXhQLfmNQ\", [\n actionState,\n navResolver,\n routeInternal,\n routeLocation\n ]);\n useContextProvider(ContentContext, content);\n useContextProvider(ContentInternalContext, contentInternal);\n useContextProvider(DocumentHeadContext, documentHead);\n useContextProvider(RouteLocationContext, routeLocation);\n useContextProvider(RouteNavigateContext, goto);\n useContextProvider(RouteStateContext, loaderState);\n useContextProvider(RouteActionContext, actionState);\n useContextProvider(RouteInternalContext, routeInternal);\n useContextProvider(RoutePreventNavigateContext, registerPreventNav);\n useTaskQrl(/* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.qwik.mjs_QwikCityProvider_component_useTask_n397ZlNS8UY.js\"), true ? [] : void 0), \"s_n397ZlNS8UY\", [\n actionState,\n content,\n contentInternal,\n documentHead,\n env,\n goto,\n loaderState,\n navResolver,\n props,\n routeInternal,\n routeLocation\n ]));\n return /* @__PURE__ */ _jsxC(Slot, null, 3, \"uY_3\");\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"\");\nexport {\n s_OzSZb6UyaCs\n};\n", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_QwikCityProvider_component_OzSZb6UyaCs.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_QwikCityProvider_component_useStyles_OC53PuG02nc.js": { + "exports": ["s_OC53PuG02nc"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_useStyles_OC53PuG02nc.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_useStyles_OC53PuG02nc.js" + ], + "name": "index.qwik.mjs_QwikCityProvider_component_useStyles_OC53PuG02nc", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_QwikCityProvider_component_useStyles_OC53PuG02nc.js", + "implicitlyLoadedBefore": [], + "importedBindings": {}, + "imports": [], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_useStyles_OC53PuG02nc.js": { + "code": "", + "originalLength": 65, + "removedExports": [], + "renderedExports": ["s_OC53PuG02nc"], + "renderedLength": 9822 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_useStyles_OC53PuG02nc.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_useStyles_OC53PuG02nc.js\");\nconst s_OC53PuG02nc = `:root{view-transition-name:none}`;\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"2MBGrmDRf08exH06a02qXuAr5BJMerNtMtuIjUgfY2iCkmjOU5yFS3W0UZXQALYSWFswybKasbR/dEiO5MgT6Q==lTvI3SBsp5qpsOGTAqaJms+ek95SvmIjouTSUgGFEGQAZ9TJRxzZ10UCX5jR57QVMb2Jx3dk0IpUt0my5R2zSA==MrEjke+OsiMTbyTclw0KsEgYMAs/kAjmb47wgpVXLrspmoMJGXAX4Lcl9LBab+ExLuUWk2Q0JX0HmjadbQtScQ==r2tV3cTLb2Ro0hPZbeqGdq6z2GzlmzSJJWQk3J8kyG+VLSh14IVQx2lfNb7sBkrMyX+qGdnqUPqTsfNv40HFNA==adfV5b8nKGOp4FkIV04zE7B8jTQG///xzc5C1EtQ/611Sb8tZI+rwr1HFTqQWgU455MtCCWREtQsv6RFDtWepQ==wKw3iPbq/+dnIabbWDXlXqlOq07CRTfMfo+b5ekeuSNlr5E8L61ec13oiW58dh9/MuCd5UA3sw2u4zSpcXeWeA==Gp+BweuLlI+mi94CZ0574wLTEYYaTFgQTXsxw+eKi8nOuPErsKU1RSU179MdVIjsJUxVrg/wxt/yojboHCzPgg==oe44xiJMtoEV6XAlK6XlJLfCloyrAWavpKw40C2bP63OPIKgZIgWS9CoZ+Fxv8BlSWPLS0+lSNx7URqwipXF2g==6kWzBgfi4xEA8YI5JOr/fKLrQL1Xz/hgmPZm013MjCbcvytAvxF0ZJKJFC5JApTN5eMVuk6kYI3RkcUvwnAYWw==Mb83XuqhXZMX2651e1lU4BMlPwXlbJ8/0NUMRmb/WSZCpYstOeFpqDK5S3iAn6h/eFGLEK9M8NzDXj+DjC3IGw==7juoadil7wKatOx/QiaSraxWepX8FgS5YapQ3tLQwvGcFpDbpIrnP4JJsj6pEwjBXXDXtQirgxQDOghRfIgbhQ==PFBtSskz4xsl09+z6tb1HaJYQG/w5aKVXxKsEa6+RqFECTXiv1FyPFGD24J8zVg57OaTUZr7ZZGYpj0JwUAbnA==OlHrALlE4mAAL8sXcT1WtDrF85q1VsgKAKd/E1OuaLThKOnMU5UW5fYmWBQDs+okk2JMXu9u17XXBRVp1NPiRA==UjEtYDF1pXKlMXaPY6TIqBbe14Jz2DCj2BH6elKr72TV9L9+HXvEmtDmly9KSxJEX7IKlPEaGiJl8YqJDHmq3A==GdpACfYpht6nQaHWe3Cmxo/rLfa7u7DqfCeU63uj4U6hUyTumIwEa0pcAkNenCFx7v44/gB94BFl9ZaJvPnfdw==kmAS5vKLeOdltm6iqb97kUuQFBkoMARis3xVUfrvJ3G9WlU5pGyAxZHP+R696BBJKfotwKjpUzS19NTYnKEprg==vOiPa1VwSuGk0nSOA7TKPpqHaokpXTJbkvXGJk/7e0hd1kcqX7iFP1Mj5lON+FbgSNJ46o8L+hQtozTb1xIOpQ==PCEcqgD/amUpZ207+qIlz8n7OSA/d94j+xIj5vJxCLvF4oE6WzA0afDP520ep1T96T18ml3fqdKGpX0pwnP1yg==YCrACZW8G0jco1eNyMjh9Ir7PqNus298NclrtA1uFQ627GOgmy46W5e3K2cTJf8UNAkQ9ufet8JryXBFIthdjw==xZXjtFtJHF2FISnuCG7t48H4D30unA6HxTpJMDoeQBMv6HlB8daMZF/rNFBzE4EkM4eB6oHbn0CjAp+nfsrIww==hKM5u1KOm19Vt9Tq0NKNN8AZ8D7ZA1BkfmSJl3latvnZ2cSHGva5lJLPt4Ze2EuAYVRBnB5LH6iae88pL/1lUg==tSLLXXSKeutMXS2bTzeUzyRja/mlI0ZZc9EQZ3GBHGg/HFZq4KCD6Lv0CxtfWHdv1LJp180KaaQBltayC3LbGA==QrNqRNZcXCBb/P6vy8R7QsHHqK7k7A3Bs/139zBJt44mgbkH9jCzi622rkjZCSHmPUDjkTW3nlCLZerhbTTg5w==n4GjdKaFO04bPfStjmO6quQxHjBbSN8iImb+q8duwOWRFkUJsUuw9oABSAft39ozb6hghdyJSCT6S1tr7W2sBQ==IQJcERc8sh82YVbljMORX9ypYt1VTt+ZcbG2Cqs58KlMhtxhbApkZp+yr9Et7a2NwYtcAx0BcTA8U85RZr41vg==eEnBU1CuwnWu9ngkhv4Bl6buDfVbeZa3WZZaQUXzIANPwX2YY6upfdTgzaNN/8/coE/Efm3/MnzKwVY/wOzMuw==v/k2Eg8oVVvukBZadNTAiBzYeD3G50vPEP2AYo4tvskYtnrNisMn4IU1HjLHdx6/xv+b3se/tELThrZwvxeQsw==fa3t2Tp265ERoNRFlNrtwyKXLokqZPHuLDSJOpdhpgnDfFMPsEsm0xES0xrch8d/pyzsPmQo2zFfBaCrWbuB6A==gCm0YFF56HayiOj6Um6Y12GJSgohTXZkv2Im4VNt2J9T6OwCJhpcDiKTTFpI5AoBAPdQ89o1kfuwYHTIkSkGrA==zrfvYmkcT4b5w3mmpokjSWgPL/OgMqldRWc/PzNzfJlfEXd381qITdDWjTflpwi2rQUJ4DIBDttZQdKTeI1U1A==5Mn+8z5lrRN49QhC4ffrz0tGzZOZPtInaSDBYc+DiQesKWK6X3h4tc6URXiAvjpa/ZwlOlaahCB18NfeAwMlwg==xGvRKxQMikBcclbHuoYNhgoXJlsqDy+ryNB4zCMqwTeBXqzv2lP27QeFFg+RO/vqP6XLA+kIOxwRNdFLPaPuuQ==nN4jXHNBurzoTXPmFYb9g39b9voJybTEa+eUzvSGM+jyNpAmjUUABmcfrlzBzLpP4+MX0aKBY3MeO5zGI+/t1A==LAzAraKdA8gnjFh2lbuGuYh+d5VLiDcDE1WWSmVe/zEv1txbdV3/c5Xy/6px2r74pHC8R2G95qhGHcFxOcGSFQ==0lPqAoNrVvS1agHLhDGCjFRC/B0T6g5nnrV+rhNECeTvYSRpZ5bMKWr5xYno0mejj7c0U9eZwvdejMg9F//tvg==1slcSd8Kk+WKBWHJ3QR7wpyLNg9hcU7OcsXEduz9D32U92VUb00ZIVbIujakQTIUoTdGTRxCoS9OE/ufPeJ9cg==nO63rGj12uQtzHJgn9n3UAxQDVcE3wRn6UX2yUFV4G/aiXuSst+18zCKj2a8cWGt0r3QpTvniUeVqyHDzbB/Fg==22A/c4ukBOpMQgU23dZhUR/u9OzjyDALLId+3h+rXcH2wxdUnUjIWGcfWN+zZCOJjm9D/ukfzHNalbNj8166xg==ITaph3wLMBNKm4spZP+JdQ5wyI/ESaF94InmsSHBLoNQfkNxmWOptHAckpWvpvoADwJo0Nn0MFnFT9u2OcD3oA==dNxQwkf0+fsA7SEP7SycaQ03QQWHm7z2CmjFUGKd3hnfdWLmGhd1VV7pjFSK7XoFXgDDab2YOZJllGAKKKwOXA==J7pBcmY84JYRtuuQKKRfC5+yz2inpYaf5UKfidtgJ5QYV56NaFJlC2RIG4tr6zsfAEETruqpZCsNAw9sYtoHuw==g+L3/njJNAjSJt1qCg3jvlNc1PtfzP68p2UblGRDNzRjnVoHEPocSYSmETJvFymZSX9u3LnaCVXU7f6SWBFnww==hw7P3qMtRR82OOHcm7o/PlNTPpHrg6gdTscXEnDj5reB2auJ85Qx1RtasjodtgB0Z3S5TcffTCJIZWPIUiiu+g==b7XqOBEfDbr2VOA416MzC649bXbArHjVsngANIFTSOvmiQH6FNFNEuaUsFhG/uRm+ZRGbRBgmav2BIDPjM74IQ==FUUwtXWU1fz9eK9LqSKz/bk9qcVTN99C6zEDQqH23Pzof9nul/q99BAwoVQ5TUtTa3dZH6n0GfCjrsRiqZzqYA==hGuH44yzpj7CNGm3fiA/JV3I2bP/clD8X5og3d5Zw1jpq7c30PExC9ebWRFHR1xwb/HZjVKsf5UqVIz+1DG5vw==lK/jjHeSiSdHM8e34lUX5l4cVB0OpohXilSoV19c64cUC6OYjmuB/e0ITlZ0Xqeux/Ecpd/ZUz569RhD4nSdCQ==XFEIPhcSX9hroC07ZMIv5bpDmtBaN68sz7mx9PhaM4hS9vHK3zWJD+U4Ej2LCCgTrHCYUhin2cCPkikHiSe9Hg==Js/q8ISd6I1ytUKQqjmNUgkBxdklr3FAekeOZih1i1gXASciKdyZ0L7KZpDaoJPFDDj1i7mtxfLk6W/SQwY9sA==RPfr13PeSuhfIN3yEMeqNAZDz+avyTQzacfwkE1lX3Z1PaR0TUU600BJEKxjvYcR5avvQOAX66BVi1F8HaIOEw==11WXLbVfqUPe6BCynbVHeqSUQW5bBBZn1xhCXZGNtJnIgMT/dzhQEXKYbk6aoi5A98SOKjGDjx5+SDuGsYELCA==ciQkkfk7Ixp7dndX6RwsWlEiVXhaEgw5TqEu2DKD9Wvw3pKkXe", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_QwikCityProvider_component_useStyles_OC53PuG02nc.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_QwikCityProvider_component_registerPreventNav_zQJLLuPn6rA.js": { + "exports": ["_hW", "s_zQJLLuPn6rA"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_registerPreventNav_zQJLLuPn6rA.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_registerPreventNav_zQJLLuPn6rA.js" + ], + "name": "index.qwik.mjs_QwikCityProvider_component_registerPreventNav_zQJLLuPn6rA", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_QwikCityProvider_component_registerPreventNav_zQJLLuPn6rA.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/qwik-city.js": ["B", "E"], + "build/core.js": ["b"], + "build/preload-helper.js": [], + "build/preloader.js": [] + }, + "imports": [ + "build/qwik-city.js", + "build/core.js", + "build/preload-helper.js", + "build/preloader.js" + ], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_registerPreventNav_zQJLLuPn6rA.js": { + "code": "", + "originalLength": 1070, + "removedExports": [], + "renderedExports": ["s_zQJLLuPn6rA"], + "renderedLength": 38290 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { B as preventNav, E as internalState } from \"./qwik-city.js\";\nimport { b } from \"./core.js\";\nimport \"./preload-helper.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_registerPreventNav_zQJLLuPn6rA.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_registerPreventNav_zQJLLuPn6rA.js\");\nconst s_zQJLLuPn6rA = (fn$) => {\n preventNav.$handler$ || (preventNav.$handler$ = (event) => {\n internalState.navCount++;\n if (!preventNav.$cbs$) return;\n const prevents = [\n ...preventNav.$cbs$.values()\n ].map((cb) => cb.resolved ? cb.resolved() : cb());\n if (prevents.some(Boolean)) {\n event.preventDefault();\n event.returnValue = true;\n }\n });\n (preventNav.$cbs$ || (preventNav.$cbs$ = /* @__PURE__ */ new Set())).add(fn$);\n fn$.resolve();\n window.addEventListener(\"beforeunload\", preventNav.$handler$);\n return () => {\n if (preventNav.$cbs$) {\n preventNav.$cbs$.delete(fn$);\n if (!preventNav.$cbs$.size) {\n preventNav.$cbs$ = void 0;\n window.removeEventListener(\"beforeunload\", preventNav.$handler$);\n }\n }\n };\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"j0Z+enwBjRets81TH8jlFftorzh9rbyGi5w7GpDNv9UqG2GNGh6r8zdvzD5DqD/4gJlHB9zc2HELHQTnywJsDQ==Id6AC6Xq4OK07bZRXfcp/k08umfW0ziC9wSXleru4SXcsVfVdoL/COPqlApjl5nZaMa7SSIxTeyTpr7e4ZnjHQ==4DXxH+wic+w9DhtK6rmskbebVmYiianA5X/fQ0duxlmqT2ECGi6842xwR9fpbedpC0/m1AplWhR1PXxWIfTkFw==VQHkojWigSbFnZu49BjOAMKXx5Wqp/x4wWjVyGyRE4u7agJfdbLXQFmRZUq86P7/+XcjTuBTY4h1ngyzOw2AuA==engvn4tkK590XI9gxxuxKY15mNPBE8yGnS4D04z+VQUWMPdljMGgsK0OnnhIB8CHCb1CKTVA/DbHRw2z1bSqMQ==8Vx21anIRErZn2cZGQIJEh3v0cPrzwt+V9UnJEBDz1p+uYeC81Hgz+uZXNmzRzheGjkNkI58h+zHiPwi/ldvpg==PhDZXXNbQSU174zsyYTfQzBvaXNV84QDQuEXg5gIwaGzE/8tv8Zpv6GjLLrrDNaDgTqZyE6WCTA/xiFC1+TvpQ==d8W1+h2/fg3TgeynwBARq8pfF/1rU1/EOahLmBtGBhlfReibB7xcSpSWcYX8o0l669uTX7d473J54zQMg1i3Fw==FfehYlAxHNKg4cIOpJQhHTlhEt4VTt6Z+PSSeN42UXg+Jp9eQxj6rlqAPWzN4ux2fHh3muKA/uCxkQD60U69Mw==jbuiFEPac8YGUM8g38gi8tCok7szKMIjn6pzsOYnb0uQmITrmiDRLBGeNrxJQuFYPNnqJJ4K8nL7JJcgcSgcRQ==mzWMPCbwIw1oNnCHgrzRKhii4SZ6ursE0UE8832dOEmkU91sZBqtgSK3xTgJK6AXXyPGFLQX4Nnx0qrrkTyFDQ==vGjXFoGpum5tdwy+BecWKr+/TMjKNwSSNdUL3rFlmvfYp53H0QKj9lilU6ROVWjpXbI66WbTFzcC/Sq5cnZ2OQ==VopCSvuP/BkYMvduxgx3Ad4mZBHEml45nfRRZXb3jkQD56RlK/o7qQEmLBzIOB8p9e+U0eDvUmINmSPpQNLo7w==wTGvIU8PRoDEGXWX2mvz8cqdcwVhZ7oBBlyJJftx5VGMk9HLTjBNpnOSHw9TcSu4anWSXYOX4RJ5ZVhV15DmSQ==3afJWQqikpxvs9sZ85UQfxiunzdDPbe4aM4Kikur55kOIgBpV+3hxkJZdjRkFcx3ygH7fmHAN9fWIzlK25ZoDQ==8z+rwjZrQRBf13nbzF0J4lvSICl4YtxSEoTMxgSpaQKA+nHNUGsWs7dv0pwYp3fBCIUYB6sZvFusgpPtm3KalQ==R+b0+qD+z8k9ihu2Pd62TAr4LcLOO72MYS0UMU/wMn+2Lm5iwmp7CVb999q79xkN0yEIgQJAVY7RlgVbecbbZg==v5MhPX7FAyzoPsj6ADuRZrxHmiODW7tAsrzL8TlSd3EVHsajO18RkI5i0LaVr/WaJQ4TSz8lss0YQEj2crb71Q==nxMPUg/vABuO8+2s5p6r2ja8luKnmOv6U5qIbePfWfHJGq50291J10liIZ+7dWprHCZuQcOHKFGkcb0OnGJYPg==7nl3u9kuUvKQLO5oIlvY+1elkVMzOToctGqMMdHWWNWPkm9zN/Vo+qJycEqUOrnjVmit6Qo979IVW2VULPtVxQ==0TZIio1OIvawEju7xm+nxpwjuHpAyW/2GtkEVE62lMM0QKeOFnRyrNBDhcUFJxGwnkeCOJHWc6hTFuiOPqH2cw==RSeWOdBJp3fxwJ3SpPlMuy9KqPWBtCTCXCRRG8Acutb1mXbTd2X9kyI7CyCVtsod+SLtMx9iJr2ChXkdt40mSg==7QbzETbldlD4Dhm2s5IaZjABafr4XkhBAVHFm70lV+/4mINU60BKsDI3Yn7WV1N/uL2BuYCymaCn4RdrrGTMFQ==Rh8MdhVzQzwPP5jl6QI6lYUvfooX5uusQPdADxbb/VmMbRLxDfttfm6zbfQv7EVwirC02LWqREJ6bx28FGy8vg==UsyZZQQ+zIO1dqOYnaGu4HAco7G7OONbBpqlhqSs4wXdqdMPrwdWNf+KIW0Mh6wPiepEBKP/fFECGIgaVhkJaA==4Br05rMizL4ja6C9cjGZd34Vr1ikwgkT/IeoDJAp7i2465wDTpN1bPej7AhZnTctbwxUWjyO350XRWsvmauung==ip90MgPtnOK4h6ThOxpVXTD67fcg5nXw7cZq92XpIhgRkIZV/vaRI9iPAFm78z23+XJ3KBSzc5Rd5qxG+OUm+w==9RtfDiwpE3XAv7syfWGz+nl5jJntvppqV4tnGclvpeMQlTG4FtjRpi/UmcN3kwSPh3H8NDG8E6OI4BwIWVnuvw==ORhR+ID1yWl9IaV9PqZQcelD6KVUsjv1QtJatVCjgfGeITL4ziDX2LmB8L+t9teNUdRMkM4MihdGTPz/9l9m7A==z7OM02FMcnxm390ou52AzlM3pifFJa14IbOsygCcp9haymMNW24OW4v3fgKOcGwUNL6UFEZ5NQcMWOfwQTB+Ew==RZQwTu4kcFH/bqsPWvZ98aNHFh39H0YUzqMvEF8AxDOL7LJYB6DMmypweSq/OKwhvkhV6VTphPUiUYcg7U0LRw==bne4QlpsXiMSdGojIfqk10tRW6w1ygES5kik/orzwf3pwGG2aCTLlqGOPs1Lxx8+G3Dxw4bgRX8rxC92xERj7w==TFQC5slj9wAhitEdofurbzZgJQKmRMibYPM94OsVpuP9KAoN6xidRcBOJIqWWj1rUJ7a6G1O2YdpgE+HaCu5OQ==to2toreB4hwl4tldN4uA034B84CA1s7sTrDwrKINlpiIPzn8uRXa7pF52eJ2WGa+kCiIYA09MNWFKroPtDJUXA==ePKZLIJj+R0Aj/MxvqZKQxHJ2H54dtteSH6ErZg99nHUHFFIGU+zUUgv7Iz1bsRRMK5Cj/Psttq+v0cZ+VBAcw==tCgtjq5DjuKppi4gVcPzYB/YAkC9lFd1L7aEWUgpMABX7aQHFyuE1kVHxxPkamsIffGOPqWhCG72qPt9SRea0Q==eDdo1jQafVO7+VpjlgWWik64479tip9N8Hr6Dvg4CwqrsXu+Ep9lskND16wF2sb/jh9X4VhlsoVSjCherFEYFA==4M1HhiMmPf/gr5NJz9Hzw2yGkl8FAPNOjRTgFvf69ngv/EZZ5SIPTPThvQOkgZkAPCps3lhL18fcc3PFUXrPZQ==qXPkWcvWdkdaXRtcEme6YghKbbsd2dG4+387t1oKRCxSsbQRxsfBAq5L5Hretq6PHtmxrpFedwQumaHxA16tjQ==Nw8GokRWouNqlXj7LZI/TIODECF1rXSSbGjUrVd98QdyJvBkjybZmU13S6eQoU+F0J9m5jgKWW8uevYDXP3gDg==ASm0U9dAziW5D2/hqPs1Bz8pgSH/tEkfKDbxBhdT3d14dTH3hu1GN7q+f8SDdfRyz4FUo9XHF8e7488KBTYKqw==FviUQl5gYnL9EORAnFwPK", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_QwikCityProvider_component_registerPreventNav_zQJLLuPn6rA.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_QwikCityProvider_component_goto_IDVXhQLfmNQ.js": { + "exports": ["_hW", "s_IDVXhQLfmNQ"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_goto_IDVXhQLfmNQ.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_goto_IDVXhQLfmNQ.js" + ], + "name": "index.qwik.mjs_QwikCityProvider_component_goto_IDVXhQLfmNQ", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_QwikCityProvider_component_goto_IDVXhQLfmNQ.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u", "p", "b"], + "build/qwik-city.js": ["G", "B", "j", "p", "E", "m", "n", "k", "i", "l"], + "build/@qwik-city-plan.js": ["c", "m", "r"], + "build/preloader.js": [], + "build/preload-helper.js": [] + }, + "imports": [ + "build/core.js", + "build/qwik-city.js", + "build/@qwik-city-plan.js", + "build/preloader.js", + "build/preload-helper.js" + ], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_goto_IDVXhQLfmNQ.js": { + "code": "", + "originalLength": 2947, + "removedExports": [], + "renderedExports": ["s_IDVXhQLfmNQ"], + "renderedLength": 11786 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { u as useLexicalScope, p as _getContextElement } from \"./core.js\";\nimport { b } from \"./core.js\";\nimport { G as toUrl, B as preventNav, j as isSamePath, p as isSameOrigin, E as internalState, m as QWIK_CITY_SCROLLER, n as restoreScroll, k as getScrollHistory, i as loadClientData, l as loadRoute } from \"./qwik-city.js\";\nimport { c as cacheModules, m as menus, r as routes } from \"./@qwik-city-plan.js\";\nimport \"./preloader.js\";\nimport \"./preload-helper.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_goto_IDVXhQLfmNQ.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_goto_IDVXhQLfmNQ.js\");\nconst s_IDVXhQLfmNQ = async (path, opt) => {\n const [actionState, navResolver, routeInternal, routeLocation] = useLexicalScope();\n const { type = \"link\", forceReload = path === void 0, replaceState = false, scroll = true } = typeof opt === \"object\" ? opt : {\n forceReload: opt\n };\n internalState.navCount++;\n const lastDest = routeInternal.value.dest;\n const dest = path === void 0 ? lastDest : typeof path === \"number\" ? path : toUrl(path, routeLocation.url);\n if (preventNav.$cbs$ && (forceReload || typeof dest === \"number\" || !isSamePath(dest, lastDest) || !isSameOrigin(dest, lastDest))) {\n const ourNavId = internalState.navCount;\n const prevents = await Promise.all([\n ...preventNav.$cbs$.values()\n ].map((cb) => cb(dest)));\n if (ourNavId !== internalState.navCount || prevents.some(Boolean)) {\n if (ourNavId === internalState.navCount && type === \"popstate\") history.pushState(null, \"\", lastDest);\n return;\n }\n }\n if (typeof dest === \"number\") {\n history.go(dest);\n return;\n }\n if (!isSameOrigin(dest, lastDest)) {\n location.href = dest.href;\n return;\n }\n if (!forceReload && isSamePath(dest, lastDest)) {\n {\n if (type === \"link\" && dest.href !== location.href) history.pushState(null, \"\", dest);\n const scroller = document.getElementById(QWIK_CITY_SCROLLER) ?? document.documentElement;\n restoreScroll(type, dest, new URL(location.href), scroller, getScrollHistory());\n if (type === \"popstate\") window._qCityScrollEnabled = true;\n }\n return;\n }\n routeInternal.value = {\n type,\n dest,\n forceReload,\n replaceState,\n scroll\n };\n loadClientData(dest, _getContextElement());\n loadRoute(routes, menus, cacheModules, dest.pathname);\n actionState.value = void 0;\n routeLocation.isNavigating = true;\n return new Promise((resolve) => {\n navResolver.r = resolve;\n });\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"bHxC7hLiJvrhOzOq7m0DNnJLTHf37hO+hFsb+kfliTdLQ1JufvCCqjuSV231DVLJpF3dQoOECZ8cxRGNqLwhnQ==VpdInLoX0o9qLPhVz1rUp1W4GK4nMdHo1kJotuQDdbtgTR5kd+gWEzLIv2IAuJi0fiXhfKh6c3EuB3MBj6UdfA==qnwLQckDIzsODkfufHShkSPa5f1NLghGDPcvdIQ/0sfla+PVBslaf0e/Weggw47QOerqsYEMNWlt4i6OVTlW/A==AokN4lJjrfmywO+hS78YnKHKwAqFpxj7l9utiQDfpeuho3YWao5gR22GNZWvPG/UxANAF+9ZRTeXw42rsthyVA==lmVSqa8gVNSqQa8iqMHDD6d51Xgyp1WL/XhTVo82KKYeeeeofAiWPKWEs4Jpdsk7Sn/l3BZCV5EEGaL6lb4E9w==Rh+MyoUWMwP8h5WvJfHaCFYzgdR/e4kya5ACojUf7naf55Gu2IkwJoMerp/3+w5EMXdXL+K/Qtnoc8ghESOzoQ==pHkiZMNVEIDxtlcVIGwFpH5xmB5CnoZkuDH+nwTOLuQL74+SJDQR435zmVl4xhSfAkaldTSWu/MqYs9IVfvwVw==MBAbRLgzL/r/0qoTx+cPg6S2w93Lb29NHZRygiM1ZN7mpNl6mWbsQSnDTVCs+EljgQSxZDYSLh+QbdEg0aBdEw==3BSnzalBDRDxX2M8y+2HcI4qp+aqHkh0djcK7Uf5pP7pmcRFrLcV1hIJrrt29yRrWLTgOa4jIBvvpfoC4ZlrUQ==2tSf8JpC6sciCEXmsydCOJFCG2qcbDbYn5P25os6pbuMxvk9B+C6s43MtDmhmtdb0v8NySUXObF58FSQyJJUBg==bwbR/EdYPRDb7kP+byAGd681yK1rRu3pXi/VOcnKhkQYffMg9FSxL0xS98052J/RseFSsYe2SZ2QrVLx1h7D2g==tZZhKs0oIrT4I8I0NCN0PnMpddEENgW3XOB54338bZNTavTyxdP9+dGWkLT0DN1r4SVIWauyIyPu0Ef4sVGySA==wPQpF2Grr9seFYDV6/L21CQlNjE1HV1hUVd/29+JdEKxqznMAifG38erJAGfG1x2g+qyqlHVPkn18vwHapvesw==d/1WntXEKPuCCTAFVg3yvrA/T2nmmR8IliEOCa0cC3+04DAYgl6ernre7Em2r4QdYNQcs/XXy5yYnQcuCu2vmw==Ek9fTbCVTe1xqwDdXsWmxaek3U/2H4g/frzJqJDzAFUFr2ySK2ac5NAdunnCLr52c+6cCSwuZxFjox9/GkHtSQ==NNVW6sOKzz8HuoD4jy+hhH3ldc3rzIdIcY83BnKhDryBB06DujXKMxGfqsZJ6Dqvq/NGhprJcZG+E6NM47GTPg==s8gwK/kmsBEbHSKBZANKG20hBRpmQUUm5nHNydXpF8jnG6xN1IIvf+O9bzD0HqvyyQIQZdjbbr+MkV05UDa85A==ZxnUwSC3z1egK7h8t27bOawr3RpeWdcd1W8KZVbruMYJFBa7/1WGNmlpMA/8LWN1ePlbBRU+v8GlSmIDJo6lVg==A1GfOwjvDwAfu72SYU1XebOsPoakHNhUx493OLjeceZUWdujaJF5vriUdsfBmCL+h3x6VJHkIDMwOusohNw0HA==b27hfIXsqjF1c9kLGR/L9AsfQ2owRRPQEGq6LfY08DfOJxA9WrWg1oP3keenmgUezP7t0pXtII6YRS9lYroa9Q==gkS26YbOCylFk7C7rLfXIKOQ6dKU7W+oEXbUbKg/X6p7H5UlkdoGiiMqATc9fjIORlCpV9UGzDtUfh3MoLTypg==t5HzniYFx2Rnb82DMG3d2OgEute5Zx7bR/o/9tucYXuJBufJQ2uAv3UQ53k6he9r0blSYWv8M0K2OizH91OWaw==IBBqJYZnZF8vRVhweb/RZn4AqaoOM/w/IYNFIYbF98O2GXUlkql0fcxPkNYDchGOZGFAT6j2AUd+CBOzTNWzIw==xku7tmG7jIw0h6uUyJTRWQpKf1mQ+7Jf/8wJJAvMMBGzASQ8CKT6v25ZlfgIpkMphbKU9Ew7VGBM/IPk0BSYHg==r32dkX86guYZWuDstqowNw5l2E6Dk0zMvzuiySWSBCEQsPEMrOke84cQODIzYBUPSNYnV6M02WxsFGeYlAJ+ig==sfG6DJSoHPgnRY/G8Z6JuSOW1cHmONS+i6akW2ZAvTFzYSvQhP3p0K6O", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_QwikCityProvider_component_goto_IDVXhQLfmNQ.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_QwikCityProvider_component_useTask_n397ZlNS8UY.js": { + "exports": ["_hW", "s_n397ZlNS8UY"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_useTask_n397ZlNS8UY.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_useTask_n397ZlNS8UY.js" + ], + "name": "index.qwik.mjs_QwikCityProvider_component_useTask_n397ZlNS8UY", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_QwikCityProvider_component_useTask_n397ZlNS8UY.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u", "o", "p", "r", "s", "b"], + "build/qwik-city.js": [ + "l", + "i", + "j", + "r", + "k", + "m", + "n", + "o", + "p", + "q", + "t", + "s", + "v", + "w" + ], + "build/@qwik-city-plan.js": ["c", "m", "r"], + "build/preloader.js": [], + "build/preload-helper.js": [] + }, + "imports": [ + "build/core.js", + "build/qwik-city.js", + "build/@qwik-city-plan.js", + "build/preloader.js", + "build/preload-helper.js" + ], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_useTask_n397ZlNS8UY.js": { + "code": "", + "originalLength": 11284, + "removedExports": [], + "renderedExports": ["s_n397ZlNS8UY"], + "renderedLength": 10162 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { u as useLexicalScope, o as getLocale, p as _getContextElement, r as noSerialize, s as _waitUntilRendered } from \"./core.js\";\nimport { b } from \"./core.js\";\nimport { l as loadRoute, i as loadClientData, j as isSamePath, r as resolveHead, k as getScrollHistory, m as QWIK_CITY_SCROLLER, n as restoreScroll, o as CLIENT_DATA_CACHE, p as isSameOrigin, q as saveScrollHistory, t as currentScrollState, s as spaInit, v as clientNavigate, w as getContainer } from \"./qwik-city.js\";\nimport { c as cacheModules, m as menus, r as routes } from \"./@qwik-city-plan.js\";\nimport \"./preloader.js\";\nimport \"./preload-helper.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_useTask_n397ZlNS8UY.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityProvider_component_useTask_n397ZlNS8UY.js\");\nconst s_n397ZlNS8UY = ({ track }) => {\n const [actionState, content, contentInternal, documentHead, env, goto, loaderState, navResolver, props, routeInternal, routeLocation] = useLexicalScope();\n async function run() {\n var _a2;\n const [navigation, action] = track(() => [\n routeInternal.value,\n actionState.value\n ]);\n const locale = getLocale(\"\");\n const prevUrl = routeLocation.url;\n const navType = action ? \"form\" : navigation.type;\n const replaceState = navigation.replaceState;\n let trackUrl;\n let clientPageData;\n let loadedRoute = null;\n let elm;\n {\n trackUrl = new URL(navigation.dest, location);\n if (trackUrl.pathname.endsWith(\"/\")) ;\n else trackUrl.pathname += \"/\";\n let loadRoutePromise = loadRoute(routes, menus, cacheModules, trackUrl.pathname);\n elm = _getContextElement();\n const pageData = clientPageData = await loadClientData(trackUrl, elm, {\n action,\n clearCache: true\n });\n if (!pageData) {\n routeInternal.untrackedValue = {\n type: navType,\n dest: trackUrl\n };\n return;\n }\n const newHref = pageData.href;\n const newURL = new URL(newHref, trackUrl);\n if (!isSamePath(newURL, trackUrl)) {\n trackUrl = newURL;\n loadRoutePromise = loadRoute(routes, menus, cacheModules, trackUrl.pathname);\n }\n try {\n loadedRoute = await loadRoutePromise;\n } catch (e) {\n window.location.href = newHref;\n return;\n }\n }\n if (loadedRoute) {\n const [routeName, params, mods, menu] = loadedRoute;\n const contentModules = mods;\n const pageModule = contentModules[contentModules.length - 1];\n const isRedirect = navType === \"form\" && !isSamePath(trackUrl, prevUrl);\n if (navigation.dest.search && !isRedirect) trackUrl.search = navigation.dest.search;\n if (!isSamePath(trackUrl, prevUrl)) routeLocation.prevUrl = prevUrl;\n routeLocation.url = trackUrl;\n routeLocation.params = {\n ...params\n };\n routeInternal.untrackedValue = {\n type: navType,\n dest: trackUrl\n };\n const resolvedHead = resolveHead(clientPageData, routeLocation, contentModules, locale);\n content.headings = pageModule.headings;\n content.menu = menu;\n contentInternal.value = noSerialize(contentModules);\n documentHead.links = resolvedHead.links;\n documentHead.meta = resolvedHead.meta;\n documentHead.styles = resolvedHead.styles;\n documentHead.scripts = resolvedHead.scripts;\n documentHead.title = resolvedHead.title;\n documentHead.frontmatter = resolvedHead.frontmatter;\n {\n if (props.viewTransition !== false) document.__q_view_transition__ = true;\n let scrollState;\n if (navType === \"popstate\") scrollState = getScrollHistory();\n const scroller = document.getElementById(QWIK_CITY_SCROLLER) ?? document.documentElement;\n if (navigation.scroll && (!navigation.forceReload || !isSamePath(trackUrl, prevUrl)) && (navType === \"link\" || navType === \"popstate\") || isRedirect) document.__q_scroll_restore__ = () => restoreScroll(navType, trackUrl, prevUrl, scroller, scrollState);\n const loaders = clientPageData == null ? void 0 : clientPageData.loaders;\n const win = window;\n if (loaders) Object.assign(loaderState, loaders);\n CLIENT_DATA_CACHE.clear();\n if (!win._qCitySPA) {\n win._qCitySPA = true;\n history.scrollRestoration = \"manual\";\n win.addEventListener(\"popstate\", () => {\n win._qCityScrollEnabled = false;\n clearTimeout(win._qCityScrollDebounce);\n goto(location.href, {\n type: \"popstate\"\n });\n });\n win.removeEventListener(\"popstate\", win._qCityInitPopstate);\n win._qCityInitPopstate = void 0;\n if (!win._qCityHistoryPatch) {\n win._qCityHistoryPatch = true;\n const pushState = history.pushState;\n co", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_QwikCityProvider_component_useTask_n397ZlNS8UY.js", + "sourcemapFileName": null + }, + "build/@qwik-city-plan.js": { + "exports": ["c", "m", "r"], + "facadeModuleId": null, + "isDynamicEntry": false, + "isEntry": false, + "isImplicitEntry": false, + "moduleIds": ["/@qwik-city-plan"], + "name": "@qwik-city-plan", + "type": "chunk", + "dynamicImports": ["build/layout.js", "build/index.js", "build/index2.js", "build/index3.js"], + "fileName": "build/@qwik-city-plan.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/preload-helper.js": ["_"] + }, + "imports": ["build/preload-helper.js"], + "modules": { + "/@qwik-city-plan": { + "code": "", + "originalLength": 891, + "removedExports": [], + "renderedExports": [ + "serverPlugins", + "routes", + "menus", + "trailingSlash", + "basePathname", + "cacheModules", + "default" + ], + "renderedLength": 9122 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nconsole.log(\">>> running\", \"/@qwik-city-plan\");\n;\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/@qwik-city-plan\");\nconst serverPlugins = [];\nconst Layout = () => __vitePreload(() => import(\"./layout.js\"), true ? [] : void 0);\nconst routes = [\n [\"/\", [Layout, () => __vitePreload(() => import(\"./index.js\"), true ? [] : void 0)]],\n [\"about/\", [Layout, () => __vitePreload(() => import(\"./index2.js\"), true ? [] : void 0)]],\n [\"form/\", [Layout, () => __vitePreload(() => import(\"./index3.js\"), true ? [] : void 0)]]\n];\nconst menus = [];\nconst trailingSlash = true;\nconst basePathname = \"/\";\nconst cacheModules = true;\nconst _qwikCityPlan = { routes, serverPlugins, menus, trailingSlash, basePathname, cacheModules };\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"G/4ORRb61cfesL5zue430f+SWvoJAcAMuVH2QgJuPVErhWBSHhCFLrukE+Fjc422bXWaUWiJ4G2eYnVBHVAdlw==oqwEpJuUL/ISL2lxP18vvCUaJkywo0msYfcijRHXz0UBsP0XMeVVYHWLRIdg5s9JTmSqogtanKPAnHJBYqLYFw==U7Zidb6Vd9dNKQ4TpVBsEeo9N23RBLUT3nhYIzqhgiy0rtM+1medSSAtZ+6OYf6dTVo6PwsUw9V4ucoIbxoy1w==lwRpm2VRrfWT0WUVjcacCbi0sl6xAbdi27OTAgQilCAa/V+IlCQF0AWT0h6kLWLe9c2Ix1xjqxd+CR3EfKc76A==3mIZIHw/k/hsD9Bdky2qVPoQSsaseOiLZft6/dQfDtyHwDfmrpW6hbzwbeclLgy9cPYNkvkPimGUGQOtzOc+og==ff+7P2fGx3ADK2KKbUrgjWg4grMn0CfQEYku1BkpxfXD3haIc7BVDHN9BMGZtlOG9/Of/lPFw4Ld+jWZioU0dA==yMIYXcyl9h/7356F8Isu5taV2GhKGyqP0sgzWAzul/4mwan/mQ6kobqXZQ0n3TCCdNIl8y6RVpO2OSAMYKIehQ==OvvNjdcYUKWgh5T53QAnBupjF793ISyuVLQEw/5Nxzc+JccC+nyq3GMVX55h7k3MY2lFWeAn0wpWlG26DHOR0Q==Y3LCnqUey31cYVYA1TumobNalW25iiaMWnDIs7O2JbCrF7rbGuhIHX6UQU6vqWAWQUTZtXT2IyOFkRCcv1vc5A==NjlLYZmQLMTHhOaAHLzdZYsjkP0bgvwfEQ9nJAx2Te9oVEtRe+LFiQkkpSUwlX1DYWzpJiRdlypIP+9mdc7L4g==xkz2KMV1L9f/WJTM3W8evXg0jUCuO3vARwnJa0RZFqiT/SeoN4hb3oWPw86nngc7ttHmTPUtLrIJvKlJLnYMoA==wg03DU5vMagUDcCk7hNO4oLy8PAWuX5Ol0P5GrgRlVC4qRiNCeKmLD6Z+qJAnppothslb2ICrMMvui1Ks/khWg==CuFzAf65/n03BU4MaRuZIMFdW2njDLbNVWegOSfVL+QU3tFuiSBEsL2ASyiyEqNIeev48+XE15vm2fEIC/Quow==HxErOqFdcC4c/f0oYX+m3jrCsIvWM3sSeo0C+9ZqHyiirI0ODAD6O9lx+lP/ZUahXAUBhLuRcepmiN0kkfmr3w==t/kJRE0GvAOPQgWwVWPTRs252LsIWzzJ8ostI/Y7jZIjbXg67SLx93dH6IgvdyXk9LdyYsSvYuMEMPyVfZ3mdg==XuTd2TQftfNzWjVXgZsKIEfpXAFh/UE5WxIOG+Zw2LNmHsArGbZ7FhDjkI75bTn/ExGmu5uL149JFk77AmvfDA==wMzBvSu/VepM4VXpMTXNX/FZDUI1ILg+hn31cx9uVxLBDILDAzfCmJ2lEqbtSe6pSgWclHHh3I6j1sPqabVbPA==7X1cWwyJWRo2YstbkxnU6YIVyN/PrS7FXMWejSznstf07Biwq16mCzkG1BSBAOwd4mW/Alxndd0yJRPgO9JBWQ==g6aif/6eikoW7hNlPxe8yu+MyBFHApgMyYIIXFoLK8q4f1i5RhkoNyQMgxbCJYn4Em3XgSvnQARzSdvOrfaPFg==9KhS2XMbp8cYYoyg7PtoZDS//kovTrs3CwfA1fHVBdGjhlHJFnyPUz+iID3nLFysMd4SwOnC1xfgQ9Dmxllraw==fYS5tWOzRFOHm9Vt72kENjjOhj75QGN6NgNaSQZJdcmpUP0/2nAY93aNLN29Imbalr7FBpNk5f1lX/xKuemNpQ==V4gR9b2szvLP06Edf163Cg06prXD+bQnswK/LB+MIbQAa04q4VTGONUM8vodYVa+WZsAZS9+ibkAoNrtWFwIlQ==hbcb8pZeHRFAf/GFF69n534nGL2thh3/n/+mHNaR+fSgQr0nh1Ijxj6AjpDMmlWywMCyDIdyi7nN6jqLdbL85Q==PDZcULXtlaMMSI/DTVFOl/xJgiPyPJpISwh3b+bRmaFDjlVNDHfZAM9x6cc5jYC+4lABkbrSDGHK+GHq1Le+/w==N39Yv1vbYNvHs+ReHYXyKUg1V1+4mshpFnQugYk5zGRG2PNp4BXHBtTmVRN1IcQ3MyGoWpTmCGQf0f3YJzA9Wg==rz7YWqR6vtsUlI2fLTxVwLs6OrbhdINEogq553OsAoRVGUIKslhoiLjw5E0+TowXeS3xF20hazCEZWncf88HbQ==PGa5gbBoElEkcAh/dHONHeQ6D7AyScpS/jgIE3kVQsPaimb6UHwVkXn1mX31R5iB15DcruWnEz/UoG/gTe303Q==u8ZHI2xioCQoblHQbqHZdjS9YmUBfKfp7zJ838I2+Uc8RwIopPBj/UUIlucEhMZnbk9pCAwqq05f4hvIz3kiNg==fFySYyy+C/C3widRmXeoZqmf8ts5r+TckAlg5BFR0foV1+m8Y7fa02nOVou2+FIYdszvSmSru21XShen5+wkZw==svHRfYwwH7jE0asln6cNBFdjwaYHIly3qiDK2455Td33LquTklp2PHs93xtvxzzqQrAO9p32fA2kNSdVUIRHRg==c6yzXapvAX4vWJ3vUzkHv8jkAhkf9UXcPQ8oyoUbD6QlZ49C8NyUXI/uT9G93UPo7Ng9VHl5c5GmIwDvsR2ijg==xbzoDbsgfphHgArXBeJ13y9MKjuz4G92uCa2LjkB98WZ/uFgJ1EaMifog+cyM9Ss8TU9u+6BAy3gbXFlXu5yjg==5gpGSSJWN2q/E4QevW+oVSelk3HVWwzwHiqDEwpuWGSI35HQlgSaayLLi+6yYwfBTePvLMCCf8TXq/yj+mvX9Q==1DYhMN2mMurhHiUkX0OIhKfFCurwVhQvXwIGyQlskuDJAROL7fNpjNNeQrPTo2RNpM+XbzG5BUGFZvYh5AG5rg==xC8+C4lSK/hUyDl3pfOKV0K425l94TmRZZZFtN1yFK8zvTk26BJSSx3MHO2nFOHMAwHiaE3+wZh+mckhdSNFOw==bjaZcRJq9giYPEZYI1rvM1f86TLNGgLeFr7dGm2n586ntDpE1w5/jtNts+QfCIGocS2OXwCtFb39XT7cpFHajA==lPMfOaLT4BoFeUh+DRJx5DZZdPXGQaQNpEolkpgoLvMXBZ2mraWcuPuNyPY+4tcLv/X7oaeH40a9lfkvnyB3WQ==+hHXvDKa/qBFIJjJCtE4TWCmAsTxTOcQSHT06RWhNOMvHGJ8gPCDMd00h5MVrMc4/zku5gXg9vuzI0b8mI4V2Q==NsLqXCUYCXHktk0nP4rfu4/fp/7+cIDvSHj1CClRNHQlTwW51OYdWB2WI+YchnrRcE7Degs2dlzXoMSIk8bOSQ==7wIhq9P1nb2TmGAnMAxNV6Z40pdzMqyhMSnRxVtZw1SD5iswxMchsn9hU+AJ2Zh3kRRrKWgORbRMfrveQuXcPQ==GilPq9DJv0T/nUhhbsncuQ/ReN1ztuKNdMCg9ZSkyd1q8adM7kDR7R0PBYKsF+ycDyDHiN6qFh2VpGXcMtwP7Q==leobx+ARey2Jt4+58Btytpi597rG9D/NogY3GkWlSW0t6+h83bPrdHbJAKr2GfcKS1hxKvGp/yEa7+xmr7aYZA==EOzx43B2fZvTtJSGCqU78Fi5JxALmRa2ifQ8zQOUyYj2TefyprAsIXMaGCCYB2nhtzZAjQ8q7yltwIFoGeLjTg==Aj6uJy0a36HSH0VBtDrQTcZKVkhEhpv2KnuGpMjKEcAfS9sP8+DXELOesWqYJ5EqOG/lzzWgkLNeU/GpR2etlw==9W6F5hLa8bizKCDqc798anuV+rTvZzylpqFD/rk2Fykc7oH70TpLUPcv4lXhtYXvHhHpYZbayHAmRl3ienejyQ==DEGEZ5xpLN0+LLznUQXtAo610zIga2/R65auXO7Fj8QphKRr9Fa6MZ4hGvYFiyi", + "map": "", + "preliminaryFileName": "build/@qwik-city-plan.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_ErrorBoundary_component_VJVmS9CcP1U.js": { + "exports": ["s_VJVmS9CcP1U"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_ErrorBoundary_component_VJVmS9CcP1U.js", + "isDynamicEntry": false, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_ErrorBoundary_component_VJVmS9CcP1U.js" + ], + "name": "index.qwik.mjs_ErrorBoundary_component_VJVmS9CcP1U", + "type": "chunk", + "dynamicImports": ["build/index.qwik.mjs_ErrorBoundary_component_useOnWindow_0s3bilOgUys.js"], + "fileName": "build/index.qwik.mjs_ErrorBoundary_component_VJVmS9CcP1U.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/preload-helper.js": ["_"], + "build/core.js": ["e", "t", "v", "_", "F", "n", "q"], + "build/preloader.js": [] + }, + "imports": ["build/preload-helper.js", "build/core.js", "build/preloader.js"], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_ErrorBoundary_component_VJVmS9CcP1U.js": { + "code": "", + "originalLength": 804, + "removedExports": [], + "renderedExports": ["s_VJVmS9CcP1U"], + "renderedLength": 18888 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { e as _jsxBranch, t as useErrorBoundary, v as useOnWindow, _ as _jsxC, F as Fragment, n as Slot, q as qrl } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_ErrorBoundary_component_VJVmS9CcP1U.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_ErrorBoundary_component_VJVmS9CcP1U.js\");\nconst s_VJVmS9CcP1U = (props) => {\n _jsxBranch();\n const store2 = useErrorBoundary();\n useOnWindow(\"qerror\", /* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.qwik.mjs_ErrorBoundary_component_useOnWindow_0s3bilOgUys.js\"), true ? [] : void 0), \"s_0s3bilOgUys\", [\n store2\n ]));\n if (store2.error && props.fallback$) return /* @__PURE__ */ _jsxC(Fragment, {\n children: props.fallback$(store2.error)\n }, 1, \"uY_8\");\n return /* @__PURE__ */ _jsxC(Slot, null, 3, \"uY_9\");\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"4WJOXVcCa3fqYhepZgOc6coCyvHVuyt5ASuTEz4yezdI9irNg7RcVmPxq2NDA2O37hMrchIlAHWNjaVecYMmNw==nhyDJOta9C28D1zEMOS4iXhwM6OA1QeL7qJACK+j4s+WBEQA83mPXYP8Zm5ZmCuSJ3yDk2v9HNElbGI4jfJG/g==gQSV1/FyOdQqkp8TtqmPjjdQynHROKVnkEkWx5AwQHZgZMAn43pMa6/yJvpUMC5bM3oWXz70vVG3pXPMlNHXtg==JQazn6K1hxNejnRbJwZ9hVnNDX6K+jVhkgPnUvnCQaZ71OqOqXlRT47qLbw1hal1m7F43Yid9zu9fcI0F1vEhg==/s8kV5Ej18oj11+Gw3V/vPS6QYliSYF80yANLiiCsEQfZQh0g8XnPkrdWWsLDg9PIJ0SEjbWy6qDKwOUXqeq9g==bBfhmG8CulQgw+PgBZh1dKQWASNNt5KGNhxyu8NYMKtQYJIfvKoszmU7yTXW0/vrdTNh2XhDkfrz2FEWuec6Ow==p9CFflf91jceLETpafo2s468yIRfjic0vDTIdVsQvC9JRgG134vm/ZPUZTdQgkhpTjr41eX8zFd24pqtXFDCsw==VhiGW4e+Oi5mJvRZqEFLOt1I1DToUEfQq4hLEjmzycQUi4RUDW7VzwFnmk/eBg56lxY4q9aSY+tJPdU5XeIQug==Ghbfxax3i2+YhBOSwuUAgLTwe365kYd4coh75q1OSX3NGBF625qzMNNKz0sVqCIfi6Svyj2BO+RDJdb/C8LPuQ==A9NNNsi66qS7n3Jbepyl18BeN27u5HpqjfXGlleiBvBGbkLYNX3DCaWQHeBf78efwuBsdkwH/YlyFtxpioFT1g==34NMox0fZXJIPr4yM+fUeJ6ts1zawI0Qi6VwMW+LLdSUQvMGBcJTbERuV9FyE/gFhzPtzNp4iKHNYDI2ZGaMZQ==7HbES6mfdzPAOgFR3AwyMU7cdMP21APn1pSmGo9ceYweh2aiWMla+zyae/BtkosGXxkoaDjPatuCcvsxaufERg==tYxbiFLCJ1Lkv7ywy5h1PtUW8RrNSlWv2xTg2Y0jX/sa4CRpS8BPECrVytZ7A3bUqxrokJd3AtNslHJ0StwVEA==jmkvEb/C6C/+O8ey8kY7lCzljhODgkQxnwsmGr71Xj85xwarDK2aXsaxy7tpGEhPTrr1i4we/RjE9owaX6nFWw==bdL2/6i4jRIsHH2y1o4b0W6aZh4xyTgkCH/sj3/36RdYdyVABqof9dgnESuqfR4rN453L+vXTaDpaGp1b8PP6w==o+xZbhql7fDtv2WZRdIqK4TyEAs+XKi5+9QHVVbCwcYSZGkibVMVQl0qENHuv+dILZyAjL/Pp1/97huc6U91yg==QYHR8u2uP97HssUhG4DDUui0/bRC+8QQqbBoclhSNgHE+fBVx8HXdDTQl4S9i2AK0psu5d/JKFyyBVnAVqSreQ==pJ5UqB0Go1g1Ir/6lU+PEH5eI1OpC2rvBfYMczSWQGyxTmHHCz0Lr62qnJbSWwWnZnU64rk2nUIl1UJ32dKxhA==IEOgL/GFCiE2C5EGH8C8kYADVxH5uhTaI0NFURf53MaRzGMfFpYTDniGS3DFXGLbmgk2tu0D/DcGRi8supGJOg==V+fzWC0efZOrlN4711KWEThyj0Z3JWPnMXu8j1zW9hjbOe/3VL+FRJ2YIxp3L6pt4H63VRD6pp/3DODa3q/CAg==2eCQ7F6AfuTZoV71n7Oyfx6zd6m/ndlpIeEhRvq7rLoh5YnuTSoYyCUmnZmJiSHWdBBaLHs3Ukd9IqzeXh9tXA==RwckEguL1A7fHgdmLFa10h9oSiDk/gpRc6smCBvSZBiMptmYdRCgPvO4NRP5sI9pv0BN8CwBR1kQCJ+FhF4gwg==0dwrUxja7qXHW4yrSuJt+tVj+vyaV1s+3E77EE/Lxe9BNMqM1yB0crkkMjzpmo+aQanLE77E+UagqgYWeUx8mQ==RwuQqlGNBydWAANngrdRxavttC1sctuKFFbORADK8wrBPh8QYRpPX3jRFbxhoV/8LQ95YZ6xbde1nHzob38gMg==lYMl0erU3HPr6YTkilFeoOYEAscEO5/Cet0Wh7yXEyFD0z+8zuuEou35tX9hDaaVKBsYJgGYPt8VEUQ65eK6YA==wElmZIwxXRRGXSMuGuMIeRiCdC1UXUXcdq+sWPwWIuwjzXkctVxVeO5rn+fjQakuW0JNIEd8jYM/GEHunZby1w==achrcHGmyNCyWBcboEtcxNsIgoG6D/65HQ9VBmR37tjXbupaSjribCQ4cFPpnTyiIXNUmeL3bMWRn7Jvy3whFg==QSG2HEWsvXTUf4U4UiMWWnBitJXQYMdjNuLBo420UM/QXWCN58hlIF8J3R04nFfjBMBx0fRtqryl58TQ572PwQ==uZkyjNrSKmmxwwVYy5MOno1jGpJYHNZK/7wKMJd3mwQ3SGqCuNYlG+sQk/ZjnVeqKFO/pYc3ooBN6HVrFFz/OQ==2l04YrqNLdPDGGDxLIOdqWqhKoaBRw2tsd26PA9LVN1UUOXRDPX76oMDJGQzfC7KK2KY5sG7LNFdo0HHYxqXrg==iR30BxPQhGNRB8f5qjgvfc7ySczw1UUnnQGT0BgNaHD9JzW8oFKwdL0/nfZUqFkHG5SPHh66adnxclX5pQLQXA==nJHRnfXEWYVClXjKG45VAeAAtKrisYov5LwimJ5XNk2VXtFJ2xiFUUq7msZ8m13c/OW6qv/IkU1lBPz24H1zyw==D3XNTqd7+0oSvbsPyIeQtaF5sTOOGXYihX8YDIOvGvjjVL2V0is6GkdouNHphOleUzWeDK3Gc9x7shUloWKPcw==nXjHlw3MiXxAbAUALCSRPcakFi1QwpVMvmSV8A0w8GcMhc72eakjUKe3DqtWWGrHYXUGAQgaGiEjYOmE/68d2w==dccIYmESuuLsPiezSvcFGxfkpf6okptnqvmkUlOQSxkZ28JumZ1JLR2z56C6r+rtGMKvRudsuHI38Gc2lJRfDg==QkqyQ31LZ0bq6fxKWNFFhEpE0831tgtn8TUI/nB2vw3ireL8wY0U/bZIH45BKxgK9Cr2HP1ljy+KvkcReJVeUg==6YTgCzg69gvS2XoNeaaGeoQE0qg7P6k8aZQG3EJp+ik2fVI7e4Ez1hpmks2oCbvKzYGpHQpOmty6Mb+7kftl9Q==ZxD/4/qEZI+hQn1r+narFhUFHFVEjBJEyHejG6Ec40JiBY7a80XKPfGqXrj/AAt3xqS18sunAA75yORraz+9Ag==fnQIJ/WaPLvtNz9sZUq2L2tdSW+GYfQxPDmzXai7TC63D6yVYW7t6FkgoljCo5xdhKGKMCmIUJKbq5VwmfnCVw==25oDbq0SQG07S6J2Unp4ya9jzkTXVefvWHm/izKm31d2NTBYo82H9O0kmTJrOSzHAWCYdWuKu0/G4xAwsLTs8Q==hMZXrg7UuDhQGseJHIdKnDgQghx6BZKKXrU4W1ZaQgiRNOxZFuKnc/9jNghV/60iqzXvHElZix8rFlPD2LjXBQ==j1N0KVO1T4qzm2GxhBZ4Dl9lU1ppGzLdTqwzqvvU+QOHVUSVHYIDhW2cv8O4rNGc43KoKKuOotQXD2jy7/asaA==b2YbqF9nSkvfbjsRn19fb8qYMsrz/pBG+iTaV5fpq77HE4KkRlysZ4HC8o/LSa7OqA0llhrN0rDUtWIkUdq09Q==yUOLcWlk+hQEQQCN0j9qQpZnatJ2vEbI2F+6MpFEgYoKn+bxFaf+p/zUgDt/aGMn+g4KJ2xPkE9oh7wE/zdpKA==fkOQN32QWFMDmNtsSxhAl3rtxS50UgxCstkfwBn", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_ErrorBoundary_component_VJVmS9CcP1U.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_serverQrl_rpc_AaY04hL3w3A.js": { + "exports": ["_hW", "s_AaY04hL3w3A"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_serverQrl_rpc_AaY04hL3w3A.js", + "isDynamicEntry": false, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": ["/qwik/packages/qwik-city/lib/index.qwik.mjs_serverQrl_rpc_AaY04hL3w3A.js"], + "name": "index.qwik.mjs_serverQrl_rpc_AaY04hL3w3A", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_serverQrl_rpc_AaY04hL3w3A.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u", "p", "w", "x", "b"], + "build/qwik-city.js": ["x", "y", "z"], + "build/preloader.js": [], + "build/preload-helper.js": [] + }, + "imports": [ + "build/core.js", + "build/qwik-city.js", + "build/preloader.js", + "build/preload-helper.js" + ], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_serverQrl_rpc_AaY04hL3w3A.js": { + "code": "", + "originalLength": 2932, + "removedExports": [], + "renderedExports": ["s_AaY04hL3w3A"], + "renderedLength": 11027 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { u as useLexicalScope, p as _getContextElement, w as _serializeData, x as _deserializeData } from \"./core.js\";\nimport { b } from \"./core.js\";\nimport { x as QFN_KEY, y as deserializeStream, z as QDATA_KEY } from \"./qwik-city.js\";\nimport \"./preloader.js\";\nimport \"./preload-helper.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_serverQrl_rpc_AaY04hL3w3A.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_serverQrl_rpc_AaY04hL3w3A.js\");\nconst s_AaY04hL3w3A = async function(...args) {\n const [fetchOptions, headers, method, origin, qrl] = useLexicalScope();\n const abortSignal = args.length > 0 && args[0] instanceof AbortSignal ? args.shift() : void 0;\n {\n const ctxElm = _getContextElement();\n const filteredArgs = args.map((arg) => {\n if (arg instanceof SubmitEvent && arg.target instanceof HTMLFormElement) return new FormData(arg.target);\n else if (arg instanceof Event) return null;\n else if (arg instanceof Node) return null;\n return arg;\n });\n const qrlHash = qrl.getHash();\n let query = \"\";\n const config = {\n ...fetchOptions,\n method,\n headers: {\n ...headers,\n \"Content-Type\": \"application/qwik-json\",\n Accept: \"application/json, application/qwik-json, text/qwik-json-stream, text/plain\",\n // Required so we don't call accidentally\n \"X-QRL\": qrlHash\n },\n signal: abortSignal\n };\n const body = await _serializeData([\n qrl,\n ...filteredArgs\n ]);\n if (method === \"GET\") query += `&${QDATA_KEY}=${encodeURIComponent(body)}`;\n else config.body = body;\n const res = await fetch(`${origin}?${QFN_KEY}=${qrlHash}${query}`, config);\n const contentType = res.headers.get(\"Content-Type\");\n if (res.ok && contentType === \"text/qwik-json-stream\" && res.body) return async function* () {\n try {\n for await (const result of deserializeStream(res.body, ctxElm ?? document.documentElement, abortSignal)) yield result;\n } finally {\n if (!(abortSignal == null ? void 0 : abortSignal.aborted)) await res.body.cancel();\n }\n }();\n else if (contentType === \"application/qwik-json\") {\n const str = await res.text();\n const obj = await _deserializeData(str, ctxElm ?? document.documentElement);\n if (res.status >= 400) throw obj;\n return obj;\n } else if (contentType === \"application/json\") {\n const obj = await res.json();\n if (res.status >= 400) throw obj;\n return obj;\n } else if (contentType === \"text/plain\" || contentType === \"text/html\") {\n const str = await res.text();\n if (res.status >= 400) throw str;\n return str;\n }\n }\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"ArQ8v0m/NAp2IPXQb9gToqSLDFcK9Av+yjKQTyIJeNRvwOvjwSaQLZ+qi3NZ/ZD3mlkCd2PUR3GtwYGBmxO9IA==5iLi6k1AUYiK6M7KYIm/QL1D2e3K6Bmu1Rcw6P9/BDbN2Mco1iHXktqa7DtNLHL2f4MBoVThIMzIPjRI+Zk+MA==fBoXvGh3Ri/zOr5wStZGgDF8cYJlDRYdTlmgrPWX9j4U5UHFpryRskYsLrxuGs5yTEX9CG8i7KVDKgIX45PFug==NSYZlyxxLgOIGYGbRlkuSWNx+gMPQKjc4ACAfaeTWf90MGCBh8FMs1Fv9f7qTmXVM0g7GFVtu/tASsyRtP2t4A==GRhQaicc51z7PhHcl0ut1fSdMr1SR5lGjbIkSnjmXZ+1ctiGKo8nm7+8KFGHfS268i15Fm+cKi+Ph89xXPxA4w==EwsHC/GIHTwY1y+cs6z8cIkfSHQoIj2jhTdqgXJBruDwvwmkrTVLs33NO1e966Bhsl9xIaEENEo440xHgqhuAA==0IpeusNCAjnUSkz5rwE4Huvhz6mZVY0fnue1grlQG2EiPfFLi4Qj3/Nh3Zemt0tGiQ8hw0Yx7kGT5IjaUm/S8w==NIOd/QjqmZGliGJ+ivXD43t2CLi8lD3VgFw4VLX61z16cW1X6Zp0lDe/DZOMWQVHKhI8vDT5wHOt0FtybgcuGg==z8FKrYBnl786SrSJIlQKlWMkyujFo1xxTxZ8bdAccpp4D92fz5Ua8GOrQALfj5XULxhyCZ73MgCUHP1kq6au8w==ysMrq9pH9SFxX/+8CJIGX02URxVaHg8Bpx5MbD4dojfXYwagimlnqJo9+j3C1XsJhVIFniQDl2eb9Gv/1QUAQg==3IxrVBTjcIESM5cJWodqiFz3sZkf2g1lLyVWEo8KzC8r65uAcTs+t/CHD2rQFxVonW8GwO5e4dIkhwKgT294PQ==7iIdNgijRH9Al6V3aXAlDwG1ZJ7HoxTroDAJV9bDznb4UH8OXpg2Tgux1nH1WyRLvor7FHveWHGNC47xXxgjyw==w40iwirRqK42p2YYIOOjPXpjddoLAIfQfNwkccI9zfKeVML8X1Di/RnE03RKeidxEacXx1iWO+S0Y4dXtqJeBA==xSacNJPYI8CVgvdcM+zlvWOkyzcSNbtOlIJSypaZeqQVEracuVT0DOlfYXzMy/NnSgvkCk6Eb88IWINhT98N7g==ccFeXghbi4riXDyG8DEKPAnjV6Bs8wEIbq6PzDmFG2rHaOGgyabETGIS6R0OGspjB4/oG0O9HtU2cjdnltFtmg==3QYBbRs6HEToERT0JIuJ4Yv08dSjv46t87KdBKaZOaApL459WHNG3PhallPsuwrD6RjfH9/6+8fHFbo7DLOtbw==JFQ5XN/c++l54ae9qPJOegMem8BLjegeP5KNHoZN4DCAaB1D1haegG3T4vx/fVRbZQ/bdiiwnEhJHI7vj++lmw==KE6Z+Cd05lbriwL8m6FtTYOboCxxf63UsSNnyN+3vjBn00psEfT1A05QpUzTGVo21jKrloQg3uhhaPb9MPWsgQ==vsBw54xPaoONqLtXK+1u6mRm0A6cqmqT2oT0Ial5N0YBFskO54DuQzB7FsJQomKV69d68wKHx+BJ3CO5rqVbkA==gYpXEMTVwSVHk/ScA01/RZX8YnOFthxya1yHlx3N6/PCvAqWILBft9t5942zXUJ260mYrw7r1AhTq9dT7eFeFQ==VC0YKegDnPCOJrGojuNsQesFxZIlIvEPkfA9i/Iri1tyRWrLXngHPXKkGEuZ3JghSUH3BD+PtPrgeFT3JDpuZg==dg4JcckguhUZ67rvpgKhCQlh/Dy2aJTmidh1VvVSd6u93PHUQPxKx2HakHa01b6tcoQJhBAscypabCstgH5dwQ==DZNQoSjJD/dXTJfjUn/n6RTLZrMkndKUrEiClKh6dULyZQPAX4MGku3aKVYDmvtQooers10EcBa87slw+libVQ==iAmAmCi3uAWPfYvi4trN75WLvB7BMXkjMHE2ettI3aahPb/mR6ULoSi/MMZMloWWRYU0KaS6/0WOao0r4RSDTA==pwa/wvcfE9DAok2OTg9NEbsvK", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_serverQrl_rpc_AaY04hL3w3A.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_QwikCityMockProvider_component_4XSnxjzQSYM.js": { + "exports": ["s_4XSnxjzQSYM"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityMockProvider_component_4XSnxjzQSYM.js", + "isDynamicEntry": false, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityMockProvider_component_4XSnxjzQSYM.js" + ], + "name": "index.qwik.mjs_QwikCityMockProvider_component_4XSnxjzQSYM", + "type": "chunk", + "dynamicImports": ["build/index.qwik.mjs_QwikCityMockProvider_component_goto_cTkWSVfU9vo.js"], + "fileName": "build/index.qwik.mjs_QwikCityMockProvider_component_4XSnxjzQSYM.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/preload-helper.js": ["_"], + "build/qwik-city.js": ["c", "a", "C", "D", "b", "d", "e", "f", "g"], + "build/core.js": ["i", "k", "q", "l", "_", "n"], + "build/preloader.js": [] + }, + "imports": [ + "build/preload-helper.js", + "build/qwik-city.js", + "build/core.js", + "build/preloader.js" + ], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityMockProvider_component_4XSnxjzQSYM.js": { + "code": "", + "originalLength": 2405, + "removedExports": [], + "renderedExports": ["s_4XSnxjzQSYM"], + "renderedLength": 5371 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { c as createDocumentHead, a as ContentContext, C as ContentInternalContext, D as DocumentHeadContext, b as RouteLocationContext, d as RouteNavigateContext, e as RouteStateContext, f as RouteActionContext, g as RouteInternalContext } from \"./qwik-city.js\";\nimport { i as useStore, k as useSignal, q as qrl, l as useContextProvider, _ as _jsxC, n as Slot } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityMockProvider_component_4XSnxjzQSYM.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityMockProvider_component_4XSnxjzQSYM.js\");\nconst s_4XSnxjzQSYM = (props) => {\n const urlEnv = props.url ?? \"http://localhost/\";\n const url = new URL(urlEnv);\n const routeLocation = useStore({\n url,\n params: props.params ?? {},\n isNavigating: false,\n prevUrl: void 0\n }, {\n deep: false\n });\n const loaderState = useSignal({});\n const routeInternal = useSignal({\n type: \"initial\",\n dest: url\n });\n const goto = props.goto ?? /* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.qwik.mjs_QwikCityMockProvider_component_goto_cTkWSVfU9vo.js\"), true ? [] : void 0), \"s_cTkWSVfU9vo\");\n const documentHead = useStore(createDocumentHead, {\n deep: false\n });\n const content = useStore({\n headings: void 0,\n menu: void 0\n }, {\n deep: false\n });\n const contentInternal = useSignal();\n const actionState = useSignal();\n useContextProvider(ContentContext, content);\n useContextProvider(ContentInternalContext, contentInternal);\n useContextProvider(DocumentHeadContext, documentHead);\n useContextProvider(RouteLocationContext, routeLocation);\n useContextProvider(RouteNavigateContext, goto);\n useContextProvider(RouteStateContext, loaderState);\n useContextProvider(RouteActionContext, actionState);\n useContextProvider(RouteInternalContext, routeInternal);\n return /* @__PURE__ */ _jsxC(Slot, null, 3, \"uY_4\");\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"2eiWjNQ3lQyIzGT7epyLoxtbRHX9NnBXnq6UtdtGRfNPYy55yjGFSBhNQ1QidKyjTWNBrNxuILKYSY3HXTjjdA==gPCEVB3wVsVt14Ymi9ymyI2tagTigCJqV+XLmcPAbhYcOxYnRQjHdklTaG1AcIwv7C/IUIGVIaN1G+Zm8pEFug==wAXuaH6Tkd8JEJF0TzCgjnwueK9GWSGYjOklQBmTfRsZSIAaQGi/QYiQbGXLsqMXlE+E0tPI0HJBhAFMv579dQ==1BHIJD0AFoJ7/aYdKRVIbfBcnQ86J38PL7gVVnm9Ii+mhqHnwuoWzOEzQ2pQIGKWRykunMKprvPtOEp4gdcIjA==Jr5OGuJ5rpzfTeDGHyQAL/J/olLyWs1D6DVPN0GMh3fd95ZBlj3b2HDU0aKVSDHgoAwuPX/Dk0GFQLOVoOI9zw==10X/bpfekev+sUWPLfCYVf8lwZh7dWYBk5stiQ42+9eUhkplnA+Nb4Qs5pFHfPoiHlUodfhI8fJjakQpKZeukw==0hyikZ4wSDGCt+0wr1OFeoOAc+5bjXggAoD9HFxTumaG2Ph1HTGePjsK4lpd3FFlgLJnfd2wQAxEOOIIDswJlw==Tr4I+J3FN87zk2Ob6CpRatVQLiWYKuKX9XF5EyVL+GpbcMszD0UBX6uZ7ErMZK9W7TlhNj0AXDPgkw1SY+gC3Q==b8A6BxUaVgX6MdP57KZ+vJSPG50KXanqG31ZukKnjBNU5OC/Sl6yWXJFiyqDIXfjDULVcBAw3zV7+zytDXuUPw==ayXExqSwp37io0kDmSXJbqmE4OI1OmaJf/g3+oXT8nWDvntYT8/76yVoqNiW7ABdJot0kC2RbbHYmwWQgFvEyg==YhO3GH7mwF5CcNbmq0avUuNPtjevcol0jJY8kgxI57zFvYIVxsBQpxxnVXhL4q9cBItc1iKlD0NzTi1NZWtGzA==vo6Jfz2xCTFF/FEAlntAwltBl3eIqsKQ3k+Yh6wymxwzWFkOgmewJejXwMg7ys2FH1WfHLl+sAmuKLVkXHHnew==HG5qqgKQbx+RIQ42veFrnlHElvKhKUVsF2URhegnNBOXZ+IMIaeYwfCs57Jhh6CLdLqXGj59Kmg0UnCBmlubVg==VCXE5U02SxBMt39Qec5zm1zK/S4B04BZ3Dt5pf1oDRnV9QQ+zWCyilG1ZjIR71PyuTRPXjsDpzWyL3i7A8luIw==et4tnUiVc4MXup1yxQSOtVaBflGqUvmuC4xf1RH61DglTJm+LCRjWDLFsvXgVUXioddjK12T6uB3lmSfEEBCmw==Iklz5TTMGZ0U8HahRs7UdTSIbFJYGjbdXLGGYoajppEuBpSjUlNwoBJIhc3BjUS1A1HU9HMbAiTEA08lHlhUYw==myK8BN6N9nipWVQCEaevOUkoC3mGLsuwEqZ5NliYNmP0+vJDxIabARxz1zVL1M0N0SoK2HgHS3MJGFSr7Npb4Q==NjM6+RcXZE2IWbq7PbBdfFOQ6A+zk0xOXPWgc7th100VcwrZpcZQ6bv2im4kaCt2ECtLHoGB/7niW4xWlQZ7lg==ayg6bUozfNQDY/KnwGtJl+MfdLG/d+kKW8w6ob37B/UutwuDIcOL+SA+R3PocV+NBmfOrtPNvPdwATCf+KFE9w==sxp1QBXjx4P4uEuM85MxX6EjkPpn/8w3MEnxtdPbD2Rm0bVZIw3Qdvjec6kW84WKVWuFpuX1Z2ZOkJBLBMOIrw==Hc65lkJDdgXk+ONC3CP1oEbWnjvWwq5bJGqxFGKYhD6yqJqa2zuXXhhmphNNlk0DfhCifgZGkbfeCKU14eecnw==yiX5rGePm4vT0qGKZ8suQ+zWj/pCotwJU14Kd5RR/kuH9cjM8ksmUyw3cZwNSFHIh3zeFtXcnTtUOxI6o80sYg==zFOtv0Z/Iq6+j2LWIJsrs/T1qYm890BXn6E8Hlp1hKkehf16XpvdHWJr59+ii79Zus2FSOiv86hEv2+1esBTIQ==dPOXNrRpm1Av5mCle4QZTBaNj7HgO4qlOohU/PndvHLhd8Nla9PqhPvYE1xK8JF0YdHcYm0e4zrepTQUTgGG6g==z/PvsxXVT8BYsYJGVX31eOWkukKvJUNw6fQk+OMU6cJYg9Kz3WrWLN+w/2CmzWGhwOiyDVMHRIMpYFgH2cIzgA==6Df1kgCXBw/yCNqxjGY73MJfXKl5+PHpO5M+9H492V4ysdouriMlm2bUt2kJNzx3HTd94YgsQzrHD/jw9O6mgQ==kaT6bkwO1XXRQdJ0mnmhurbizqg/o+xw0HZSXIrLr5J+67/r2S2R9+RGalFxuwVUg2UVpIUDVuJ+MaN8dmwHjw==fuSqE0lo3sxx6vCUanuthmKqJ79oGf4e40B7kwfXKZPq0OZjI1yScuesxt9krL0EXAbbsRZzzChTm/0KQeFDNw==o+ATXAVHHbA8hPTyRdvbZKEhyv/1kxz/oMFPTRF0JukjBSJhNQV/ncuBgYk3UYmFdPm6//0Xt6VrGoiszznrZw==rlUJJZP1YBWMGpjhWWoDIoeOGctiHW4pUHpU03C9j8LLquLqahAREj/26S+IJb56iUU81GORXQgPyo69FiwtKQ==7U+KlQk7NSz1IoOo6aJ8KJpCNgHvNJw0kAtcrBLYRVFyVsjEHs1PLMNzj3qDTTWmDmNVNziW+Q6q0rcmx8lTRA==h9VumruIQACV7HOiQD3icN1WTC4oahRfX8wZBIdVw9TJBjkjXF+PwvNkI2ynP4bvHAXHZqxJrr0s7kMdctyK7Q==jXz34", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_QwikCityMockProvider_component_4XSnxjzQSYM.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_QwikCityMockProvider_component_goto_cTkWSVfU9vo.js": { + "exports": ["_hW", "s_cTkWSVfU9vo"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityMockProvider_component_goto_cTkWSVfU9vo.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityMockProvider_component_goto_cTkWSVfU9vo.js" + ], + "name": "index.qwik.mjs_QwikCityMockProvider_component_goto_cTkWSVfU9vo", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_QwikCityMockProvider_component_goto_cTkWSVfU9vo.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["b"], + "build/preloader.js": [] + }, + "imports": ["build/core.js", "build/preloader.js"], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityMockProvider_component_goto_cTkWSVfU9vo.js": { + "code": "", + "originalLength": 145, + "removedExports": [], + "renderedExports": ["s_cTkWSVfU9vo"], + "renderedLength": 14260 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { b } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityMockProvider_component_goto_cTkWSVfU9vo.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_QwikCityMockProvider_component_goto_cTkWSVfU9vo.js\");\nconst s_cTkWSVfU9vo = async () => {\n console.warn(\"QwikCityMockProvider: goto not provided\");\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"h6rsIX2/FD+/O8Jo1cv31sGOhVuAURY2A4vuOF9at4Mp8XrdPDu5pvasXfhTcHwY6XEixvzsDp/X9GiLO8a/0w==dl1jOsMN/pm7parPeRd2gaxAAuUsVfRlKG7L4d2q0LxY0CUQfJPMQ5rZLSLetUkz8LikrfqF+EEua+LzH4TzVA==blZipYgZOr5qJubOR4HegRp/Yw8oFZNz35cJR4aa//K9g5scFC+KAo2t9+flv9UDeO4yL70vrcggVSBWVYEJxA==QEzH9Aw5rS8VG3qEIZfWi9pkSM5MvuUPZlE8TQms5FpZGG1N38oto2ti8tgwTz6PxBUYaaap7BOJ+cJvNom9nA==R79H+FxttEP1sEcElJ8YwLJ+GWD1CQBi9OuPjy70EU3bOxci7tqp5MlQxC52I8jy0cnypdYPn0GYDUpWWKlgeQ==AtMOIRPM9cmsqZoWdZ9b2J7l05IcIJpktiEclkde8P23z4PUbO5JpwHsn90hzVkAuY6GhSmF8XKzG8nTYMYxTA==WhCXVCKmsX7+TjKj7GU2OC1vrXCFFU6d+6th/pv5OwIrePkEmms4ldS/TSUUzfJumBJ+P5wsuKXjguahdILxGw==vQW2KlobHh3KqVtRHW5PkawUFUlTq/eKwr6OH5i30+zifi5SdxJikO8HFod/Y26LSmPZ2Xi3Etl1nlMbQ763DA==P/7FHK5itNfP4PNfpwpPiGsEUOKJTra0Wzmbqi9ERPs+UARW23Ae9YVNt4nSB5t8OgiPBdgtcax2PCHf4SJCGQ==ahzJsOChQHaRFHEfEGfzU45SvTfEU0oa5aCJbYlmrJr8ZHTEFY+iVmxXp9CMCsB0vx1Gm2iZsCEMadWRVE4tIA==s9ZFX85ZV91C2pfqIZ+xfLWyWqYPCbc5TY7ZjmlmRO7tJnN71zxbVK20MtPRl9PoKqbC1nxuaydUmZWRMo/8qA==1LfZ2rrTYdyiWJ8P/3liMkGdhKpDbJkus0LlptRUCHmoIT6fyKqbk/64j1+RqlghwZzSNP7MFZU4SJWQQPL0Yw==kDwFckFXDxOHWca5AYW4KB4QjhgS6I4ZB4zY5p8vMtN6ioxQhztcHjc7ebTOdZT5TUGvGJ2lVYSbx+WGPmFxPg==sgf0aOaJDoQa8McZiQ1HRRmTmKol7cmQeJklB4ihBPSnHs30jZrfRqDC77FJYfXC+2pI0bbzaOpyOKheXqBZWQ==DnZJb6uU/1DunDn4KiTYhk0XTCGrn8rpm8mvcpdYD5na0JhiL9o0BdAJPlHWc/XrvKzG+/i9VXRJ0xqFRxQtYA==K5IsRYbhGNQt3UBZg2jT8YRbQyfkwBx5xnLFr32ePlJilUQIuoMm9+FQrnw0NZKeTk29lDEyWs3GcFPBaOb8oA==+2GV9xInwuRKWzeos7VjdPMeoD76jikH+3j6qwOqVdzbgu1UNCB4PVBxPQ+iHWSfHrJax4LhwE0qmCWDvojtfA==sEh9upPE0LqA9zm6hX0c26y8nLQVWNo66PctQVSEZgA4PmqtaSyCzoxPVoPopsKxgiUs9GkMiczYs2J8+p+QVQ==uYECFKMYnQug0cdVwJfsHtKnACgNHjrTjeCH93wOhAMcvkjjF2qev3wEOSeULOdh/qYg0JQpfKjl9C42EuWvTw==wzN+YAjtthyc4M5FHW/Tm78wt1HJJ6Tabt5AXM2guCYLnflUwKWfmGc8JbhGsudL9vUM9aXjZKJgBnv7Mq/F+g==DA64h3ZyQwcbyWrku3P06C2+96FSU9/QIlTn0LE1y2shTFi8X/GdvujHN0O3vOtTyf0HuqmFndMsbL3yw51iAg==tl/dX0IiLeNWxplfcM2XUDH5Cr6ffaZ766g5Qj8DdY3+Y84WaYiXRi4L6QWjEVJ6YFXpdEV93OAbN7fdlvjypA==KFc4ri7kyDbiv/2gRrW0UXnXQ/Ya+hGKmp21Aipbq8PW8dHrLC0JwBdlvz1NUypI3N55hZuQ9q7/RzaU95VA7A==Dj+vkVYLLpftrx6QocH2R6zsYp7UE5js940xh8UK2a0FtBBaKRNKnnYW2namXvbqT4zLiPWXsMwPQy4PfTcAPA==6+lDIpBIFoX/JUQ61VI0EMemrKe/uUMKDrj2izahsSEgDjIm6gb0pNjaxQyRsZF1302k04s6sp/AxzIK+mWbkA==jEs85wE4i19ivARA2BidtoDskZqiLg3Ar7Yqv9piJ1Zi++vnZ0mty3S4VV6wfPPzDGrfJo8jOdwlfeKzf0nLcw==Mk5FPZGQ344y2AUCGcIr06+NgACIoz4iAcQq5E9iVFvPEYo6mAJJvmplj2RyUlEeHYmOBOOPhGISjXw340K4oQ==F11em4Sjdb8dQC3j6bJjkw/0J49NJjNzbMAFVm3b2AzOD762zrYjhckOfgNyKqC9UTj6oGnfvLFuUnz9tnc0Hg==AqH9KoT3NgrDxO2FFNnNf4XR7qnJll7Q1eyAFz5vSIYBAtjiokhsOauTjI4j06VosfYNBV53cmN0gZ0Mxk1zqg==hGkfq+QQ2hCmLoToEtBjT6d0dhYGm5HmKdXc3+WS5UaD3h/BaHm+LNaEn7sA/6pP4zwrwXQusKDaT2rfIIhM5g==vggFWxaVUh2KX1DEjfAhSgrx/+n3CXsOelrOTiC0QIW7g6rD3WFSPDbBCjE3PI9rUy2QF8Jzuu6PJ+plYEGyHg==dqdDXCA8xW6ebKcAXoGhc48IOHY9gzbel5RZc6hvAhHl8p7jiQAgZEcrjrT3QWbdkAUQ74uR3Xtala3rCF975Q==uDpZBkFCKbO95LcsQPhZUUrHn3TWjeCUvarrAmDN6AK7RF5iecuLJHC5w4LG5hD22eWV34/Dky4hMfzgFVcuxA==zgr/FnJ20kgwZnw97xprgbMnikjPRBDkhaq5n9d88vwOiW9UQCKTGoY68rpmWyyEFhnPl0OVc/xtp7L/1MHPVQ==P4UjrbLKUP7lzdyNgbPZmZ1Ae3jTREd+3GpqW4l7xBaaVkkWoi18HSeuoDJ7rAHVKhHmvmHSJN76kndPHNqAvg==nGHcSzzttwBbG4txxgDVHtQBRyarp7AwFyO7wvxIe/OdsuzmMD7kcDrtxAQzczRNLnF2u86h0GgPjrrzai6K8A==7fm1Fq8S+9ew1V+bhKSMw5Xvkx5IiqHesZ18oujCwuDI81W5jDEMGmLGxnfi8oean/cTZ8CWe+9tVNml7BN3Og==20VUKvrULF2q5wrZCF4itSYQYLx/mwSi3dGM4eE+tigE6QPxVAcaa6M/n4NdqK/iRAvcjn9v0VSkWnGP+VSKRw==DrH9r3PDyfmhOI9Adc46dsUn0Pyr64OrnWjQmJMkwr5Z4jJznOwdtGf9yOnySEY853CoWMPuAF0BeFNlvdPjfQ==k5D38QTJdWUS8Cw1T/kcHMvwL6kDS1wf8PyqnApGJClFVNAUQSDfbyllNEjHXMoVI3JxM7eDSOXsLrgzra2NlA==78eI7c7sPOY/FuyT/eEti11dGsdku66vqXfkhdTvCOZBwH5thrRt+DJe5oRHwJsGM22xQ5EREFSqThaZ2VNdwA==tTx/qKgZDy9DVqN3ELYFpWuB8FVH7Ybc01mvKMtbWpUHJwhnHoDjUBVNRhHyBZgfTmj7rzF7n9HMQH/brdvEfg==FCk6nKz+YCZgxZB/lS82fWNoxWW5+PRAe5f2Wmnhgdcut5cO+cGJXpNFmpBCxzyvtnhbCvv+mj0JhWo/BE7j2g==002ZtAT9JbHEeRDJ2d7Yib495TviFipaSI/4d89bcgk4n/oziG753B6tK9BBYHo3cm0UsfPZwGgJ6w7giS3ptQ==GYq5DkXq7KkzzzWwgm3yyh2agfbnIyEW4AMK8jUnQ9BhbS7SYOz4dfW7dMoFw2Hx8KtjCqdmRl6IQNhc8b593A==CH5GtgqRnetjV+um0QpmW15qtO2EpN4cloKCOR96rVzPCWKHVLr4iz2fbnJKyoafPumP32KvzWOK7I8wr/IfCA==/TkcWHIJa5jTX3KPnkFNEZOMLEAbzzxxWaNpq/z/fbfhbXxkN5DJZSsMIDVpX2I6NsgSDdu6hMcArPTTyIbPlw==+E4ffL2dph3cDazdIvUMTNkNmQF6CivKP4jOCCeVFv6cpUtK3NSH1WEW6MZW4AsIFmJhpylqUtfGKv0yMN3XhA==uuulT3joB5yLaFDpBVgLPsaUUi7aOxP/P+HhFADWMBMKvSmse+t+MK2xx7uE8iavwXebePjJeYuImC/99Mfq9w==An7jiW5rLfpBAsllWyteFZFB2cjoO3MD+1afHWchQumSQSHneZuNzlxeHrgGh3Pl6eBxcfRM18yT9CfsH6iqvg==QOIsQWdBCLNnGtS6pQLpewf7abgG7LjeipKaSurhA8Q4", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_QwikCityMockProvider_component_goto_cTkWSVfU9vo.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_GetForm_component_form_onSubmit_OBdjRsvd4IY.js": { + "exports": ["_hW", "s_OBdjRsvd4IY"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_form_onSubmit_OBdjRsvd4IY.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_form_onSubmit_OBdjRsvd4IY.js" + ], + "name": "index.qwik.mjs_GetForm_component_form_onSubmit_OBdjRsvd4IY", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_GetForm_component_form_onSubmit_OBdjRsvd4IY.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u", "b"], + "build/preloader.js": [] + }, + "imports": ["build/core.js", "build/preloader.js"], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_form_onSubmit_OBdjRsvd4IY.js": { + "code": "", + "originalLength": 474, + "removedExports": [], + "renderedExports": ["s_OBdjRsvd4IY"], + "renderedLength": 11442 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { u as useLexicalScope } from \"./core.js\";\nimport { b } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_form_onSubmit_OBdjRsvd4IY.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_form_onSubmit_OBdjRsvd4IY.js\");\nconst s_OBdjRsvd4IY = async (_evt, form) => {\n const [nav] = useLexicalScope();\n const formData = new FormData(form);\n const params = new URLSearchParams();\n formData.forEach((value, key) => {\n if (typeof value === \"string\") params.append(key, value);\n });\n await nav(\"?\" + params.toString(), {\n type: \"form\",\n forceReload: true\n });\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"fdIpxmI7VpIDd7i9pMddOHAWTmrdqj0ElWwPkZxcxTP2r9fWzePNrvAOXDQDbE7f+eGKnkxbRCVXyIVSpbSLnA==JToime4L8mgf+cWu5qbReRI0pwrz58nX6Txe7PS9NJybU50D9hoeBY9TIjp8RFbZ0HfWlQgHIEvOT2cbHp9yOg==YNuiLYc/PxGcc0rtzb0/xFNuNUX0spXD+7FG/f5vCFGN1T2WnDAfx8trk4d2Bb1pIKLsxmUpPYpFnrhrj804LA==u76+boBZehAeQJaJHd7zvA3vTb0XEE64SB3NVdoZUvAYVcNYp2ZfYOKw9KnlZMz5AlK6M05hwnHHdEveSlWQZA==ZnneGPC88/xoANTrmSvCNPcBS+dJlcEudzFHaEwCOvKQZQ09GJTJhN4fHXp7Lau/YxRbnPB4ODLQI7rA8Zn5QA==y74q8CzbTKqSAKMCIRh45ye6y6QwRvXkNR9z5RjfPOUoK4BtsutA/216tOvUxXK+D6iLCRVwXamzBeVKtaDX+w==K0taTvguFGfjzASV5dAA/ErGKJhkFXCHzVIKRS7xq4L4+MAbpl3UYbEIIL11LXZFwoX9RVOP4x69EapaJutxSA==M7THY7RtdVxigu9D66lB+nNLY0UBobsnYC2LP16gstytg722ospAR6N0pRd0kxVyjJW4yWrXKBirmLUMNj3H7Q==y8s+8ZbGqgaMlVgp4PoNxurYGgxvsP/5SRUvq0p0bBOBOBLgEFRFesH8Ae4XYuaxBbRVlX292cCoblzmkpFuuA==ZaSxcoUSDAYgIxAgGDDGP4PVrkrERPHnaNYMKr9DWCon4BmdAPrMgIxZAuqqwLlHuz8Z2UGRaFB4dA0hf59lPQ==Irqax3RWQkBvtXb34iGfD93XlYp5ssAqA7giKRSxm3Nua3sbsw6ZTIzsoCYFaSyLemVKN+n7Nfa2CtPqWweexg==pjwQp4A8jV6+pWhquczUeb4DsfOTHyCAoF6hVQi0TjDwRoIbCTAfWiFFrHs7HOPBsh5Wo3VRTHQaYXMwliawTg==R2ZodaTwd3pfbyJgTIaN423Jg7ysJ63VMAvI0dwVkvAbsz06httFz9/y2FwLLy4MRLJMI7qXD7JXknWJcQORvg==7tWee2huy0wkxsr9ioiaMxTUcMQgASRS3/m4aEfNMJ+3RVnP7K4UKMhw+2+0I472dyGENZx3ibWatuBAUyLXzQ==pqVxGkSb4sC4rFPT32m0SM4FBpDhz5K0gM4gKkUmnX8ySCxValFV5Mwx+M9pY/n0Jxocf8uq/pzAg3wElRa1JA==OA7xO4qOXS/gSy/h2KwaJjGHDimg5oLimtXQ9fKE0XXD2L/J1y/h9Arpl8X/fwICzbPSm/bTtAq2nYGfQas2uw==9p/H1KbKxJvj3V9SP4A1Y2tEbTwisfz6hp2/10r3TCgYqxnkqmktZlwAuaVr1eIHqyK3b47K1deJTilg0R9C0Q==QTo8+gX3sJ3FUI9eZOJPDzRBWqtAOH/IicTX/gGptkDZEjTAAMVF2GbBd6I2oatsPZwwkVBOXR+xTmdl5aKgLw==cplPbPqHX1DKfKsNt9ASE5ZQFOjWOU2V3IxramXfoSsE6qx+Ul/ZPRbOSREqBonREaL5LxIKa0c3hNTgsPOP0g==YGHbPOSfsAjGHgMjhlkjYfu5nwVLoYneebbhJEy4leaW8vbTdTgbKyOdu5eRwOT9rNNp0hgUHqa8FQOq/cbj0Q==M0vsWOMrBEmxIDSg3BYgrAnaPbw8v3dTfPqZgcyAAKLCKQ+NUT4CUwVS7RElETs6GD+QwoCUvkNiRvuYOfkB5A==7JuG+yeKzSOPR+37gg04HcBub93VNNf/J8DUx4hngXjI4V38Hb5gDcjFv+WFbx/WNyyjysDWMaJof/utSAm28A==icWim9+1B1f6D9dq0yxKwNwhHFdFDrDsPU3kicyBEZARNEsPAw4KVDm6IpafJu2Qt4s9mV7YHTWH+rzF9kMy1Q==HvTf0zzaNqss5AX5izOG5Zr97EgZbRwNGslGcsEmVBP3duGXSOAQdqBqf2WSE+o1SclRD8RqiFD9tMFwXKppyA==D5PSw+cbAHhWo4AKhctVWWYnlGjFoHADAQqBvxkoN+y0OUrx6iX0PUOx7yN+pbGTgGyzTqxjyMcMPfdHMAU9MA==WPYgP3AHpmj4CFa1jIlq7JoIQGhrI8hc4Ow38pwlN2WmpeV0F62r3zlyA33TeI2tf0q56XA/AsPCe7Cc7dNlIw==NSTzhWCBzOwaiQRO4ZVFnKb+IsusBiJZDrRpN8PerGqKFOebzwDn+/nLDMA6P51zgFw0+ZDXwyNM1nvJpsvtqg==hbpEOz2JNldWzf9QA7SAiVRYknfWDNN0PvOovR9QBdsWFep324//szADSbsbKRyLgT1vHKtSpBGbwYbSfOY1IA==rgBBRhU5fppTBSy9SjBRxDvycd05xrg/Z4yB303Pod/EgVnRTf1ypHI7exRJKmQAKuIRofr0O3iIU4nbzByQTA==k/y3A0O9Cl64sJChz9WTo6kIqBfU/FNQpLx47s+hio3u79imRrJKnNJMUE6W5o5kuci4/j9NWLYm/4HGsnB8hg==Bia8BA6XgOwfQBVO0L7M1/y6I2mYJBgtb4gUbT9MApWt1Uq0yB64RVdIckLzxu5ROtfnMa+9rlANPA5Nwf/mIQ==Ixsz1w5faGytM0NIBOorNDvYSlREVcFendhf5j7RFM10p8XoYlwU+0iqAlcB7rUrRH58oSKOqOA1L69nYVm9eg==yHp+kM8EQpyf3VE2EHRgniQOU9yg/V8KUYp1S6egvHrzq1UbOzME/xVSmUzgnC/yw1y6z31bvfxTHvX+/1X76A==J5gDizS/xZIln0cQUqA+H9rAbG96PPfXy6QPvB5HbZcdm7f3jsSkZPXmSl/aEhpsvmBrRQ/I9M6uDTuBY3jo2Q==b4qBznipc6fgepuSWRpa3aRI0pmJbC+0U3VEOOja8wQPcJXowNrCRbOP13eg6Y41JOkeN6Omm/4asmjCsSE+Jg==m+ucSCCj19Ek+rxsZCRbmt3axwCbsHXMekGvQ/TqDh5HS+zFVqLibX7b2GC6wFRDleM5+ANdVZbF4UGwMsEglA==HyEcI8JXor5oaKIFFdhEps8UOZb5odVL4g6CuVAJWLZ8CnbssG4s7D5DACiWF97x1wk1kuX8qQKYsj8eKIldfA==KTSLRPSxe8mS0mlwTfGuhFk+OUu+ENrC8DzVzxq93AJuyYXnDW1NcWQlfMQ/QCM11GoUak19xfv0SuvoIwfSuQ==BjjTRrL+BXu1SMgvMqI0QCT2OCUVtvKHh2tVYTHeK7hHouj/0QYXqetW0zu/mTMY2FEHEzV+EtB7Z51+xZ1ajQ==tYg0oL+WBLEmdB7YxUiPxO3iZYQ/dxjtWtyRcFr5+dpJ0twqTWby+GGpH5uxw+mFyy/zW2GNEOk+kypRcRtDkA==I3WLaOjBj23EcJthlYEhJDqJ+oHv79sA1oOUkzFHnvXynn8HwTk6PUINMNEKyyQTi9cB2yXA+PNUyb8Okid3wA==z/OIK1OEDZW1+3EOjChbk9lt2P/A17HeaBEafQik5GCRKReCa1mhoM5RhBOJ43RA1fynGiY/58HgOhVWUM3PRg==v3C6fZAihe+xNdKYkmzy9kbMdUB4aa0Uq7pL/qpTTmduET5K3zYjqaWVBc/loQ6QaHM8ONSVtFd368ExES+lmQ==+4QxsQCjoXh17nmW/Bp3Xu+V6jnnUoOdlKBBeyKjt5UgnxUL3QO/mClqmsV76lVVyriHUaah/621CNk2UxJ3hw==8gRbmwlYR3AcOyUVFN7M4Qbcm3uua8utfkg9bBacAhk8WPbF0UKJ4vHlDq46X0Wwrn4pqslrydRLyzbwtQBuug==TJfvuKHwZ5eAFqaYCMIGFhyGs/lRbS8pwqj1vMB7kuKO/bp78Xc3iPWxWqiZK57gq25SYuEfM2Cqpk7notVqyQ==ab9kN9caJ6doXAY8EEfY+2vYA9Ek6ePhZAEXVkxeLamyYkovtwVsfaqed5MM8ZiaT9qpp2VHF2uJwOeI8w9Ozg==2lAIzFc82No", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_GetForm_component_form_onSubmit_OBdjRsvd4IY.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_GetForm_component_amqstTwiNo0.js": { + "exports": ["s_amqstTwiNo0"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_amqstTwiNo0.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": ["/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_amqstTwiNo0.js"], + "name": "index.qwik.mjs_GetForm_component_amqstTwiNo0", + "type": "chunk", + "dynamicImports": [ + "build/index.qwik.mjs_GetForm_component_form_onSubmit_OBdjRsvd4IY.js", + "build/index.qwik.mjs_GetForm_component_form_onSubmit_1_8hsX2DP6YsI.js" + ], + "fileName": "build/index.qwik.mjs_GetForm_component_amqstTwiNo0.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/preload-helper.js": ["_"], + "build/core.js": ["y", "z", "A", "B", "q", "_", "n"], + "build/qwik-city.js": ["A"], + "build/preloader.js": [] + }, + "imports": [ + "build/preload-helper.js", + "build/core.js", + "build/qwik-city.js", + "build/preloader.js" + ], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_amqstTwiNo0.js": { + "code": "", + "originalLength": 1596, + "removedExports": [], + "renderedExports": ["s_amqstTwiNo0"], + "renderedLength": 9675 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { y as _restProps, z as _jsxS, A as _fnSignal, B as _IMMUTABLE, q as qrl, _ as _jsxC, n as Slot } from \"./core.js\";\nimport { A as useNavigate } from \"./qwik-city.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_amqstTwiNo0.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_amqstTwiNo0.js\");\nconst s_amqstTwiNo0 = (props) => {\n const rest = _restProps(props, [\n \"action\",\n \"spaReset\",\n \"reloadDocument\",\n \"onSubmit$\"\n ]);\n const nav = useNavigate();\n return /* @__PURE__ */ _jsxS(\"form\", {\n action: \"get\",\n get \"preventdefault:submit\"() {\n return !props.reloadDocument;\n },\n get \"data-spa-reset\"() {\n return props.spaReset ? \"true\" : void 0;\n },\n ...rest,\n children: /* @__PURE__ */ _jsxC(Slot, null, 3, \"uY_10\"),\n onSubmit$: [\n ...Array.isArray(props.onSubmit$) ? props.onSubmit$ : [\n props.onSubmit$\n ],\n /* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.qwik.mjs_GetForm_component_form_onSubmit_OBdjRsvd4IY.js\"), true ? [] : void 0), \"s_OBdjRsvd4IY\", [\n nav\n ]),\n /* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.qwik.mjs_GetForm_component_form_onSubmit_1_8hsX2DP6YsI.js\"), true ? [] : void 0), \"s_8hsX2DP6YsI\")\n ]\n }, {\n action: _IMMUTABLE,\n \"preventdefault:submit\": _fnSignal((p0) => !p0.reloadDocument, [\n props\n ]),\n \"data-spa-reset\": _fnSignal((p0) => p0.spaReset ? \"true\" : void 0, [\n props\n ])\n }, 0, \"uY_11\");\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"LRJgxhePQqnxUA7pacyk/TAAdfqd7K39y7gT4rrJiPnY53TQdrnMjTueafJrzzus4yM962gJhidv2QXGibdpfg==L4JzMzCX30fDyFvlClBqjCfldDvfSOtR7LCqG3TtBn5AqKVb0aDalVYjBiwH/wxL0pT+u1I1NMo0vyCB8rwtjg==869z+GnuRwQ+NZlkC0zBzhtOcxQcdOfn+s4bV+ux3+S1K5ZeXPakUgccImMKKSPpZokfbFFR1oR8bHX2W1hVAA==s5utwkCMH1mt9k8P0fUjh6j6ZJtNrgbbEiSsQag05TyHTXDMkllowilQpd1C12XtbYUydV4c8wVfOkw1fPc+3g==BvgxhOLNhJBQtyTPyHwStGK/q6t8jVhsL+n6ucrjjd3GrDVDNAt0QK7IQ2K0Bn8/BjfhjDlponkfpL2PYzl7CA==3gJtyMGDY3s7xf373u2n/xA38qsLtdujTHTUwwa/Ra6+FwY7jAFIjKOSVuxKXSSJdtxbd8kCzZT/uIfQobEQ4g==GyKKs92Ermux+6DqJ1J+aa3In8oQlmblWXXu18b05U6KJkffK8HBkK9FBsriSNaAIFAo4EEbS3ARlzBz9mgzvQ==rizYflrFjF0ye7OkVpRcirs46IBh3ks9CedDd7hkjdIK56ljs3U0p69yBMQvWlMcv35vylQH+b5QrcArRFhtHQ==24aJDpIEiMIH3p/bx6VtwnZztlCdY3ngOq4E6G4DRo76cQBD9PWyCiPZbKAClz9H8wnNzGManeeB77DSYC0Z6A==cLH4+4t5tyV8ak7UAplMNccjMBl3K6oNK0oAGYQiYGQ6Cxrd5d9Qn9npbSwotFH0nZkeUrCptBCbV3WAGVd5jQ==KnmEN9FyvY1SRgIiAFtXiUaW45RnaqZbwiebxht+YNTUyR3c8NF1y2qduwBeGLJDsUnOFMkd8hQhwF2y1kqlww==tWUZcleLaWB4boZgMr8NpkKk+o/pOrORCwpUWHoC9/fS5EvKKt0tZiNkg8TEb0/S+gCpr8Lc7hoBpiEC9WQuZw==i+jkkcuMooBRnhXYatLLoR+4zLTkA5ZhIHC0nJLtTq7UrKwkn/MyUsGCwgWID8QNUTCfi1P7iZkeLF3xZabTWQ==efNvdaxeoNXttvBwvg8/0ShbWqYh6Ij3PHvs900iPBBvAO7g5IJ9dKxGX9uHPB9HiIX0btSwOGskOTCXfFwEMg==xG4hY6HiqBgmPbdUXqnsIuB5wULvlVqXPyjQUmdYccbpWH8MgJ2oDQJ93CVHMXyGpjes39TtxxzB43yyWpHTGQ==0AAiWNE1pVAoSe1ARHXtg6+afue7uaPzOyV5oFufEsaz5At1F1Dc9gSz8qkM1/yBiT8LZ1IM0H1ALSbqB9AmqQ==Dkn0h2Ap8Y26VA2U4xDsF06p2E8iFTTJNlKdKXQwE4CkprwgitgyZfbRJklbOuFVbk8dc0IXag7xD1hn/YxW5A==aI1hIdYvqLEdbgTE5yTF6f44dR1IdhbLayMowy6tgF3xItzsv3FAkR3bMwpGMyF34S4lW1wgiwCqGojO47emiQ==ysGe3fFuT/BX6iYwrFnwerNGSDVYkZKdveHxe7Aywq8sjuvim5rGPp9Tv54tr5mIKbZBMPyATRWsg+rfpOvG9g==TFSDIZQugWtUUagbO2PTNb6C8EVOoJOVyoHnSw7yTUO/LTZLdP19YHMtsQC0pxlYZI6Oe9IaVuujnEIP5j58gA==LDJ6XPICgHzaGYhYsazJbJ1XrLPAnpwyBE5i2WbdkrKlKP3Md6O2AGBguKAnO6dZ2uZb99LxFaqfWf61ba/xAA==61TS638Lh3gzJjnBdj5Mac0hNxPNAkOZe3SWUuoTNcKMi6Qt0uouQhhjF2tdzK8Twqsmifw9zY+So/59e+kQ1g==IDwpmhX9ujaMtIdvkhX+kiRQaOswK00ffLg3tOQenf7PrIr9z7Bq6eNFxsQhoYVJ9uVKeOCkH/j2fYv73yUo7A==GzC8qub5/ShVufb9ctwelr7TTVK7wRXSQ92Y/oKTKesy0YQjA1Y1gi+neMeokLIZBLbJC0FEa/Dn9O8l4nBzhw==e1Vm9IWORLqPYG/ldk0ZdL5SQ4UBa2GoFC7G1j0YYXHuBZWGpCI9T5LuqcMsihPrLsUvJLM97GeRFHKEz5B8pg==9Tla6qvten0pDIzvlJ0dHJtpR6Dzmdhf8qDhUsEbWNx37vFoyVey0hRyuvTRTVQB5rRwhYSt1hpPK5mvvjcQGw==oQsgr9yCeDcXnM392zm41nWb/wDtjs0h8qVMjclmwQsp4B+wYWxvyTvfKxZZ3nH2WyFSOkzPUwNFrpSaiv411g==FidA2ivj/qIU+gLO3dhYFH7ek+whj/+JIm3HopfwztoPAVQNz61F2t1RiUOfJMtM6R782Gu2VtFn/Vrx5njSSg==3Dc0tlcFeNSoDHE24ajvoDISHCxa+muyRSuIuQrRgrNBUnD39H/4yIG40Us+FRAfpxqbfuiAMrAMyCQHszxyBQ==RbhuXPVk2TQ9hjQuVaU/g0MQ8LHgS91ke3PkEpuqJF3fs3f4/O72x5aeZT43woq/ycxCxI+mCMHo8Ig6zHxAMg==F9XkKoorJczZ9NwT34HOAL9hLuLHL9KoviZbj1jqH498ZU5U+766p/JfoDkZ0MKoRf5ZhulW4kc3GTanph3jSw==jrEP8d0RJSsps5xqGa4EkTiedKJODg7auYaCBWgGZHGc9TOWoKUgdFi44KiLLxbu+hoqlwNQQD+SVWnt66q/nQ==Wv0qtY0O3JcPawDB9ZdC/GiJR17rfIQUZfU6oaWIp5slSjPBrlzhqoHP8gb3oWmDjy2ao8dusjHs3cey9wFoyg==tqFHwex1lwjsa+hmbPq7PYPDqpqPGQRE6ti5ZWSzLR99VwNMaoJ/dyPx3CH+zWRDPLzTxJYObOeNT0QHiRl0Cw==/geQ4xBvql9M5fr3gpazcoCLGvQe8A6yxLuDD2/qoSKnvmMuiyes/NIo3l1AqXnp9LK+pjOJsHTFliZ9aqqauA==gn+eQd4a+Zs/2uIICrf2h3MF7wGNqNvef9EngvM5TTLWf7D3jXSGi05Tmue+dCX0lfh3wRkh0Vp9ZGcBC781Yw==4/PSFjLSGMyW8cDAI7ZC1HA5N+9hEfyWB", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_GetForm_component_amqstTwiNo0.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_GetForm_component_form_onSubmit_1_8hsX2DP6YsI.js": { + "exports": ["_hW", "s_8hsX2DP6YsI"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_form_onSubmit_1_8hsX2DP6YsI.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_form_onSubmit_1_8hsX2DP6YsI.js" + ], + "name": "index.qwik.mjs_GetForm_component_form_onSubmit_1_8hsX2DP6YsI", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_GetForm_component_form_onSubmit_1_8hsX2DP6YsI.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["b"], + "build/preloader.js": [] + }, + "imports": ["build/core.js", "build/preloader.js"], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_form_onSubmit_1_8hsX2DP6YsI.js": { + "code": "", + "originalLength": 355, + "removedExports": [], + "renderedExports": ["s_8hsX2DP6YsI"], + "renderedLength": 650 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { b } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_form_onSubmit_1_8hsX2DP6YsI.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_GetForm_component_form_onSubmit_1_8hsX2DP6YsI.js\");\nconst s_8hsX2DP6YsI = (_evt, form) => {\n if (form.getAttribute(\"data-spa-reset\") === \"true\") form.reset();\n form.dispatchEvent(new CustomEvent(\"submitcompleted\", {\n bubbles: false,\n cancelable: false,\n composed: false,\n detail: {\n status: 200\n }\n }));\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"\");\nexport {\n b as _hW,\n s_8hsX2DP6YsI\n};\n", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_GetForm_component_form_onSubmit_1_8hsX2DP6YsI.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_Link_component_handlePrefetch_FpLYno2MZMA.js": { + "exports": ["_hW", "s_FpLYno2MZMA"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_handlePrefetch_FpLYno2MZMA.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_handlePrefetch_FpLYno2MZMA.js" + ], + "name": "index.qwik.mjs_Link_component_handlePrefetch_FpLYno2MZMA", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_Link_component_handlePrefetch_FpLYno2MZMA.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/qwik-city.js": ["F", "i"], + "build/core.js": ["b"], + "build/preload-helper.js": [], + "build/preloader.js": [] + }, + "imports": [ + "build/qwik-city.js", + "build/core.js", + "build/preload-helper.js", + "build/preloader.js" + ], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_handlePrefetch_FpLYno2MZMA.js": { + "code": "", + "originalLength": 546, + "removedExports": [], + "renderedExports": ["s_FpLYno2MZMA"], + "renderedLength": 7450 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { F as prefetchSymbols, i as loadClientData } from \"./qwik-city.js\";\nimport { b } from \"./core.js\";\nimport \"./preload-helper.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_handlePrefetch_FpLYno2MZMA.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_handlePrefetch_FpLYno2MZMA.js\");\nconst s_FpLYno2MZMA = (_, elm) => {\n var _a2;\n if ((_a2 = navigator.connection) == null ? void 0 : _a2.saveData) return;\n if (elm && elm.href) {\n const url = new URL(elm.href);\n prefetchSymbols(url.pathname);\n if (elm.hasAttribute(\"data-prefetch\")) loadClientData(url, elm, {\n prefetchSymbols: false,\n isPrefetch: true\n });\n }\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"CI6DZG+iZCKEZqTnXgsdCQgxZoojdAQxhAMi3buexNUOaQZOuLouwSVV7A4T4ARXqRPFWJOJtmZl1/MM8v9vrQ==pCJAsn60Orm1yc5upaVhyLjWdlFRoTBLdTrLvyQgOBMRqTcJu0hBqBd8urCy2ARq7uwveLlh8CxeDdWGMNy1Gg==sRn3ruWNBFKdWcgDVKXYCn/jR81LtrzqAGDpLaZeRjkRELOLY8XdkpboI4vOTBUhexHjTnhPQp+Ig+Gtq34amw==xpHu9OZ3Wc3ESLMNq3tD14lYfBtnIIKT1XR8boo1sA1y6Ci/KFbqSee7U1NVvsVdvEM3kBSC6g8XCG0gJfVbmw==V1o2uMgI5HumLHCuQBeO5jzyNnacIez5soAw4suLWXkqE6vGiJmUJgyzmBUcH/PSd4ZbeTcvS/sFAxV9g+gCTQ==a+T/4hIbIv5DCFIa1oHYUGKXXxjYWwRJfG4cQCJHLkrIcmly7m39p1GM3R70XNH/4n99HevwNL4khbmSAK5a9w==ZDxAb8eJmLJVhbVyw1iLkP0SJQPi27Xu1nedHiC/j599NQPQEfYxNvyEw7KINcmrCrpistJKm8w89uw7yqLLGQ==nVQJkq4+v4iA6VJizoIGwfVysyEefGdpZuEcZOnvdKgumt4GMzX0HBQtzjTFQgWEw+Z7O6wKMy7B/AiJz7xseA==CclymcgdX3f7wYfkUf3lmAqH+Nh93BoC1gRPvuynzZyDNbeeff8gpuE2iK7tYZuJkzWv77dhXfmERLuQhNLy0g==98U+MQlzirdHx/po5IGW828LGMW8I7lwm4nMpol20liPDwcV29qySh+QvqaENWJjNEaJFhNHt55ArZUQ4OqZBA==H1mgVUD22n4KQ2pYabUI4+gPo9Ryv+C8dhmUN17sfhW+gVg+VbAA3qYpry3Gftmtc2vg+aCncA3syes4/cRQ/g==/CQeEvgHSY5Qp+8YoIEENd8pqFyNcaVIr1Z7PmRRbGEoIZBNacJZNloMO+tD639myewGFlqDl2j3a2oC9eTvGQ==Ri+sbNrCMuNbBOWSLr7oiWrfCHKip4Cjvv9uIJqRVIjxRtfnX0dKYmVzWRB0vX55oCP5AU4ehUm0cxRZGULbBw==lKyR/9oSxG/QNCb6S7vMCLdDb4he34t4gh1c+RQcWqduZMiDaiBMu5GCDmm0vNESHW05ApqlNe3l66R2ROs5Aw==Woe/YY4ihWpzh/XBCPaCHHCi3TuCdN5EK/kM3Xdf6OnmUgKQQoOd8ivZ/Had0tELSTXi5Q77gvxVHyMq6ahMRA==hwy4z+AcWsL1zTLjU1T3QsBZuqZdHitT5WBWjip+B/0Zx/wkgpc6qFLLfHe9rKUHlFkowqq3WKUTsE+rdUWmIA==Mlyzwf7IrKF4kaaP5ixDslJFwM8t4cNcKzwAa65tSpqAHgfBnSVOvNrmdHo8fC++/RW6PDMtdkgHkebR3//8ZA==dMDZT1TpUb3SZN2oRq6Je4MciBXk1hqKkFnOPI/813vHQeZ7BRKZlrkXjZxQsEAXN18oROWvZU09r6yuqiOEiw==u4lJVeb2hAfmgNdpmGt+C8eOfUwcwV7Fpt4JtzNJKhC53iE0ya0GmuCG0z/5SNOL6Ppc6MJtA2Zd9vkHuIniSA==1daSQytOmfYId4yJh4grZVi0ygiTE0MO3ul5CyAHQz9hd0M1iyR0Jqukx3p1VznwlhoSAwmM/vM8Tzd5hXmCzw==lb45BYUWWmihh/Ovlx6yVKcYnQR+5W8I59xloBdTETo66OVYrtS3CbNUzT3CQt4uMOlffLM8Z3zRVFTJIBud1Q==/vZ2a99GXn+lTFAjsR21e68JMrXmnAy31d56w1595c5RfgaCD9NuCdGTxORWEQXLQ/h/Ic6RAs3+BXU+sznTJg==RP7OtvtCXF7+oBQYG2hEA5QS/JeJ8Qf34lsdiVZqe5rWyGfI47sEEokCXDs47u7q37GLxbmrMBKly/ZOsmmNfw==P0kabNgkfCRBuoBKR7t4aHeXrIZtnHjkDoc6HL+ExNdTZxuTLsKukfWb5jTuzMSK96DcbAHyfMpt1GwUy/nSMw==5mR74Suc1Pa2XYo6wkorLkXw2ky2j0lffWuEz1/cxOsHTnq1aJga7T/CGQGAelvDx/ERcYwq/LEoA3VEWdMU/w==z431SQcDD8fBEy0/LxFVP8VcggqjkFZ3vnKQJ9rPrtvP++PPxOXbIVwyoPPCXQaH/C1LS+/DUs6R4Fos1TDYEg==8ARzo8MU9T0la3SvXP/nreOtaYIBUHF/jBCy1GQnd6wYozq5CUIjd5TZNj9B6+YxfYcN19QHojn7EBsQc7o/RQ==WGH+rdm5PKPha88wFP3qNb2rIR15rT6mc6EzamY+XII0OJUlsGzAn8rdFqOnghdF7k8eDlrRWy0tELtdFIN6uA==QCiiibsF1UWw5BOOVKUBdtIu4apHc2cGwpxTQJxQkApZlsHg3bizlaaTFzudSZd0OS6WW88wxeibe/o+9IcESQ==v1ouWz3GZRmxWnTeGTVnrSdPAj2cr1qlrLikujysurcVcJM8gxg/mMfI7iNOI8cQEk5e1yNFQy2CMWzmUwr2PA==E10Pdj8MAUClJVfX5V8N9ZpLTNw7BMKA6tZISj9b+zA5DdDlXfUii59URaYq7ctJq683aBQdt28RnIJbYnfalg==ilKDDzbEPKz6MLHLlefVVENJnrEwNVl0GCF/8+50iISS1foI4iodKWuWSyJCtQi9wmfqe40Uh9GrE3Zq8YIrSg==tOC4sFSw2/3qeqr+44BKL/UW1PhijDsTDdqW9+ehG/lO1h7mqKXwSe0ubtxTbIrzT5wZjaAAsoJY03PmcT8iYQ==1THylg4tVur87HBXcVV7/7ZzPhwnGzffYklGOkz+/1h9LMQVapbG6ZspJTHSbxjRMxwqO7UqepQ2AFV2r5O0dA==7YiBuRGfisAj8wCUsILDcsDiVJn44tHH99VnByz0Xa3wlNMIQlR2080ES1rsGbnPX1r2/faF3rcpY/qIi03IlA==t874i/W2Ezvd5Wptv2PY982/F5iihqetj7ErzR3aEF7hL15f620XJJwztRcLiC20Fu597g3BdZ362bK6ky/G/Q==0VyOsK9TW8bFr6xZ05FTc1ZfGsnZXQoLgdc/fny3OMdudhEbiDWCUOBYWF4mf3idSa3GY4SBrR/zaUnmDFmueg==D/RN3kbzCm6O6h8JJ74wTPK8k3q+pIXdY7+EbvoWacHDmdhfT+erlADJ/1SABr6SOKk/N6E7szIBn/Ziiwh5HA==TNBMUdR5DmuPXKp9OcActfasGMsOjQcVciyhB0h0eWmM9+D7FQRXt5wcqs0Ah/nY26qRtViovvg3dRFwnjTbzQ==39TAWjmrXfRIAQjcAXszNjWVtfTAdm5CQY+jXMC+GRGZtyb748w5cKSeGvUSBF0InHld+A2vgXelmY7NjvYj0Q==U6MIjVo8RIUkbwIYw2Lj+kH3nmCJzoi8KvORSwQKGOtr1/wobnoW7CePSaz9QebKAqCqjzOkAeR/ef6wIlUN4Q==VTDrw46l3+mPM8EQS/rXURqmXPOMJqFp5yyDdSaTPkKgLG3cAablYoT+awFEucue5Fgqe+j25Y0dJmZKGCB13A==6tvdF6LX0cxGHzC/TL5QooU2sXLS8csF4JeIntypRv3cbAEQA6UYQLSbYUmMSGs272+R2lvSLrO7cgL9tgkaBw==inDOrsiVHV6UIy2lNPAY0/TsSoHY2hKq3Ax6wWyM1Hng3hFIQhVkL2I1atyduOWUx/U+18uEMDLLT/Wr4w33uA==EELen92ROJGhHqBxwYN4NgeX7Tv3QbjI05mcPYsYh3Da6i7cUw5WWbHvyI+r0tcZUWc4oiQ++uWjtPn9j/MyWQ==l4Llm6AK+ON5pcyQMSYAFv/OqNLZd+SpU84wPZT7w/tUX+mCqHuCEgNRFUNX4Zn4ZeMPI5zILzp8uZLvmZa3Ng==nFoEQ3rD5rRHWeKeE27jf2vatU0wtpVwzgjHPHU+d/eB", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_Link_component_handlePrefetch_FpLYno2MZMA.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_routeActionQrl_action_submit_WcRKLKMW88U.js": { + "exports": ["_hW", "s_WcRKLKMW88U"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_routeActionQrl_action_submit_WcRKLKMW88U.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": [ + "/qwik/packages/qwik-city/lib/index.qwik.mjs_routeActionQrl_action_submit_WcRKLKMW88U.js" + ], + "name": "index.qwik.mjs_routeActionQrl_action_submit_WcRKLKMW88U", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.qwik.mjs_routeActionQrl_action_submit_WcRKLKMW88U.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/core.js": ["u", "r", "b"], + "build/preloader.js": [] + }, + "imports": ["build/core.js", "build/preloader.js"], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_routeActionQrl_action_submit_WcRKLKMW88U.js": { + "code": "", + "originalLength": 1586, + "removedExports": [], + "renderedExports": ["s_WcRKLKMW88U"], + "renderedLength": 13204 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { u as useLexicalScope, r as noSerialize } from \"./core.js\";\nimport { b } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_routeActionQrl_action_submit_WcRKLKMW88U.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_routeActionQrl_action_submit_WcRKLKMW88U.js\");\nconst s_WcRKLKMW88U = (input = {}) => {\n const [currentAction, id, loc, state] = useLexicalScope();\n let data;\n let form;\n if (input instanceof SubmitEvent) {\n form = input.target;\n data = new FormData(form);\n if ((input.submitter instanceof HTMLInputElement || input.submitter instanceof HTMLButtonElement) && input.submitter.name) {\n if (input.submitter.name) data.append(input.submitter.name, input.submitter.value);\n }\n } else data = input;\n return new Promise((resolve) => {\n if (data instanceof FormData) state.formData = data;\n state.submitted = true;\n state.isRunning = true;\n loc.isNavigating = true;\n currentAction.value = {\n data,\n id,\n resolve: noSerialize(resolve)\n };\n }).then(({ result, status }) => {\n state.isRunning = false;\n state.status = status;\n state.value = result;\n if (form) {\n if (form.getAttribute(\"data-spa-reset\") === \"true\") form.reset();\n const detail = {\n status,\n value: result\n };\n form.dispatchEvent(new CustomEvent(\"submitcompleted\", {\n bubbles: false,\n cancelable: false,\n composed: false,\n detail\n }));\n }\n return {\n status,\n value: result\n };\n });\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"wpgG2UGK9JxDoPrviwpGABTh+cAiBQIMHlnlUpUGAdJ8BfjpMIbH7ZIe6VW5N+2fXI8ew+tIG1bxYIRa5O522g==t4EH3T9i0Pj6rCyyQirE9d4OwyqHBVTUmy30xCxlxVdOM5pmrrIGIs2ba/YAk955QVkaiG1/GKKs/N8aRR4XGg==zqKp3SdcGrN0tVnF7rssj4/clb3nsPK280XCzFRgGa/NODfXaenPQxIRC5WvLGNV7INGDy4YOU+yyTaOZVHnQw==ixc0q+fBcmHHqZkLGc0RpNdQN3gSq0ZHjsTgLB8PZ2h+b3TKYq78aeZs5/JlffzJJW2b2vUBG/I0Hszfn9kP6Q==hmmQWG7mHSxuhRyANpTwTsi9rH+Uv57ETy9XoDMmjwjEoR2DZz3toagIYTh5Yn/Zs84VJ+2FOtQ/j6El+M5xxQ==2mmBMH9flB+I5Hn9hhO9MY4EgzO0/0a0qlgnnBWv/+pfCDFN0XBujnCANS4m94kW0jiL8yA+APFxLjvlqDBndQ==RUXxk+PSRgtMb3hICjrIFsfLrnGkkhycyurUssLCXEf1pkNVDk3LdXO0EC7pZiWTqVtOTyYDP6ICijfb4r70Bw==RexO+ygOOqAIR4Ygd0gLY76jrBUsSPmfit1h8mW+RGpTC0Qp8YJzdMb6jpdnnGxbhX5R+0Wg8npgHx0MF3ImGg==vlFvIDfh5L0q+94MFW4tkBlMnP/QEhYtYjvmiQilPPGrF7bohZbpCcOxM9EnpkJodXYKSlpwgHKbiF+SbylS6A==Tdgm8WILAWU8kVcFDmHYeEOIriMJIK9CrXweCpjkaqw2ft9UDskYLOKD3wyNyPhu827GxJ5EZAD9hJCI52QsdQ==U8JnOGI7c/QB2NFIeINRIAELi0tgAQyCuKrcM/QFwFVca+AQf11F7bd+9hXbpeLfpVfKyVzgdmpU0O1Iz+X/0g==OQUc9vVPUEkfcU2ZOjZRjVKvUClmuwfHQwfK559oil+VbAPEfyx0Q1N2Gtd21hCW/3Kr7ih/+xdezgOy5+0aFg==Rg0a78ktuGcSmSsl0mxZtTOz/wOfZh9XG+fHeraXOrK9yaBPdA92wSNj3l5xOhRu5PpnLEBnM/Qe/XJaFLILKA==z2s2vpYLOl1t0rRwGPPJwfEFqUE2tZ1mAG+oOEJL0/QXVFT/ayb49uo6ukIOElmX0eTiobZaJTUVoQ8xq9I8GQ==/ZLlCH4cycbbqErYKIFSiQmPcfr+FYbH7+xkUNmhodYBkLKweI82NiGkO7ak7ZhXBnQ9uWAGZRkNAOOsfJ7Odw==lAJ1dL174YEq/cPo3r4EGmAS3YA08kNUVphJmqfN8aiHKa/aA2LWVQL433WDMdA2oZB8JXon1NfuUHLjQmiWlw==2n9Nui8A9Frs7OJPEep2bk40qgXNRq7cF0X5hPfJH9nTwe1iJmPVNhPW3u9baEtRfmwdMSV1Y+B2DfrJOfPbNA==yS3DKuCaoXmk0adnmRQ6YiHz3a5LaSb43D+QeMFfCeBI/yJC1DoW/836UrfDo6STfl4k3wrQ4wZU66lbi+oQcA==LBSVOyNB+U+jIzLAOrcrmInc8gc8FouFYtsdwUpK3WKqumgkksOtoJbu8d0+aFWitxdZJwDTDCiO9zvWYVKLxw==JYJWZrOtWEO7p1eJMCMJquXE+PX7ozXWURTt/4LyMSZ0JSl3D8G3EW+OJI3QUfA0WlHOtRd9jLpYN40KEMny3w==Mq6Qa3wur3vyJm3qHSiUYlIkriIWyLNYsluUBHHmhN+Sv60uhvgwTFqevxVZtX39P7/7106XrgpXy5hIxyjYJg==0C+PC9qPhbwFP8xauEK1mNlPSfcQq+sD31Zi3LNxMSaEafDZUBcBYdbDwv8FThSX+VTcPsUhqJDb+7J6tAYkjg==SBMzjpMB+zhTyWN0YNSCVq2mL4gnaSKaA3i9NkoXeoTj84V6Hc17QsEmha08jFC2T7EfHydsYwvjluY0WYKitQ==0kSH9du9MFfQVk1Kt53nvJNMb1j3zeM2EIEm+o8Q165AOmknEDDDeYbl2Uw6D7wTm9jcdDx0nk3tw21e5s2Paw==plUpBl8zCeQDCZpdrfgBNmTLXkJg4ZX/y+oKOHZPUBTIVn7zcmtvM23zjso8/BRu7ss7xLdhQH7Onj6C+vfkww==BmTEmE3JuRSM0r0vmaOWn/jk8UYrw/LUG7jIsia41k6rButEt02EeSrP+jfiXCQuEAEQZK7zHLKZ8QUW4TkomA==VZe/+54g4etNbHiFZ9vvv44Wa7mADh6zah2yAnmphYMZCJx0Dr7zc/tI/a8tu4THhX48emlV28D11Rj53uKHJg==o3orPO2Z4l3n2X0NvByAr1MnntXVBlrx/GzLsPqwSjYmlvRq6BOdpEPxjHW/8+wTguX1cPqruR7rX5h9k0pLCA==UFN1LuO8xXzXgHfcHPEcprNBrb4kVauXszr/V/Co8ddXakMOlDR7im/c27JC5ntOJ5ZmfxoaeKhaBoKLbFOSQA==1MCo5ESkyQ5zVwiGwN6ycm7GrDnWFNeTmQk6Kb23xWmoxrhVXl3r8200ZVCMjwqIPUFv8wsJAfJiOTX9Vhqsnw==Sce5qpg0oJDBidRzfZ6nsVnoqoISlOKs3edgKJpzhVJUv/bWgFYv2ZcnK36ea+Ul52ymDXg0fDnj82Wdk/cfrQ==j2XBGFaQla/WoRaGbegWrWBaCsj+tS9r00yqjavfQF9fy5WnFd/kh88CO3KrLS2Zuaz7cTB37uWn3WiOttAvHQ==UWzaK7beKAFVb2Gjyardco0n1uYxRa6TikRSrqldTr16sL5rYLike+6T+oesBoDt13bbjS9UL0PNS02kacx2pQ==cy5EoFjzs6P/K87VUKfJmUsfv9Er5iHbSGQCP3xRDrOZOHx+WkzWNNLqThHmCjXNUBVCQL5iFsE7U9+kl4D/9A==ggd6xR6W08LTiFX2/aqgQDzGqvVYgZmC4MrauDbCXKqJihjYkL7UX203Mezjydk92C8XILX+qQKHmzRG0CAGEg==T/b5sPE75ArfrLAt4JlZfTkblNXsWD9e9vMmCKAQ6b2C0O5SBJ0E+JvBjfRwsGq/x3yKhK2lDPKkvmOb4T1d6g==iDSkpbS+bzfGLF58fWq0EKxFU+5pXyMeZ/nACWkJQmkXPrhKOOOUEjDnD0RJJ2uhwzokILpm3", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_routeActionQrl_action_submit_WcRKLKMW88U.js", + "sourcemapFileName": null + }, + "build/layout.js": { + "exports": ["default", "head"], + "facadeModuleId": "/src/routes/layout.tsx", + "isDynamicEntry": true, + "isEntry": false, + "isImplicitEntry": false, + "moduleIds": ["/src/routes/layout.tsx"], + "name": "layout", + "type": "chunk", + "dynamicImports": ["build/layout.tsx_layout_component_SHtFir1Ia94.js"], + "fileName": "build/layout.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/preload-helper.js": ["_"], + "build/core.js": ["c", "q"], + "build/preloader.js": [] + }, + "imports": ["build/preload-helper.js", "build/core.js", "build/preloader.js"], + "modules": { + "/src/routes/layout.tsx": { + "code": "", + "originalLength": 1806, + "removedExports": [], + "renderedExports": ["default", "head"], + "renderedLength": 11604 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { c as componentQrl, q as qrl } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/src/routes/layout.tsx\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/layout.tsx\");\nconst layout = /* @__PURE__ */ componentQrl(/* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./layout.tsx_layout_component_SHtFir1Ia94.js\"), true ? [] : void 0), \"s_SHtFir1Ia94\"));\nconst head = {\n title: \"Preloader Test\"\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"qQSsTjmQWnXgN9GU24m98wTSN25cqiUSsFPUBvw72PC0rgemWDuQ6e08XjqBXZQitZfZel6kEKiHxs9PQ0TdGA==2K5KXBZq3tOEFUFvkgDi/i2aGaUGXWrfOjsGfQKoI/ekA0mFB+ZBXxeDXvkSyHJVwIOETL4Gkxz6bwXDPSlEmA==UXJbMSc60VFwEoHRqpv5sHf+VDxVJTXfN2NPjlRXgvDlM85+1TjbtmmRDmzTMfaGj6w90Uc3mpZRZ+d55ClGHA==+DkNAPU2HQmHL5WX9pxFTn3yllv5NzHvOneJLm7uNvnS7FMrZx+inCjxvou7YbxeXSbf7CjGQYJSxl83h4Lx6g==K7IR/hJSJ/pajcH0glqGDfGemZvzgtRrU9h9KBkFs8DOXskAMkzFd+HO9Sv+n98E+/NAFBHL7iYLV1pxQ/XP/g==ZrUHQZqPg/7CiGMs20lU+1DffeG/NFGZxcWaa+Y/1AxRXk2bDVSBVtA4Q11+4SccFx43rUpVwyTsm4Gy9Ww8uA==gZV1CGLXrR8yORLi0a+ZYRZPI9RBK4RlNA8ZtXf60kLs6SfJSKpjTMbeNgUEBEsW32OSRrjzhOGNMUehFYEXLw==ufc/Smy413XuEyZPHyu4aMZPLRIBKWyDV5YHMQTFOmdjP17dsw9cIY336hIgK5wnOWoaQ/aFe3a4BTnnd1gKIQ==HTOT/5I+ML20pRlUVmX4+lPA55n8KJ8pExx1ToMA59bg5KkvRE+doOw2tZ75CmzNP13GmfxwNx/XoqOkH2tIwg==NfXXgjizb0I/psdXKM0Dr1Wd5i8RCkyF77sPEE9ZV2/aQIhi5boDo9NKZaUKLRfhF1J+kBphoEn/JtRPJYZevQ==UnMdBTVDyIaioEnjr6aI6mjM6cIjoGtGiALnrTqvL9AfZ0egslILtc8FttCmjV6PhcY4wSc8PjmBkSnlZkQMyQ==HYeMSwd/IRCAfu9T8xouW4cBIvjD86sU4Urcj7uAKjovf03Mh8HlagCsjmOyPgm3vIb6/1g5AZ8ppUHQ6LYSAg==pu13RFpHkcNKk/Of61LWiiCsHi28hNhC2ESVVcNTae4gCpZeqwtR+Xi1W/qHKmLTqIQQvQVieynWahLW9q0x6w==Tfc2psL/BUP/MoFwPFLJGm+2wYsAAKKBGQbmVtLZj4SkK4my1AOPMOGlaARjR0LLW2DwMtCTaOoqsj1YnCId+Q==HGhI15prkizlOAbu+SfVMHWblR/WFLL3tkOKG9pJyP3oK/rtyEHIY0tIB5sdnaQkVERYXC7xpqBAgo1CjgQkPA==fiy52YEKKtw/7C4xV25841IgX/+P1jAT8ucYVUW7vChXmK5zpbJryI+tYSeP+VZfZR06NBhe4xsioCmVsTL2Ww==h6MQ3V7JmLAFKtbTJJjslFtZwbeP2SqkNmkVl6eUpzymGtFCwmAiQFYqLUNpfHdn41/QG2atd411FmgYD+LrUw==8iKked8DprQ1fMkA3aYuZ1jFlueRDTmuOFyW+ayO801zRWqbQmoi7ZxgMJCrsn/gJdBmqapUdzFdVBJqSPTVcg==PSkllRQIxqKqrSK0PtJ1YEDa2JUdL77RJg0MXVsXlFf4aXDPmCNgrM7i6M/8YZ/ByEpo8E6+Ut5o94HxIdwoDg==KzH4N33mHJhiF7knfHbp878toDZlGmskxeXm4cL1BDt5kPAV2NbD+IFWL/nVRKXqTu3CqnyRdr9Fk6CnFcfoLQ==Vt83n8GNY29auepi805R1qInrbK3+24p+GzkP/S0A54ruWMjR/p4ujk3cx7pclAZTgDA+RMsFjqcgVJ4V3buZA==zXZagyVby6fdV8wEoU3a3CTBo6VINAnxV5MB/3mqNrjTFwWHFaUS5Xqdcxixntt32UmfE+2TFrT9uMq1EnQ5eA==h0L8ZmX1PTLIw3nLcxjBUgHQa0GLqAaOzfR8PlUM1Qbt2v46m7s6ATBjtjLKm09wOMZ+EdMldeqqHXfEDnlDaA==+Rdc3sDyclpMsaQX5XfkD6qKHxOmig5B/oRzyyuhYH7Zr3VV7D/CA1kkSxu560gvU6anBHXP6qKSnYi6xTyReQ==Opf0vWAd8ftUxOwlVibon1Xwhjc1s6+fzj9m01YUZn6knH4LoVF1HC+UEfy30tY/pUpi+iKLKeDaZsF8CS/DyA==L/JWEuyR+qrCIU04wE8gBoKDcaa5T/HvHqBj/7RFuV4OgGctTRsOvlkpK1Z0vY/1SxL2KDz3bsm4t6jx6pdVcg==he69Tz/4+sFB/np2cCDlQsAy6F29SaiibKya48YRmOxRpBURaIZ7xHtIcUt9FSVIyzfL3jnIjwK6P2uD5EAUXQ==ic9hnwhTZVmTwcckaza1x8/BMFDxgx4gOhOgXbuzY5Ei8PPqsxpl6LTbWbhabZgCgHOTVd2fxYhtjVzoxqYZbQ==4b8RmSJGS8f61iqrWF9HfhBWLdVvknmLZt6lULgwn4FU4nZKlQ/Kfo5ohZ1pTH+TeJEhMBc3E9UEQ+jHj2WSrQ==Y853FxaVJkhjC9nQyJswkuX56O/5DtN6W6hbZX0pbrryNLOnZXvMa/GESJ2i2PlW7INjZQtf/5+satpA8F0Xxw==HjO1oHnaTdtICh9HvXdHRirvdgRPCna5RB4v1Lktl9PquxEKluXYD/2DlkcW0hx6ENXE7izJJH+W9gilH1f9jQ==rMo5SVL81KDnPkqRpynFQEF0hoRSaI6UQxSEchnl7t+UJWbfH05uQDIEHNaIrOChD/CdfSaywzeydHHfBa74Mw==wPYUDbCoHGp5YVUikbXFuulUacZuYXVDgGd0yvcOJoGnZZN6fDhaP+T4N30qEDKjYc/lcjZLFnBpPkJBnEdp8g==jhcs7a2rIp1Ey/sWfaDLP+Kw4WXxJ4iv8XoMn5G9Uo2eS68dsjn+vUyP7dLNYC6evBOLu6XRtdfiXUNXQJKiQg==Phu4tk/HSV2PFPz5PEeugDimX8AYVpwEIJCdYmT5lljzO9suw/w0zDiIUjUPw9dAF+Bxoe8vsDXtFv/WE8iY0A==xBCFog4785Ed53ug7QLSWLkrQW7aT/Nig8u9Yfppo0ZHg6uPJPbwb0zRn3bmMJreBlWUVTQ12OpkvyfP3qZjig==LLkSqAewomtoU3ZPaJRnAE+5pyeNBOSI7DPnfI3XQrtDPdGkepJ/sNCH/EvoylRL5LNiBKavxkeANaFM8Avv3Q==/iuBL2vjIenQ86J2CO9r3qTeRk+SHnnMDVZAp5Xz/Q944aYwhgve4syqyiAWaheAss+eEdHpizVV1/5NUCMWLQ==UKAK0Bi9lqh2DKx63v5xDcVjquUm5+BUk4EHptNS09/oc39e1FctAtM+A2ojMlRqusFqCjj0ZaAe2DLVKl61aA==RycG0K2mCCX5EFnea6l7nxR9vTkEual9F3riSqULL9y3bWHt9xhzpVms3JBLxTC8kHRMry7INX6VRyCGsuLIkw==VR3qEhaRphx2gCZUaxzaVbJLoSWGw5URFXcd++vCwWOYK3hcdjxt/QozhAf3VzPRq5dzHxBhF8tR9UT8zi96kQ==wbKBl9E9IVTrHjg71iwaqIQSt0r4nTBQrctpSvg9Etv1/jut8C0EAvT0uTus7OLkmb9rgjFlN5XJHU14Y+l4fQ==CnULfEkt1KBrOU0/HdnT4gLn6HWhp4VjApS0x1L9fBeY2K0Qvwx2qdH0HUF/HUmk7K45lssc5t+bculIMzAHaQ==ZJy0wYkMx06bvd+8DcR94uz+SLgZEyQLWfAQs4FEFaeWF1pRiqXdHauraaR0EsHUC7ZT7eBXE7MvFhazbzHJog==Oq4qQIW5Nht+dIAHVCU0A4srP9si0ziFirZQc3nzKWqYVY4pUChPvBzoMf+Sv7OYFPqhDsszneH7zhM1IClKTA==/wqeTJnpnJN8RD3AvlEM3FYE1UV03xn/cl8wC7ogN/x60uggxH8iP15y8FvhakQ1Zq2RSoP6gXszEP4mweEupw==f7V5za8S1txnB8/ZvHWeuQ8E86vgtDhq7lC6BkqkVflsL4ifXbxC0Vx8UmJJBlQM4Z/1hvuJ+WwAc9Oyf+oPhA==rmywuACn/wfWRmxqiiwWEaok56ODhZG4NE/9id8oQOcIIx5R6wpUG6pADONIpGKz9diQJloNj56SNVWw9oeLOQ==r6//m/o6QOgkXDeR6nUohI4HtJRaYSkPXPj7ekAaS6zRYzirTwGgw4Nm3J+2A4XudEUosyMvAnBj4TgL", + "map": "", + "preliminaryFileName": "build/layout.js", + "sourcemapFileName": null + }, + "build/index.js": { + "exports": ["_auto_getLibA", "_auto_getLibB", "default", "head"], + "facadeModuleId": "/src/routes/index.tsx", + "isDynamicEntry": true, + "isEntry": false, + "isImplicitEntry": false, + "moduleIds": ["/src/routes/index.tsx"], + "name": "index", + "type": "chunk", + "dynamicImports": [ + "build/src-vendor-lib-libA.ts.js", + "build/src-vendor-lib-libB.ts.js", + "build/index.tsx_routes_component_vG0UuU4cNCg.js" + ], + "fileName": "build/index.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/preload-helper.js": ["_"], + "build/core.js": ["c", "q"], + "build/preloader.js": [] + }, + "imports": ["build/preload-helper.js", "build/core.js", "build/preloader.js"], + "modules": { + "/src/routes/index.tsx": { + "code": "", + "originalLength": 1954, + "removedExports": [], + "renderedExports": ["default", "head", "_auto_getLibA", "_auto_getLibB"], + "renderedLength": 882 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { c as componentQrl, q as qrl } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/src/routes/index.tsx\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/index.tsx\");\nconst getLibA = () => __vitePreload(() => import(\"./src-vendor-lib-libA.ts.js\"), true ? [] : void 0);\nconst getLibB = () => __vitePreload(() => import(\"./src-vendor-lib-libB.ts.js\"), true ? [] : void 0);\nconst index = /* @__PURE__ */ componentQrl(/* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.tsx_routes_component_vG0UuU4cNCg.js\"), true ? [] : void 0), \"s_vG0UuU4cNCg\"));\nconst head = {\n title: \"Home - Preloader Test\",\n meta: [\n {\n name: \"description\",\n content: \"Welcome to the Preloader Test application\"\n }\n ]\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"\");\nexport {\n getLibA as _auto_getLibA,\n getLibB as _auto_getLibB,\n index as default,\n head\n};\n", + "map": "", + "preliminaryFileName": "build/index.js", + "sourcemapFileName": null + }, + "build/index2.js": { + "exports": ["default", "head"], + "facadeModuleId": "/src/routes/about/index.tsx", + "isDynamicEntry": true, + "isEntry": false, + "isImplicitEntry": false, + "moduleIds": ["/src/routes/about/index.tsx"], + "name": "index", + "type": "chunk", + "dynamicImports": ["build/index.tsx_about_component_m7u9ARcfDGU.js"], + "fileName": "build/index2.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/preload-helper.js": ["_"], + "build/core.js": ["c", "q"], + "build/preloader.js": [] + }, + "imports": ["build/preload-helper.js", "build/core.js", "build/preloader.js"], + "modules": { + "/src/routes/about/index.tsx": { + "code": "", + "originalLength": 1898, + "removedExports": [], + "renderedExports": ["default", "head"], + "renderedLength": 662 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { c as componentQrl, q as qrl } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/src/routes/about/index.tsx\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/about/index.tsx\");\nconst index = /* @__PURE__ */ componentQrl(/* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.tsx_about_component_m7u9ARcfDGU.js\"), true ? [] : void 0), \"s_m7u9ARcfDGU\"));\nconst head = {\n title: \"About - Preloader Test\",\n meta: [\n {\n name: \"description\",\n content: \"Learn about the Preloader Test application and its features\"\n }\n ]\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"\");\nexport {\n index as default,\n head\n};\n", + "map": "", + "preliminaryFileName": "build/index2.js", + "sourcemapFileName": null + }, + "build/index3.js": { + "exports": ["default", "head", "useFormAction"], + "facadeModuleId": "/src/routes/form/index.tsx", + "isDynamicEntry": true, + "isEntry": false, + "isImplicitEntry": false, + "moduleIds": ["/src/routes/form/index.tsx"], + "name": "index", + "type": "chunk", + "dynamicImports": ["build/index.tsx_form_component_ds9jIPT1g9s.js"], + "fileName": "build/index3.js", + "implicitlyLoadedBefore": [], + "importedBindings": { + "build/preload-helper.js": ["_"], + "build/qwik-city.js": ["O"], + "build/core.js": ["c", "q", "J"], + "build/preloader.js": [] + }, + "imports": [ + "build/preload-helper.js", + "build/qwik-city.js", + "build/core.js", + "build/preloader.js" + ], + "modules": { + "/src/routes/form/index.tsx": { + "code": "", + "originalLength": 3445, + "removedExports": [], + "renderedExports": ["useFormAction", "default", "head"], + "renderedLength": 735 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { O as routeActionQrl } from \"./qwik-city.js\";\nimport { c as componentQrl, q as qrl, J as _noopQrl } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/src/routes/form/index.tsx\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/form/index.tsx\");\nconst useFormAction = routeActionQrl(/* @__PURE__ */ _noopQrl(\"s_DHfw8GZWF6Q\"));\nconst index = /* @__PURE__ */ componentQrl(/* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.tsx_form_component_ds9jIPT1g9s.js\"), true ? [] : void 0), \"s_ds9jIPT1g9s\"));\nconst head = {\n title: \"Contact Form - Preloader Test\",\n meta: [\n {\n name: \"description\",\n content: \"Contact form for the Preloader Test application\"\n }\n ]\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"\");\nexport {\n index as default,\n head,\n useFormAction\n};\n", + "map": "", + "preliminaryFileName": "build/index3.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_spaInit_event_EMGw8L1tB9Y.js": { + "exports": ["s_EMGw8L1tB9Y"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_spaInit_event_EMGw8L1tB9Y.js", + "isDynamicEntry": true, "isEntry": true, "isImplicitEntry": false, - "moduleIds": [ - "/starters/apps/perf.prod/src/global.css", - "/starters/apps/perf.prod/src/root.tsx" - ], - "name": "root", + "moduleIds": ["/qwik/packages/qwik-city/lib/index.qwik.mjs_spaInit_event_EMGw8L1tB9Y.js"], + "name": "index.qwik.mjs_spaInit_event_EMGw8L1tB9Y", "type": "chunk", "dynamicImports": [], - "fileName": "build/root.js", + "fileName": "build/index.qwik.mjs_spaInit_event_EMGw8L1tB9Y.js", + "implicitlyLoadedBefore": [], + "importedBindings": {}, + "imports": [], + "modules": { + "/qwik/packages/qwik-city/lib/index.qwik.mjs_spaInit_event_EMGw8L1tB9Y.js": { + "code": "", + "originalLength": 5912, + "removedExports": [], + "renderedExports": ["s_EMGw8L1tB9Y"], + "renderedLength": 6295 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_spaInit_event_EMGw8L1tB9Y.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_spaInit_event_EMGw8L1tB9Y.js\");\nconst s_EMGw8L1tB9Y = (_, el) => {\n const win = window;\n const spa = \"_qCitySPA\";\n const initPopstate = \"_qCityInitPopstate\";\n const initAnchors = \"_qCityInitAnchors\";\n const initVisibility = \"_qCityInitVisibility\";\n const initScroll = \"_qCityInitScroll\";\n if (!win[spa] && !win[initPopstate] && !win[initAnchors] && !win[initVisibility] && !win[initScroll]) {\n const currentPath = location.pathname + location.search;\n const historyPatch = \"_qCityHistoryPatch\";\n const bootstrap = \"_qCityBootstrap\";\n const scrollEnabled = \"_qCityScrollEnabled\";\n const debounceTimeout = \"_qCityScrollDebounce\";\n const scrollHistory = \"_qCityScroll\";\n const checkAndScroll = (scrollState) => {\n if (scrollState) win.scrollTo(scrollState.x, scrollState.y);\n };\n const currentScrollState2 = () => {\n const elm = document.documentElement;\n return {\n x: elm.scrollLeft,\n y: elm.scrollTop,\n w: Math.max(elm.scrollWidth, elm.clientWidth),\n h: Math.max(elm.scrollHeight, elm.clientHeight)\n };\n };\n const saveScrollState = (scrollState) => {\n const state = history.state || {};\n state[scrollHistory] = scrollState || currentScrollState2();\n history.replaceState(state, \"\");\n };\n saveScrollState();\n win[initPopstate] = () => {\n var _a2, _b;\n if (win[spa]) return;\n win[scrollEnabled] = false;\n clearTimeout(win[debounceTimeout]);\n if (currentPath !== location.pathname + location.search) {\n const getContainer2 = (el2) => el2.closest(\"[q\\\\:container]\");\n const link = (_a2 = getContainer2(el)) == null ? void 0 : _a2.querySelector(\"a[q\\\\:link]\");\n if (link) {\n const container = getContainer2(link);\n const bootstrapLink = link.cloneNode();\n bootstrapLink.setAttribute(\"q:nbs\", \"\");\n bootstrapLink.style.display = \"none\";\n container.appendChild(bootstrapLink);\n win[bootstrap] = bootstrapLink;\n bootstrapLink.click();\n } else location.reload();\n } else if (history.scrollRestoration === \"manual\") {\n const scrollState = (_b = history.state) == null ? void 0 : _b[scrollHistory];\n checkAndScroll(scrollState);\n win[scrollEnabled] = true;\n }\n };\n if (!win[historyPatch]) {\n win[historyPatch] = true;\n const pushState = history.pushState;\n const replaceState = history.replaceState;\n const prepareState = (state) => {\n if (state === null || typeof state === \"undefined\") state = {};\n else if ((state == null ? void 0 : state.constructor) !== Object) state = {\n _data: state\n };\n state._qCityScroll = state._qCityScroll || currentScrollState2();\n return state;\n };\n history.pushState = (state, title, url) => {\n state = prepareState(state);\n return pushState.call(history, state, title, url);\n };\n history.replaceState = (state, title, url) => {\n state = prepareState(state);\n return replaceState.call(history, state, title, url);\n };\n }\n win[initAnchors] = (event) => {\n if (win[spa] || event.defaultPrevented) return;\n const target = event.target.closest(\"a[href]\");\n if (target && !target.hasAttribute(\"preventdefault:click\")) {\n const href = target.getAttribute(\"href\");\n const prev = new URL(location.href);\n const dest = new URL(href, prev);\n const sameOrigin = dest.origin === prev.origin;\n const samePath = dest.pathname + dest.search === prev.pathname + prev.search;\n if (sameOrigin && samePath) {\n event.preventDefault();\n if (dest.href !== prev.href) history.pushState(null, \"\", dest);\n if (!dest.hash) {\n if (dest.href.endsWith(\"#\")) window.scrollTo(0, 0);\n else {\n win[scrollEnabled] = false;\n clearTimeout(win[debounceTimeout]);\n saveScrollState({\n ...currentScrollState2(),\n x: 0,\n y: 0\n });\n location.reload();\n }\n } else {\n const elmId = dest.hash.slice(1);\n const elm = document.getElementById(elmId);\n if (elm) elm.scrollIntoView();\n }\n }\n }\n };\n win[initVisibility] = () => {\n if (!win[spa] && win[scrollEnabled] && document.visibilityState === \"hidden\") saveScrollState();\n };\n win[initScroll] = () => {\n if (win[spa] || !win[scrollEnabled]) return;\n clearTimeout(win[debounceTimeout]);\n win[debounceTimeout] = setTimeout(() => {\n saveScrollState();\n win[debounceTimeout] = void 0;\n }, 20", + "map": "", + "preliminaryFileName": "build/index.qwik.mjs_spaInit_event_EMGw8L1tB9Y.js", + "sourcemapFileName": null + }, + "build/index.qwik.mjs_Link_component_bp3n7NtzXfs.js": { + "exports": ["s_bp3n7NtzXfs"], + "facadeModuleId": "/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_bp3n7NtzXfs.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": ["/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_bp3n7NtzXfs.js"], + "name": "index.qwik.mjs_Link_component_bp3n7NtzXfs", + "type": "chunk", + "dynamicImports": [ + "build/index.qwik.mjs_Link_component_handlePrefetch_FpLYno2MZMA.js", + "build/index.qwik.mjs_Link_component_handleClick_gZBt5yIBEB4.js" + ], + "fileName": "build/index.qwik.mjs_Link_component_bp3n7NtzXfs.js", "implicitlyLoadedBefore": [], "importedBindings": { - "build/core.js": ["_", "a"], - "build/app.js": ["A"], - "build/preload-helper.js": [] + "build/preload-helper.js": ["_"], + "build/core.js": ["C", "g", "z", "q", "_", "n"], + "build/qwik-city.js": ["A", "H", "I", "J", "K"], + "build/preloader.js": [] }, - "imports": ["build/core.js", "build/app.js", "build/preload-helper.js"], + "imports": [ + "build/preload-helper.js", + "build/core.js", + "build/qwik-city.js", + "build/preloader.js" + ], "modules": { - "/starters/apps/perf.prod/src/global.css": { - "code": "", - "originalLength": 45, - "removedExports": [], - "renderedExports": [], - "renderedLength": 0 - }, - "/starters/apps/perf.prod/src/root.tsx": { - "code": "", - "originalLength": 356, + "/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_bp3n7NtzXfs.js": { + "code": "", + "originalLength": 2657, "removedExports": [], - "renderedExports": ["default"], - "renderedLength": 1007 + "renderedExports": ["s_bp3n7NtzXfs"], + "renderedLength": 2914 } }, "referencedFiles": [], @@ -164,135 +2289,32 @@ "importedAssets": {}, "importedCss": {} }, - "code": "import { _ as _jsxQ, a as _jsxC } from \"./core.js\";\nimport { A as App } from \"./app.js\";\nimport \"./preload-helper.js\";\nconst root = () => {\n return /* @__PURE__ */ _jsxQ(\"html\", null, null, [\n /* @__PURE__ */ _jsxQ(\"head\", null, null, [\n /* @__PURE__ */ _jsxQ(\"meta\", null, {\n charset: \"utf-8\"\n }, null, 3, null, {\n fileName: \"root.tsx\",\n lineNumber: 9,\n columnNumber: 9\n }),\n /* @__PURE__ */ _jsxQ(\"title\", null, null, \"Qwik Blank App\", 3, null, {\n fileName: \"root.tsx\",\n lineNumber: 10,\n columnNumber: 9\n }),\n /* @__PURE__ */ _jsxQ(\"meta\", null, {\n name: \"viewport\",\n content: \"width=device-width, initial-scale=1.0\"\n }, null, 3, null, {\n fileName: \"root.tsx\",\n lineNumber: 11,\n columnNumber: 9\n })\n ], 3, null, {\n fileName: \"root.tsx\",\n lineNumber: 8,\n columnNumber: 7\n }),\n /* @__PURE__ */ _jsxQ(\"body\", null, null, /* @__PURE__ */ _jsxC(App, null, 3, \"0t_0\"), 1, null, {\n fileName: \"root.tsx\",\n lineNumber: 13,\n columnNumber: 7\n })\n ], 1, \"0t_1\");\n};\nexport {\n root as default\n};\n", + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { C as untrack, g as _qrlSync, z as _jsxS, q as qrl, _ as _jsxC, n as Slot } from \"./core.js\";\nimport { A as useNavigate, H as useLocation, I as getClientNavPath, J as shouldPrefetchData, K as shouldPrefetchSymbols } from \"./qwik-city.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_bp3n7NtzXfs.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/qwik/packages/qwik-city/lib/index.qwik.mjs_Link_component_bp3n7NtzXfs.js\");\nconst s_bp3n7NtzXfs = (props) => {\n const nav = useNavigate();\n const loc = useLocation();\n const { onClick$, prefetch: prefetchProp, reload, replaceState, scroll, ...linkProps } = /* @__PURE__ */ (() => props)();\n const clientNavPath = untrack(() => getClientNavPath({\n ...linkProps,\n reload\n }, loc));\n linkProps.href = clientNavPath || props.href;\n const prefetchData = untrack(() => !!clientNavPath && prefetchProp !== false && prefetchProp !== \"js\" && shouldPrefetchData(clientNavPath, loc) || void 0);\n const prefetch = untrack(() => prefetchData || !!clientNavPath && prefetchProp !== false && shouldPrefetchSymbols(clientNavPath, loc));\n const handlePrefetch = prefetch ? /* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.qwik.mjs_Link_component_handlePrefetch_FpLYno2MZMA.js\"), true ? [] : void 0), \"s_FpLYno2MZMA\") : void 0;\n const preventDefault = clientNavPath ? _qrlSync((event, target) => {\n if (!(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)) event.preventDefault();\n }, \"(event,target)=>{if(!(event.metaKey||event.ctrlKey||event.shiftKey||event.altKey)){event.preventDefault();}}\") : void 0;\n const handleClick = clientNavPath ? /* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.qwik.mjs_Link_component_handleClick_gZBt5yIBEB4.js\"), true ? [] : void 0), \"s_gZBt5yIBEB4\", [\n nav,\n reload,\n replaceState,\n scroll\n ]) : void 0;\n return /* @__PURE__ */ _jsxS(\"a\", {\n \"q:link\": !!clientNavPath,\n ...linkProps,\n \"data-prefetch\": prefetchData,\n children: /* @__PURE__ */ _jsxC(Slot, null, 3, \"uY_5\"),\n onClick$: [\n preventDefault,\n onClick$,\n handleClick\n ],\n onMouseOver$: [\n linkProps.onMouseOver$,\n handlePrefetch\n ],\n onFocus$: [\n linkProps.onFocus$,\n handlePrefetch\n ],\n // Don't prefetch on visible in dev mode\n onQVisible$: [\n linkProps.onQVisible$,\n handlePrefetch\n ]\n }, null, 0, \"uY_6\");\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"JwJKyrm6PEVQJuWMTvfX2SGfU8+sG8LomOtJTDL6JmzmILXlZjzgbZ2HWLwA1f2+qzO0PA86qadGcxObZ9iiNw==e1brRJCDN88YxpOKW7IVk8JMGAZzyNjx7Xo1rudCjDdEEUCE0IIAQybuSVBxQ/zD9zYNbEiuwB936lwU4w95mA==xvhgULy3vpxrgfiFkijJ12WxcK8rlLs/VzDqyHEmFq6u49/Pq5UeXVCRsuk/YCZCwJTRVOcYMair6G4afMmC0A==sPoYRQb5WvSFJHIdHmW3/BpMX8US/LQ6bENz8kYlWs8iGVXNNNlTrL8jRWwzzwDGKNgSJDFdOf7YYAQciLWfgw==j03ohuASU/ba4I5+YWq7VVUKBLJqeoJdlqmHXTsCY6O/VRbT6nPl63Lgj59js2nLklrkqTe817LrdcACD+gJGA==\");\nexport {\n s_bp3n7NtzXfs\n};\n", "map": "", - "preliminaryFileName": "build/root.js", + "preliminaryFileName": "build/index.qwik.mjs_Link_component_bp3n7NtzXfs.js", "sourcemapFileName": null }, - "build/core.js": { - "exports": ["_", "a", "b", "c", "q", "u"], - "facadeModuleId": null, - "isDynamicEntry": false, - "isEntry": false, + "build/index.tsx_about_component_useStyles_WOcPLNnm2is.js": { + "exports": ["s_WOcPLNnm2is"], + "facadeModuleId": "/src/routes/about/index.tsx_about_component_useStyles_WOcPLNnm2is.js", + "isDynamicEntry": true, + "isEntry": true, "isImplicitEntry": false, - "moduleIds": ["@builder.io/qwik/build", "/packages/qwik/dist/core.mjs"], - "name": "core", + "moduleIds": ["/src/routes/about/index.tsx_about_component_useStyles_WOcPLNnm2is.js"], + "name": "index.tsx_about_component_useStyles_WOcPLNnm2is", "type": "chunk", "dynamicImports": [], - "fileName": "build/core.js", + "fileName": "build/index.tsx_about_component_useStyles_WOcPLNnm2is.js", "implicitlyLoadedBefore": [], "importedBindings": {}, "imports": [], "modules": { - "@builder.io/qwik/build": { - "code": "", - "originalLength": 115, - "removedExports": ["isBrowser", "isDev"], - "renderedExports": ["isServer"], - "renderedLength": 49 - }, - "/packages/qwik/dist/core.mjs": { - "code": "", - "originalLength": 258665, - "removedExports": [ - "HTMLFragment", - "PrefetchGraph", - "PrefetchServiceWorker", - "RenderOnce", - "Resource", - "SSRComment", - "SSRHint", - "SSRRaw", - "SSRStream", - "SSRStreamBlock", - "_deserializeData", - "_fnSignal", - "_getContextElement", - "_getContextEvent", - "_hW", - "_jsxBranch", - "_jsxS", - "_noopQrl", - "_noopQrlDEV", - "_qrlSync", - "_regSymbol", - "_renderSSR", - "_restProps", - "_serializeData", - "_verifySerializable", - "_waitUntilRendered", - "_weakSerialize", - "_wrapProp", - "_wrapSignal", - "component$", - "createComputed$", - "createComputedQrl", - "createElement", - "createSignal", - "event$", - "eventQrl", - "getLocale", - "h", - "implicit$FirstArg", - "inlinedQrl", - "inlinedQrlDEV", - "isBrowser", - "isDev", - "jsx", - "jsxDEV", - "jsxs", - "render", - "setPlatform", - "sync$", - "useComputed$", - "useComputedQrl", - "useConstant", - "useContext", - "useContextProvider", - "useErrorBoundary", - "useId", - "useOn", - "useOnDocument", - "useOnWindow", - "useResource$", - "useResourceQrl", - "useServerData", - "useSignal", - "useStyles$", - "useStylesQrl", - "useStylesScoped$", - "useStylesScopedQrl", - "useTask$", - "useTaskQrl", - "useVisibleTask$", - "useVisibleTaskQrl", - "version", - "withLocale" - ], - "renderedExports": [ - "$", - "Fragment", - "SkipRender", - "Slot", - "_IMMUTABLE", - "_jsxC", - "_jsxQ", - "_pauseFromContexts", - "_preload", - "componentQrl", - "createContextId", - "getPlatform", - "isServer", - "isSignal", - "noSerialize", - "qrl", - "qrlDEV", - "untrack", - "unwrapStore", - "useLexicalScope", - "useStore" - ], - "renderedLength": 164160 + "/src/routes/about/index.tsx_about_component_useStyles_WOcPLNnm2is.js": { + "code": "", + "originalLength": 630, + "removedExports": [], + "renderedExports": ["s_WOcPLNnm2is"], + "renderedLength": 1161 } }, "referencedFiles": [], @@ -300,35 +2322,36 @@ "importedAssets": {}, "importedCss": {} }, - "code": "removed for fixture", + "code": "var _a;\nconsole.log(\">>> running\", \"/src/routes/about/index.tsx_about_component_useStyles_WOcPLNnm2is.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/about/index.tsx_about_component_useStyles_WOcPLNnm2is.js\");\nconst s_WOcPLNnm2is = `\n .about-container {\n max-width: 42rem;\n margin: 0 auto;\n }\n\n .title {\n font-size: 1.875rem;\n font-weight: bold;\n margin-bottom: 1rem;\n }\n\n .content {\n font-size: 1rem;\n line-height: 1.5;\n }\n\n .paragraph {\n margin-bottom: 1rem;\n }\n\n .subtitle {\n font-size: 1.5rem;\n font-weight: bold;\n margin-top: 1.5rem;\n margin-bottom: 0.75rem;\n }\n\n .feature-list {\n list-style-type: disc;\n padding-left: 1.25rem;\n margin-bottom: 1rem;\n }\n\n .feature-list li {\n margin-bottom: 0.5rem;\n }\n `;\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"FtfbHwMeTmFiKXow0vTDqaw9XjTU5KOKqUIMTFd+H7zVzY3PHCnrtOKJZ/Qam/F0XFiwAY5EDJVZQ35bUAKQ6A==/thkvVusR+DfFxowti/MeYNco9A0jfNkmoN44yVbSirZ7T0r1VNX+j0oR4cZTC5XqgErTvKErms2Xu1x2dS+mA==\");\nexport {\n s_WOcPLNnm2is\n};\n", "map": "", - "preliminaryFileName": "build/core.js", + "preliminaryFileName": "build/index.tsx_about_component_useStyles_WOcPLNnm2is.js", "sourcemapFileName": null }, - "build/app.js": { - "exports": ["A", "b"], - "facadeModuleId": null, - "isDynamicEntry": false, - "isEntry": false, + "build/index.tsx_about_component_m7u9ARcfDGU.js": { + "exports": ["s_m7u9ARcfDGU"], + "facadeModuleId": "/src/routes/about/index.tsx_about_component_m7u9ARcfDGU.js", + "isDynamicEntry": true, + "isEntry": true, "isImplicitEntry": false, - "moduleIds": ["/starters/apps/perf.prod/src/components/app/app.tsx"], - "name": "app", + "moduleIds": ["/src/routes/about/index.tsx_about_component_m7u9ARcfDGU.js"], + "name": "index.tsx_about_component_m7u9ARcfDGU", "type": "chunk", - "dynamicImports": ["build/app.tsx_App_component_jn5XSz7NZ88.js"], - "fileName": "build/app.js", + "dynamicImports": ["build/index.tsx_about_component_useStyles_WOcPLNnm2is.js"], + "fileName": "build/index.tsx_about_component_m7u9ARcfDGU.js", "implicitlyLoadedBefore": [], "importedBindings": { "build/preload-helper.js": ["_"], - "build/core.js": ["c", "q"] + "build/core.js": ["h", "a", "q"], + "build/preloader.js": [] }, - "imports": ["build/preload-helper.js", "build/core.js"], + "imports": ["build/preload-helper.js", "build/core.js", "build/preloader.js"], "modules": { - "/starters/apps/perf.prod/src/components/app/app.tsx": { - "code": "", - "originalLength": 5135, + "/src/routes/about/index.tsx_about_component_m7u9ARcfDGU.js": { + "code": "", + "originalLength": 1804, "removedExports": [], - "renderedExports": ["buildData", "App"], - "renderedLength": 1351 + "renderedExports": ["s_m7u9ARcfDGU"], + "renderedLength": 2329 } }, "referencedFiles": [], @@ -336,32 +2359,32 @@ "importedAssets": {}, "importedCss": {} }, - "code": "import { _ as __vitePreload } from \"./preload-helper.js\";\nimport { c as componentQrl, q as qrlDEV } from \"./core.js\";\nlet idCounter = 1;\nconst adjectives = [\n \"pretty\",\n \"large\",\n \"big\",\n \"small\",\n \"tall\",\n \"short\",\n \"long\",\n \"handsome\",\n \"plain\",\n \"quaint\",\n \"clean\",\n \"elegant\",\n \"easy\",\n \"angry\",\n \"crazy\",\n \"helpful\",\n \"mushy\",\n \"odd\",\n \"unsightly\",\n \"adorable\",\n \"important\",\n \"inexpensive\",\n \"cheap\",\n \"expensive\",\n \"fancy\"\n], colours = [\n \"red\",\n \"yellow\",\n \"blue\",\n \"green\",\n \"pink\",\n \"brown\",\n \"purple\",\n \"brown\",\n \"white\",\n \"black\",\n \"orange\"\n], nouns = [\n \"table\",\n \"chair\",\n \"house\",\n \"bbq\",\n \"desk\",\n \"car\",\n \"pony\",\n \"cookie\",\n \"sandwich\",\n \"burger\",\n \"pizza\",\n \"mouse\",\n \"keyboard\"\n];\nfunction _random(max) {\n return Math.round(Math.random() * 1e3) % max;\n}\nfunction buildData(count) {\n const data = new Array(count);\n for (let i = 0; i < count; i++) data[i] = {\n id: idCounter++,\n label: `${adjectives[_random(adjectives.length)]} ${colours[_random(colours.length)]} ${nouns[_random(nouns.length)]}`\n };\n return data;\n}\nconst App = /* @__PURE__ */ componentQrl(/* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_jn5XSz7NZ88.js\"), true ? [] : void 0), \"App_component_jn5XSz7NZ88\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 1334,\n hi: 5133,\n displayName: \"app.tsx_App_component\"\n}));\nexport {\n App as A,\n buildData as b\n};\n", + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { h as useStylesQrl, a as _jsxQ, q as qrl } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/src/routes/about/index.tsx_about_component_m7u9ARcfDGU.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/about/index.tsx_about_component_m7u9ARcfDGU.js\");\nconst s_m7u9ARcfDGU = () => {\n useStylesQrl(/* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.tsx_about_component_useStyles_WOcPLNnm2is.js\"), true ? [] : void 0), \"s_WOcPLNnm2is\"));\n return /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"about-container\"\n }, [\n /* @__PURE__ */ _jsxQ(\"h1\", null, {\n class: \"title\"\n }, \"About Preloader Test\", 3, null),\n /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"content\"\n }, [\n /* @__PURE__ */ _jsxQ(\"p\", null, {\n class: \"paragraph\"\n }, \"This application demonstrates the preloading capabilities of Qwik. It shows how Qwik can efficiently load only the necessary JavaScript code when needed, resulting in faster page loads and better performance.\", 3, null),\n /* @__PURE__ */ _jsxQ(\"h2\", null, {\n class: \"subtitle\"\n }, \"Features\", 3, null),\n /* @__PURE__ */ _jsxQ(\"ul\", null, {\n class: \"feature-list\"\n }, [\n /* @__PURE__ */ _jsxQ(\"li\", null, null, \"Route-based code splitting\", 3, null),\n /* @__PURE__ */ _jsxQ(\"li\", null, null, \"Form handling with validation\", 3, null),\n /* @__PURE__ */ _jsxQ(\"li\", null, null, \"Toggle between native links and Qwik Link components\", 3, null),\n /* @__PURE__ */ _jsxQ(\"li\", null, null, \"Responsive design with CSS\", 3, null)\n ], 3, null),\n /* @__PURE__ */ _jsxQ(\"p\", null, {\n class: \"paragraph\"\n }, \"Feel free to explore the different pages and observe how Qwik handles navigation and form interactions efficiently.\", 3, null)\n ], 3, null)\n ], 3, \"xE_0\");\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"IxPSpht6t+WQ4/2aAWkqh+GfDKpcrBNALlm5TYjJQYjQGXxtWUJrRSF+jn9Xu8NoM9t/OAiBz4nfdKQHbdoGNA==xWEQyoPjB1xdKy9uPlkf0HsMHIh2kAh1PLkmZvVlakE+AzlV0bvfkj1hJzxIHGvHoyNJDrmXxfsfG5y1h/HS3Q==rF74crDYflEZLdFYh5jL2llFram8sad0u6JeXTJ+DgWHqHbpKR1WnNnbvyGzrMw+4OC5uLpYApqJFp9MSCBnRQ==\");\nexport {\n s_m7u9ARcfDGU\n};\n", "map": "", - "preliminaryFileName": "build/app.js", + "preliminaryFileName": "build/index.tsx_about_component_m7u9ARcfDGU.js", "sourcemapFileName": null }, - "build/preload-helper.js": { - "exports": ["_"], - "facadeModuleId": null, - "isDynamicEntry": false, - "isEntry": false, + "build/layout.tsx_layout_component_useStyles_MOLFIZOhXmE.js": { + "exports": ["s_MOLFIZOhXmE"], + "facadeModuleId": "/src/routes/layout.tsx_layout_component_useStyles_MOLFIZOhXmE.js", + "isDynamicEntry": true, + "isEntry": true, "isImplicitEntry": false, - "moduleIds": ["\u0000vite/preload-helper.js"], - "name": "preload-helper", + "moduleIds": ["/src/routes/layout.tsx_layout_component_useStyles_MOLFIZOhXmE.js"], + "name": "layout.tsx_layout_component_useStyles_MOLFIZOhXmE", "type": "chunk", "dynamicImports": [], - "fileName": "build/preload-helper.js", + "fileName": "build/layout.tsx_layout_component_useStyles_MOLFIZOhXmE.js", "implicitlyLoadedBefore": [], "importedBindings": {}, "imports": [], "modules": { - "\u0000vite/preload-helper.js": { - "code": "", - "originalLength": 2281, + "/src/routes/layout.tsx_layout_component_useStyles_MOLFIZOhXmE.js": { + "code": "", + "originalLength": 798, "removedExports": [], - "renderedExports": ["__vitePreload"], - "renderedLength": 1945 + "renderedExports": ["s_MOLFIZOhXmE"], + "renderedLength": 1233 } }, "referencedFiles": [], @@ -369,38 +2392,37 @@ "importedAssets": {}, "importedCss": {} }, - "code": "const scriptRel = function detectScriptRel() {\n const relList = typeof document !== \"undefined\" && document.createElement(\"link\").relList;\n return relList && relList.supports && relList.supports(\"modulepreload\") ? \"modulepreload\" : \"preload\";\n}();\nconst assetsURL = function(dep) {\n return \"/perf.prod/\" + dep;\n};\nconst seen = {};\nconst __vitePreload = function preload(baseModule, deps, importerUrl) {\n let promise = Promise.resolve();\n if (deps && deps.length > 0) {\n document.getElementsByTagName(\"link\");\n const cspNonceMeta = document.querySelector(\n \"meta[property=csp-nonce]\"\n );\n const cspNonce = (cspNonceMeta == null ? void 0 : cspNonceMeta.nonce) || (cspNonceMeta == null ? void 0 : cspNonceMeta.getAttribute(\"nonce\"));\n promise = Promise.all(\n deps.map((dep) => {\n dep = assetsURL(dep);\n if (dep in seen) return;\n seen[dep] = true;\n const isCss = dep.endsWith(\".css\");\n const cssSelector = isCss ? '[rel=\"stylesheet\"]' : \"\";\n if (document.querySelector(`link[href=\"${dep}\"]${cssSelector}`)) {\n return;\n }\n const link = document.createElement(\"link\");\n link.rel = isCss ? \"stylesheet\" : scriptRel;\n if (!isCss) {\n link.as = \"script\";\n link.crossOrigin = \"\";\n }\n link.href = dep;\n if (cspNonce) {\n link.setAttribute(\"nonce\", cspNonce);\n }\n document.head.appendChild(link);\n if (isCss) {\n return new Promise((res, rej) => {\n link.addEventListener(\"load\", res);\n link.addEventListener(\n \"error\",\n () => rej(new Error(`Unable to preload CSS for ${dep}`))\n );\n });\n }\n })\n );\n }\n return promise.then(() => baseModule()).catch((err) => {\n const e = new Event(\"vite:preloadError\", { cancelable: true });\n e.payload = err;\n window.dispatchEvent(e);\n if (!e.defaultPrevented) {\n throw err;\n }\n });\n};\nexport {\n __vitePreload as _\n};\n", + "code": "var _a;\nconsole.log(\">>> running\", \"/src/routes/layout.tsx_layout_component_useStyles_MOLFIZOhXmE.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/layout.tsx_layout_component_useStyles_MOLFIZOhXmE.js\");\nconst s_MOLFIZOhXmE = `\n .layout {\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n }\n \n .header {\n background-color: #1f2937;\n color: white;\n padding: 1rem;\n }\n\n .container {\n max-width: 1200px;\n margin: 0 auto;\n padding: 0 1rem;\n }\n\n .nav-container {\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .nav {\n display: flex;\n gap: 1rem;\n }\n\n .nav a {\n color: white;\n text-decoration: none;\n }\n\n .nav a:hover {\n color: #d1d5db;\n }\n\n .toggle-label {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n }\n\n .toggle-text {\n font-size: 0.875rem;\n }\n\n .main {\n flex-grow: 1;\n }\n `;\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"hMvLkKpni0W2DXlQUzeGxVLgQtn/ZWVNuZ/ib2RSr4jyXqTTarE5k6p2oaI/Gpdh5l607OXwAa8wuROHj79aRQ==\");\nexport {\n s_MOLFIZOhXmE\n};\n", "map": "", - "preliminaryFileName": "build/preload-helper.js", + "preliminaryFileName": "build/layout.tsx_layout_component_useStyles_MOLFIZOhXmE.js", "sourcemapFileName": null }, - "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js": { - "exports": ["App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI"], - "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js", + "build/layout.tsx_layout_component_div_header_div_label_input_bind_checked_eevMxFvmCM8.js": { + "exports": ["s_eevMxFvmCM8"], + "facadeModuleId": "/src/routes/layout.tsx_layout_component_div_header_div_label_input_bind_checked_eevMxFvmCM8.js", "isDynamicEntry": true, "isEntry": true, "isImplicitEntry": false, "moduleIds": [ - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js" + "/src/routes/layout.tsx_layout_component_div_header_div_label_input_bind_checked_eevMxFvmCM8.js" ], - "name": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI", + "name": "layout.tsx_layout_component_div_header_div_label_input_bind_checked_eevMxFvmCM8", "type": "chunk", "dynamicImports": [], - "fileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js", + "fileName": "build/layout.tsx_layout_component_div_header_div_label_input_bind_checked_eevMxFvmCM8.js", "implicitlyLoadedBefore": [], "importedBindings": { "build/core.js": ["u"], - "build/app.js": ["b"], - "build/preload-helper.js": [] + "build/preloader.js": [] }, - "imports": ["build/core.js", "build/app.js", "build/preload-helper.js"], + "imports": ["build/core.js", "build/preloader.js"], "modules": { - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js": { - "code": "", - "originalLength": 281, + "/src/routes/layout.tsx_layout_component_div_header_div_label_input_bind_checked_eevMxFvmCM8.js": { + "code": "", + "originalLength": 216, "removedExports": [], - "renderedExports": ["App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI"], - "renderedLength": 182 + "renderedExports": ["s_eevMxFvmCM8"], + "renderedLength": 4172 } }, "referencedFiles": [], @@ -408,36 +2430,45 @@ "importedAssets": {}, "importedCss": {} }, - "code": "import { u as useLexicalScope } from \"./core.js\";\nimport { b as buildData } from \"./app.js\";\nimport \"./preload-helper.js\";\nconst App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI = () => {\n const [state] = useLexicalScope();\n return state.data = state.data.concat(buildData(1e3));\n};\nexport {\n App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI\n};\n", + "code": "var _a;\nimport { u as useLexicalScope } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/src/routes/layout.tsx_layout_component_div_header_div_label_input_bind_checked_eevMxFvmCM8.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/layout.tsx_layout_component_div_header_div_label_input_bind_checked_eevMxFvmCM8.js\");\nconst s_eevMxFvmCM8 = (_, elm) => {\n const [isSPA] = useLexicalScope();\n return isSPA.value = elm.type == \"number\" ? elm.valueAsNumber : elm.checked;\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"CF3/swXhRJEIiouslPvdjfVTqY6OjyJh0KHcBSDMzdMtdZxLX9gnEKx9QFzyEAtvgW6a/3HZSalGcnZvZMpl/g==TQ3Ek98xt7mLx/8u/EQsWg5yjVdl4r/2o37NHyWiYKkdGs2tlpKyRDEAKRy5vJ6EY47F/TdEuiTHK1LMA9g+mg==stoI/hbx4+1iBr0AYtnaHgum8/IjGph9RbEqm2QtPw40nMIBBI7OPZEJmD2pNGZHKyUnTxYLPOu+mU8Y3Obp9A==xmWcBXMWpCv82fL8UzhuglJWL5jQd+WGXGb9NVup7tQKEdRT9QckUXs80KIh3P9heqU75vxdAyHy00PJYDX/gQ==FyShB6khhLViYhcPhtXbb6Ao8+398G3HyYnnIkkuCnGZ32HrAlM+AD8RVMXf0+n23JAAUjO4j3jZycfeXHfYgg==ekzD2aIr56T/IBIxHmyeKqy7q2+90K7/RS+JqnYdZxTKa6dqDWL12dwqUUsnJkPT3brjsQW/WsWmZn8F7TMrrQ==tYyHuj2wxlZixYzLTVHOrKsoJ0wrdR4YZLxzagJxkc5gcH0YC6J5swPJk11UCAHmkZhyAMNsHuo63ZPl17a1wQ==/1bDgf8fpWQaBrx28fhK7AUGmXO6EDDwkZLvD/Om+p4PiDXzIj3KN50YvhzDyU3tCIJl7sf+frVuM32f/1+Rcg==i42TaJG09vxKQ2hwHK+gUjoyaj3VzYY1boUpDuJyxKiLJhNuvraNJaX9O7XymyRpk7RSzYkihWxcMSiyP7Rupw==wwvmQWnmebfm47abGfk30gzz2vxaYe7e+2+Psajn/l7tTtXp1tqgDVEjGWFGV+LgZyW3JLzOUnqqVPq+jlOcKw==uaycFwKv++pA3nTpfHa/tf6u2/thrOl95JlNaC6FzlFWANXMpb4aVHyWrEFTFkRuPmMvvXgzDea5HQ0tX3zXoQ==FEfUdlGjLzXBpr+KuAEJDrgGNUfKblF4k2/0f1mchbFEGVbx50HXT5MfalcnN8t1WHpPf2V187PRa7+DgKVIYQ==sT3HLVYtva1/+UI1WJQal891JYQA0WDkbiMWNMBeno8K9WjytAlgZbj2c0HBRkSR2iPOxgOKxM7pRu9vLjMjSg==yZR151EOaxQnTj7zfTGBQcN19cBgw8CX7N8qaD7qYk11L9mrG8DCN1Nvv+iR/wCvE7WWGOcnapeufecTm4ND+Q==zR5SCQHREkC4XYgkTI8PtIVKZoWexhszA/GK9SxDbehhEZ2ZGs/dcGg0PmYgbQVvmFUmIydbdMICq+lK6O5w8w==utAS5dAQJ8iV2utKZylfrJdAApnd00Ts8hI2BL3XJworss0UoV/ir7rkQDWH2Ze2IiulaotYrRSBReij/SnE7Q==J47pcZbQ8z9FP/CtGcvGPbZxlb5WemQZXHbHzOkN4TTbMoOM8fwsJwY7T267qL2BKhwZeWQM5SGfYd6ZyGdbWA==IQb+8ppmkVEPVct3yQWmRMf3Ja4IDjot3k7bppYnaMYRqNcNlupaTWz5Of1lnbEO/cvXsV47sh1VHy5dpC6QYQ==UOPs3GpfIDmVARJj2oRrVVNpqEhUIhSPTrbYYFkTupiH+wupl6z8RLl8E2tVoUwSJhozbwTS95WKoVg2W7w+oA==zfjTaiWSjkGYtqqs7VG80dhgcyFLOjumSCII1eP/DBgVqvwek8M/QR4n2trnByMIbnh4EZ6ESxKTVwhzq5NDQQ==NRbsLGD7JRmfBH02qqEaTjvrVGR5uAV3tib2VbYwNYinuzz79dtEi98GSr16fUSX1OElHYap2ezbwEBuVA7Y4g==k87oU8/PjOwj0NxrlxUtj4RMZARmFiAkESmBKeGQPaPBV22izWZv0CNb6yRO6a25lGhNQIlUXYQ9eQgXIV4aFw==ySjfIqAbeVCt4G9HQHukqIv0k7wrbCWXZeR3XpRzzbXstUuJ2fMpYqnNnodiFEhKaTjCNnFNt26yMBlwBOhUNg==LFQ7bdk7+HPV8llRrjjvvfP0ycWdJBvaLGnOzlWxlitqwLW3eHInLTi9kTCAIErB4v0oytfdCfMi0ZlBqJO8Ow==WJllvqfV7kkjskGC3O1iM0GOIq/4KNKuz0sU/UOIf62UTZhJXHSCdA3PtUWL34OpYpvCRbvzpc5H20Gth3VGTw==nUQ2hZNBqqVH95hSG2Ai4p9iuI67cRrm15sIguy4s1J2sqZOhb/r/Pz/dmrDiPXYWMRVgSApj3jrnUfNykzOUw==IDiJeYZ1w0xyhAjL9d821rEIgcV0CwyKw9b2NF/6dXpguV0JtCQnXAbTaGqiEpTjFNc1E1NQn5ExRk3VT9icMA==fH1b140h1qaTAUsHzK3N+o/qesu4KyTb5VWjExlKQiJWuT7p4HSDVI5EuuDFX4NlMNn2ZrYl3WlYGSzFxju11Q==xEyXrUkLZlFHtP+YLy9YFiT1IRyn6DTRnW9JkeOdAROdc+imolUSN7Yzf3c0j8BDccXCdBdCE2bnUL2Uqb388g==SzB6A/wm45+LuRdy5A6teOaqXWdFemNK2tJW8cnQYq9l+q+WuJQfK8fLPEZg2sEIaiJkDBmNSPkCL3vxPwuqVA==b4OEMuyMgPn+6iPCg1jbPRmE8OcQcO98NxKdeXoaHytP5LlhTKrhj6m336vB3ei5Jc6w3PFzWYnPObHy+U5zTg==7x+bJSqomcmEbRCnaIkthSstoV96FytDMrHcjFxeE+vWIGzUe1Ca7F7u17+oxxsGJEvXrRQBziXYlFuEzPROwQ==ZWO3828orRO0VNaozlejJ7Lkm9dGCtyqZmahO/YNSYVsC+sXZhdE+SqhvAKzli1NBxK2QikcTfG48hI2nOJSRA==A1AaIF+aX8ZW1S/ycLFyC5t8A3fVG8ajxGmF2n5lX3mKuAh2cJM4Qa6xM/SuIh/n8ZeqpXbNW1raEaROT/ffng==XIx+i4240LAUuMRaT1Uykgg880996k3N1OFToKPUIbZEuPIN0SiQjGi1lEqfeS9B40LxV7Xkgpima4LNeoB14Q==uAlmTAV4UIXu0DBIfL3Nvr5U5QLpkCdGeegLfqezf6fnKeN5lMPVR53bhJzT7qlQ4AZ1fLPGKDVN0KjYJiohVw==9tXz/feQb40uLj/9Rb1rL7ZizWHHnPyOZgZfutTln1qZX1OOhiVyKz48sX/z5Xil7tKcFpRdZkvU4nNVEIqrqQ==WiCKKZ3bkdz57YshXYw3eM8gwP2/hPs6uXWAWIIO/RBkBs/wHYSIbxl3eM4nAEguu2ZB0CDla81/A3ZlM/Q9Ig==kdDgx7dUZXrY5duGWTakvyJTVmaDdSa/8KQ0p9iBQwqutZeSUTrEsro53EKZgbumgHbrrV7X7OC/UTOIsiMH3A==xeF+pGforrXsfrpu7oG5Xp+hRncF2C852As/O+ZsDCx67gASRtw9ho8yIVqshhxjQvVHHsRdpWEFxzfwfaADDQ==C0y+6pmzNbNtvqPJvCFEWXxpRthMbCg/h7hkv0rqAAVhilrJT9tbf/zheu0qZ4eiJrhM+MBBjxo9HlBLuEUxvQ==\");\nexport {\n s_eevMxFvmCM8\n};\n", "map": "", - "preliminaryFileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js", + "preliminaryFileName": "build/layout.tsx_layout_component_div_header_div_label_input_bind_checked_eevMxFvmCM8.js", "sourcemapFileName": null }, - "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js": { - "exports": ["App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34"], - "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js", + "build/layout.tsx_layout_component_SHtFir1Ia94.js": { + "exports": ["s_SHtFir1Ia94"], + "facadeModuleId": "/src/routes/layout.tsx_layout_component_SHtFir1Ia94.js", "isDynamicEntry": true, "isEntry": true, "isImplicitEntry": false, - "moduleIds": [ - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js" - ], - "name": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34", + "moduleIds": ["/src/routes/layout.tsx_layout_component_SHtFir1Ia94.js"], + "name": "layout.tsx_layout_component_SHtFir1Ia94", "type": "chunk", - "dynamicImports": [], - "fileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js", + "dynamicImports": [ + "build/layout.tsx_layout_component_useStyles_MOLFIZOhXmE.js", + "build/layout.tsx_layout_component_div_header_div_label_input_bind_checked_eevMxFvmCM8.js" + ], + "fileName": "build/layout.tsx_layout_component_SHtFir1Ia94.js", "implicitlyLoadedBefore": [], "importedBindings": { - "build/core.js": ["u"] + "build/preload-helper.js": ["_"], + "build/qwik-city.js": ["M"], + "build/core.js": ["h", "k", "a", "_", "B", "q", "n"], + "build/preloader.js": [] }, - "imports": ["build/core.js"], + "imports": [ + "build/preload-helper.js", + "build/qwik-city.js", + "build/core.js", + "build/preloader.js" + ], "modules": { - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js": { - "code": "", - "originalLength": 272, + "/src/routes/layout.tsx_layout_component_SHtFir1Ia94.js": { + "code": "", + "originalLength": 2722, "removedExports": [], - "renderedExports": ["App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34"], - "renderedLength": 212 + "renderedExports": ["s_SHtFir1Ia94"], + "renderedLength": 2817 } }, "referencedFiles": [], @@ -445,36 +2476,42 @@ "importedAssets": {}, "importedCss": {} }, - "code": "import { u as useLexicalScope } from \"./core.js\";\nconst App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34 = () => {\n const [state] = useLexicalScope();\n for (let i = 0, d = state.data, len = d.length; i < len; i += 10) d[i].label += \" !!!\";\n};\nexport {\n App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34\n};\n", + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { M as Link } from \"./qwik-city.js\";\nimport { h as useStylesQrl, k as useSignal, a as _jsxQ, _ as _jsxC, B as _IMMUTABLE, q as qrl, n as Slot } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/src/routes/layout.tsx_layout_component_SHtFir1Ia94.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/layout.tsx_layout_component_SHtFir1Ia94.js\");\nconst s_SHtFir1Ia94 = () => {\n useStylesQrl(/* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./layout.tsx_layout_component_useStyles_MOLFIZOhXmE.js\"), true ? [] : void 0), \"s_MOLFIZOhXmE\"));\n const isSPA = useSignal(false);\n const LinkCmp = isSPA.value ? Link : \"a\";\n return /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"layout\"\n }, [\n /* @__PURE__ */ _jsxQ(\"header\", null, {\n class: \"header\"\n }, /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"container nav-container\"\n }, [\n /* @__PURE__ */ _jsxQ(\"nav\", null, {\n class: \"nav\"\n }, [\n /* @__PURE__ */ _jsxC(LinkCmp, {\n href: \"/\",\n children: \"Home\",\n [_IMMUTABLE]: {\n href: _IMMUTABLE\n }\n }, 3, \"rW_0\"),\n /* @__PURE__ */ _jsxC(LinkCmp, {\n href: \"/form\",\n children: \"Form\",\n [_IMMUTABLE]: {\n href: _IMMUTABLE\n }\n }, 3, \"rW_1\"),\n /* @__PURE__ */ _jsxC(LinkCmp, {\n href: \"/about\",\n children: \"About\",\n [_IMMUTABLE]: {\n href: _IMMUTABLE\n }\n }, 3, \"rW_2\")\n ], 1, null),\n /* @__PURE__ */ _jsxQ(\"label\", null, {\n class: \"toggle-label\"\n }, [\n /* @__PURE__ */ _jsxQ(\"input\", null, {\n type: \"checkbox\",\n \"checked\": isSPA,\n \"onInput$\": /* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./layout.tsx_layout_component_div_header_div_label_input_bind_checked_eevMxFvmCM8.js\"), true ? [] : void 0), \"s_eevMxFvmCM8\", [\n isSPA\n ])\n }, null, 3, null),\n /* @__PURE__ */ _jsxQ(\"span\", null, {\n class: \"toggle-text\"\n }, \"Use SPA links\", 3, null)\n ], 3, null)\n ], 1, null), 1, null),\n /* @__PURE__ */ _jsxQ(\"main\", null, {\n class: \"main container\"\n }, /* @__PURE__ */ _jsxC(Slot, null, 3, \"rW_3\"), 1, null),\n /* @__PURE__ */ _jsxC(Link, {\n href: \"/\",\n [_IMMUTABLE]: {\n href: _IMMUTABLE\n }\n }, 3, \"rW_4\")\n ], 1, \"rW_5\");\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"\");\nexport {\n s_SHtFir1Ia94\n};\n", "map": "", - "preliminaryFileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js", + "preliminaryFileName": "build/layout.tsx_layout_component_SHtFir1Ia94.js", "sourcemapFileName": null }, - "build/app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js": { - "exports": ["App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU"], - "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js", + "build/index.tsx_routes_component_vG0UuU4cNCg.js": { + "exports": ["s_vG0UuU4cNCg"], + "facadeModuleId": "/src/routes/index.tsx_routes_component_vG0UuU4cNCg.js", "isDynamicEntry": true, "isEntry": true, "isImplicitEntry": false, - "moduleIds": [ - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js" - ], - "name": "app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU", + "moduleIds": ["/src/routes/index.tsx_routes_component_vG0UuU4cNCg.js"], + "name": "index.tsx_routes_component_vG0UuU4cNCg", "type": "chunk", - "dynamicImports": [], - "fileName": "build/app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js", + "dynamicImports": [ + "build/index.tsx_routes_component_useStyles_Iyy38y0K3Hw.js", + "build/index.tsx_routes_component_useVisibleTask_HEFxKy9cwuk.js", + "build/index.tsx_routes_component_useTask_99K9SAWjPFQ.js", + "build/index.tsx_routes_component_handleClick_ep3t0fF0SDA.js", + "build/index.tsx_routes_component_div_button_onClick_BjxcCeNQ9ak.js" + ], + "fileName": "build/index.tsx_routes_component_vG0UuU4cNCg.js", "implicitlyLoadedBefore": [], "importedBindings": { - "build/core.js": ["u"] + "build/preload-helper.js": ["_"], + "build/core.js": ["h", "k", "D", "m", "a", "A", "q"], + "build/preloader.js": [] }, - "imports": ["build/core.js"], + "imports": ["build/preload-helper.js", "build/core.js", "build/preloader.js"], "modules": { - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js": { - "code": "", - "originalLength": 198, + "/src/routes/index.tsx_routes_component_vG0UuU4cNCg.js": { + "code": "", + "originalLength": 2341, "removedExports": [], - "renderedExports": ["App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU"], - "renderedLength": 138 + "renderedExports": ["s_vG0UuU4cNCg"], + "renderedLength": 66719 } }, "referencedFiles": [], @@ -482,38 +2519,75 @@ "importedAssets": {}, "importedCss": {} }, - "code": "import { u as useLexicalScope } from \"./core.js\";\nconst App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU = () => {\n const [label] = useLexicalScope();\n return alert(label);\n};\nexport {\n App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU\n};\n", + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { h as useStylesQrl, k as useSignal, D as useVisibleTaskQrl, m as useTaskQrl, a as _jsxQ, A as _fnSignal, q as qrl } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/src/routes/index.tsx_routes_component_vG0UuU4cNCg.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/index.tsx_routes_component_vG0UuU4cNCg.js\");\nconst s_vG0UuU4cNCg = () => {\n useStylesQrl(/* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.tsx_routes_component_useStyles_Iyy38y0K3Hw.js\"), true ? [] : void 0), \"s_Iyy38y0K3Hw\"));\n const count = useSignal(0);\n const message = useSignal(\"\");\n useVisibleTaskQrl(/* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.tsx_routes_component_useVisibleTask_HEFxKy9cwuk.js\"), true ? [] : void 0), \"s_HEFxKy9cwuk\", [\n count,\n message\n ]));\n useTaskQrl(/* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.tsx_routes_component_useTask_99K9SAWjPFQ.js\"), true ? [] : void 0), \"s_99K9SAWjPFQ\", [\n message\n ]));\n const handleClick$ = /* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.tsx_routes_component_handleClick_ep3t0fF0SDA.js\"), true ? [] : void 0), \"s_ep3t0fF0SDA\", [\n count,\n message\n ]);\n return /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"home-container\"\n }, [\n /* @__PURE__ */ _jsxQ(\"h1\", null, {\n class: \"title\"\n }, \"Welcome to Preloader Test\", 3, null),\n /* @__PURE__ */ _jsxQ(\"p\", null, {\n class: \"paragraph\"\n }, \"This is a test application to demonstrate preloading capabilities in Qwik.\", 3, null),\n /* @__PURE__ */ _jsxQ(\"p\", null, {\n class: \"paragraph\"\n }, \"Navigate to the Form page to try out the form functionality, or visit the About page to learn more.\", 3, null),\n /* @__PURE__ */ _jsxQ(\"p\", null, null, [\n \"Count: \",\n _fnSignal((p0) => p0.value, [\n count\n ])\n ], 3, null),\n /* @__PURE__ */ _jsxQ(\"p\", null, null, [\n \"Message: \",\n _fnSignal((p0) => p0.value, [\n message\n ])\n ], 3, null),\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n onClick$: handleClick$\n }, \"Increment\", 3, null),\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n onClick$: /* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.tsx_routes_component_div_button_onClick_BjxcCeNQ9ak.js\"), true ? [] : void 0), \"s_BjxcCeNQ9ak\", [\n count\n ])\n }, \"Decrement\", 3, null)\n ], 3, \"B7_0\");\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"RO3ohxhcpAk08MTYZo7NTpPbpUHgpvw20+rSpp2qbJfE+ipaEZlHQPjNnHKt++VH2z8e0E2ObHgn6lhd9vqWwg==u3yjKnng6i39pAg+04FgAN2m7WUdkOK9YB9SsqDULsh7PmEWiLbGmsc8rwOazFzE1XKMLoFnK6D+MNTULitIDQ==wDK44qqsj3pKScymOc76IPq0uoQIv4hNB+6315VQfW9MwdRHrvza6T5gOHAEb/jXttv/02k3JmgevZ2MdqKCKw==/Xdq0WH2QNakDa0xQM+Qth/DsaTr+45x5v/fuPK775iAMOSpit7vcP47QdOhwqZFVzHtZGGnXwkkjad/lv5GfA==I1jZAeJCFUyW9qxdAe4kDu/NKuXUEE7ANFTK45RL0nrKBSyYfjdW57Mg3Uxmda57pEQK13oP0PZBj7uHspERMQ==YqBEml3jCSsl8aB9zbDs9V+ZNIUigphV0tItnEDQJXuXVS3u/Mdb7uTny0ZaPytgaPdCRuz1mPrLoGj5C6leFA==J4AxSEQ0azvjQkdE65yD5Cg+ltdfuwRGB7O52uOK+SxJZJU3dNILOglsHD3D+G+G0QCgZ55lhCIBTj7YcgNh6A==a+1yHBndD7RngZ5qSCBUuDxLzTTDpsIRmAaZbf6RbUR4N7XOfk7iaFeoYBvqysdNLYFYbJO2oZWJV8UwOGh2Lg==NiyiPzc6DcMYQW+u+z3oZW2juIF4yRPcr85rb+P4bFmQb9D7hXcdRH0o2zHSjlsxvDOdRa7YA0StYa8lHR/X2Q==0Duk7N1MtQTkzuerKykuD2nqDteaGUXScSRuHzgxeWXURVE8GIOPvPWINRVImjg4eIWBtwPN/LjKVENXr52YWA==5yCKfzOwTYmnWvIiB9hqoQIzBjjRLlDfVvZz5vhYvnIy6nnWWDlOy/Zm9kT3Hdn0+ro2aaiV8nyqi3MvADavbg==+6jWbJ+D/h3EL1VqnZx3RF1ChlOb0m3PV1xYP+1LEbRncr2usPFzj8gcfH7/fb4I/MCdeh6pxi9bikItEwIp3A==YehTeh6uTyjQ8MGVqhP9H+Zpoy5qqypMpNjOdv6DiWbbVbhnEM4t5nWTtWbYF0QyT9fWNeLq/pJFNBhG9OExng==XA9bBlV6Bi21jz4O9SHQQwQHts5Emq9CzCvQWfBjDuyf2IwH3xb6h1dHEtBEIxKEnWO9A2P/CZtNCpk/YzU7ZA==EMhrIahtF90bSs+lfgKLBOGDilwCsrXcL7hxfuk9suEILpxCpABNrrp1kQqwaarE2NMyeWnEjXXnUpq4O63XRQ==rYh6PTjFGh1NSFwsnZway83hWSmsvqy7EcB3NJSJZZcjSv5y0/qn6WdAMNiv7AvZqmdIKRMDcglqMsd7TPtQfw==Yh+y3HcOd2Khotdv7P0x78Dy6h66PJdE/lPcrIVjPJvht3xuCi/eu3IQ81THIx/AZP5Yew5CTXc/4MI+67kkOw==8raZJpBSiTInkkzyTyfLJULNs92cvdzZKFTu7DlUBwCkRqpk7Z+S5TMIrOelYzM/qV0eMiyAapbWLCKAO6MQcw==r2YTlSGMSjNNSQcmpBKCpaSo1HGorOBdD2V5kHBBGfDF59j5foeyMjZswoJ2wjKAhO9re0aagy9y9aC20An/cw==cAoB7CSp83qq6dGFmIziizIaIN9MFW5wtE8m+zkMHFgpn61Jkliy5naTDvJW6FYsn0HX0GG7mpxzK9PyvQPeYg==5h6BarmmAlhVAqsyPr86r6d+V7rqvOwoH2rqegpoG1HbETVW7j5dJ8m8vla31pJuGcfuOyQAwZtcgIICh5xbBA==axWGcB6JOPWHVmBLfHWz7935RlmfpeGkV+921CD9yr83K7totWXLvpwLS/B8wsdG9YmOxJ7kP340Zgs9mIgH+A==h9OqHQOzZ8DXJ6T0FE6OfbX7vutJBOBBNSzqfWH3Fs7FGzcymYi1LCrpGCQQGVQ5fTZf9uLfE/4qC/frFx9I9Q==5fDLexZky/GGOXuWtezOPKBCZwLQiaMFgzkUcCFs7QLb4yn5wxc8N43qmctduSJenowvaz8IrQ0Qn+Vsonl+Sw==+kElAOVi+g4+c1l3SVWfG+WdI9drdrVdRZX4KDTfiW2RVxPWl6AzdRqfNs+ScpRe5sHcbXQMhUC4Uvc9SYqifw==pxM1N6OFha9rel+qBcSD1l6XYWD4ih1XbZQ+cFmqL7x5Tz1gxrjlifNtmVCUKnLEIHf8KwhvgW7onfng6AGPrQ==o37EGhIPteIy53aHd8yHJQCQuoYhMiX0RkMHc0yzJi", "map": "", - "preliminaryFileName": "build/app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js", + "preliminaryFileName": "build/index.tsx_routes_component_vG0UuU4cNCg.js", "sourcemapFileName": null }, - "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js": { - "exports": ["App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU"], - "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js", + "build/index.tsx_routes_component_useStyles_Iyy38y0K3Hw.js": { + "exports": ["s_Iyy38y0K3Hw"], + "facadeModuleId": "/src/routes/index.tsx_routes_component_useStyles_Iyy38y0K3Hw.js", "isDynamicEntry": true, "isEntry": true, "isImplicitEntry": false, - "moduleIds": [ - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js" - ], - "name": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU", + "moduleIds": ["/src/routes/index.tsx_routes_component_useStyles_Iyy38y0K3Hw.js"], + "name": "index.tsx_routes_component_useStyles_Iyy38y0K3Hw", + "type": "chunk", + "dynamicImports": [], + "fileName": "build/index.tsx_routes_component_useStyles_Iyy38y0K3Hw.js", + "implicitlyLoadedBefore": [], + "importedBindings": {}, + "imports": [], + "modules": { + "/src/routes/index.tsx_routes_component_useStyles_Iyy38y0K3Hw.js": { + "code": "", + "originalLength": 260, + "removedExports": [], + "renderedExports": ["s_Iyy38y0K3Hw"], + "renderedLength": 31405 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nconsole.log(\">>> running\", \"/src/routes/index.tsx_routes_component_useStyles_Iyy38y0K3Hw.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/index.tsx_routes_component_useStyles_Iyy38y0K3Hw.js\");\nconst s_Iyy38y0K3Hw = `\n .home-container {\n max-width: 42rem;\n margin: 0 auto;\n }\n\n .title {\n font-size: 1.875rem;\n font-weight: bold;\n margin-bottom: 1rem;\n }\n\n .paragraph {\n margin-bottom: 1rem;\n }\n `;\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"mlI6OBWpPPUMhv8ozuVm2y4+AsltrTPkKSiejU2UHeMuGr/B7wF2IPZKWdH8imGFZTij4G7HKQ9VC8EueYMdfQ==0OC/eCcmaAcA6LGVfARlGJ8uu7w77RGpsq+V+uGwYnOy0duGdPYqmtqxYEIljysbiwJkchCWHZtFxjh3cpVmDw==+naeT8gzg/C0UuSyMyUG/TkRqkMtXzvbYTifaX8ZnVb/+1HLTbLtjI4Ub0sgPcYXiH8XEZPGMFuPvgFNwdTGEA==y/yBc8/Qvx1bM96lap6imOyEICtMlIfqtlf3azAu6dOHF9cRiY69+1XGpk2nSmU6hfL7ETq1dD6kRuWE19l69A==RhajFSYjayWqyfSyHivqxtGaUzovfHv1lPB9w7kjl+UVnpXqt2SzP1RzlX3wlXGy8gRIWgDpauiy9Bro9tktxw==+jAFRNZFKQm+vJY+qeyGpk47WYJPZHdX53RJiHzAZdDx8IhmHqSbyOL+Z2cZa6EvWJUBw+D7kw00aLp1cAAgKQ==aYUC3UyLInykteQsE8tx3QEo4YzC5E9Y5hmyU5yFFpoHqLtYTkODWVR7QXjOPAqk/jgpa6qlbKOb2TatzvfX+g==w4fs9mA5iqbfL7YbbW6BUSqEr1Kucxn6Zs2YkD+eIEHL99FvNDlXSa5mneaqI2p+26HIFShHn1vgzdGQ2SllJg==JFeERBD3KsyZH0WIvgvJ+B/6uaT/uwTMBix9t/WOry6rWHTmhAN2fybbLLVJmeRzoJkwN7pfPU8cglAIuWZ6sA==/2UY+z0mSRy13LtCXxLUnOxd0EtdgiR0veGS9n4/bHKcLId61VF1JmOOMqHmF96kR0Jpi1bw9mCYHn6DL7xRpQ==a/6ghSZyqpHujpsdkPlOA2PyqUBq4Bg9SHlhE/dmjgLpYlb2SZWCMCWkESX2QlM6GT5yb/i49cs/yhZxpCEVCQ==WBTL+bQxdzpkjb0I6zgHXE6+IzZxJ55d5s1odVfOSsrHGmaB0X7hHSSdAjcPg7Qhd4ATJCkG12GiCaaH4OjInw==SAahXoLWCxIHUj7l+7UraylFHazyJ7kpl3Kf7FAR8nJ4AZUZf/AGCwT8f+Ws/byIUrcUlhap16+4DA7hlVEU9g==ntqeamW9ZVF3CrCmYSTqFlMkQTngF10DbQtVR0jKQR53WOV/zvdcbJDhmYlsFKvGVeh8RMZTIaxlLTvmbgYWpg==/E3DplYtY/FulIN4E+ngBzG1RbC2mUIsykrgT2lj/JpjZe1/u6OTiacaKdWoU3tSRMaxqv5kqEo8SJo65veNMQ==+Ye9wSDombbD6k6hLLq6ybf7q/liGfdgO48hMdJ+Hxxh/kwDLcZmnuYGOSHZDwvbkNvQWFkWVlgHgVw5PJL9LA==b0ffdRrIbgXj1+ljfe/kQVM9f3XVDMP4d2gWpr4sqz1sdzv1o+otTYB1QJtnkhH7ik1SA8Pino79Mf96ouoMmg==xd4XARH+i0oyUr5m3ysw9JsOiWqMyhUXG2BC5GgE3WdqOEznEFyE57NwJdjHCgJs0v07YRQA7E5+nCSPgtUGow==CgX4Is5U2MPK34JmNMjjtrwWBxSZwfc/BQguVaF5LgxgM5XNZ0rv9bdMStcUGFyaz8bHPKkWOAWGaAaMjkQcvg==JLO4CWa0mOChk5bB7GzYU5o1YOgqwOtQDdSf1eIk0QC+THpQB15x1gvRwkytRArRcwVkowfgcvPhI89T3rHbLw==qWTGAa4t7feQzJdqPhHkvQlAS6mvcMi1gac5lzRDGXLmU/lGkkMLFcxDc4Ek/VUCio9nK5wiYjq46sFYl2U66g==pKwPPIRkayTkK6QwRKx1gWQA6j/6+GsvKRxU36bQWy7iLOXvLLwx+YH+o2klukvkfKEwErdkHOXVEu0jNGAnYA==8RNFfDI5cceytuHXGTI4Ng0HGxagsAlnZZf/mfUVXuveRJZv+fuEmeIrR91j774Zbk94XbO8whU7Zuahr8BnjQ==IRHmo9iqHnADlOmPsBmMqb09OHpwRWjOSOFYssXy+aHif02d5w9zSkRljiG43632ZR4dy3PfNZ/aSX0BxT16dA==pfXHZkvMWujxtbqnV/JImaOP+5YFSHxTDwsMCwpaqtv+wyBENInNN5Jhd3OBOA7YyS0Ys9/d4sd0iXc41PhfkQ==24rGZNwzsEXwe1H2d8v0ZHLH2ZpGhy6jVUWylcasbO3nDoEQWw86DOVdsdmyhbpbQApAVTekFjPA2blin9TTxw==3K0g+a6WJtR8rbZ0Z3tDV8TMfN/QJECwiA6MmhkyOz59t6xBUpyWpYsPywwhI6MNrufpT8HBdfHENxZ8gXl+hw==lDbAFSRQt5PhadH9I0hZ4AHou4P0OhyH7CGhbY3LB3/fkAMU9vvZfjEKFFk7Fa6HwFkna1A4t6HANNTqyfHtXw==QHDM70zCWUbpz7fxLOtCG1yl+fIVCm4gbDXo/tJN/S7nbVjb4BNXibGc4PDNRZp4hNHp3dAqHvNR1Qe0KpFsPQ==89HbRtcOLs3DIdiAnVPKgLAAsdsTu6yehPx/ncx4eDva1BfcVDe3Al5xEdmLTH0MwpHIWddnJ/4KBW9wT/lFuw==hznLhB/0kY0bYr0FCv8Jb6feEPT/p6PmxyxUi2aon8tUz295BHgSlM4kMxC6y7j91MN5jD5unCXC7mWCWlnb6Q==R8qyeDZHVh9h3Lx2yyNuAt5y4zwv0UVzX4oVhs6HVe8zKIQN9Bc3731xatcf2Ez4iPvXEKtr5pxBO9U2wzw/5Q==hObvkIuwi5x52tb3mOQamcyhedaYWM9ulmMhWAOMMEwKiJd5aOoeKm8Lco8ZWyoXy4QpntwDXpUTECn1yNPjAg==u4fpAHIyZi7Igu3aKtL7GaCL9qFX9DaWKmqcOrUC2g15EnZNcAZ4sPPj/NdeXKtBIkullOH4wDHCyMLQ+KQWDQ==kdhAO2CIP8giBjCmFKIpE6I2kKBVBh7T3oe5Lvb7ib0auvYkz6xA8zIYiMFqCunI7bkUTEpNbn9hlKiR5cSdYg==JVn3DY/hCvSoSD4zco/33Rs8Y4jAtDz92yYDvT/86SK9XZAMTdYRJmYFXFWMA02j2fuehxw3eakj+3ZBaUAWeQ==260GfvGyCQlc0EWrG+HyWKqkCGkN4outLRuAHYjLjcmIF5dCPmfZGzmCWKa6KrTuiid6G4G1D2Xh5+SIXdLOdQ==yqGcTMoXIL6Bjy3n/VP6BXrmrS2pRjOoO2JfnT3U6lJwBxWzAk/ujdj0qsSu8RTrdh0lza6ow67fRKe4hFydwg==WcKgc1txRcZn1BKGQXxIJDWvpwulUGBfCK0BVH81siDUs0W5TXRoWbgQyUiQaYbnIPXVNWifWuE2G8a0586VJQ==b59fEpL9zy1G2ENBT1z6NJuTue6H+6OA11XKB6PvEQ+M/hx51qyHHhqFXI+ebdLSxclwpFQkjD+x2yyNkEHXwg==SdMVIMPOb5+nDHdXXWnQnmnoYHTobUYYtcVK59O81Ai4PksnxP6sB9sak5wviegZfngd5J6j2+5uvSJctwD/zQ==Jkrygam5V1cKHAIdZW220QW22WrlxUsS/TvmQC0BUd1i4bCbtTW+Yxp7e9tNcq5BK+2Z1YnxOARncPKg3S0IrQ==STqoh0bszMWWDTKuzit5opw/hSYugguqLAVb598WI4MvX+bd05rYlew9IAAE5xjPrTwrW3RfNiqsrUmi9nochw==XygMYJgJzfiBPs6mmF3ndRYMaQGF3HCPQWEQAh3rdbQ2mRCa+cCrs0oVjbsAWv5TIB9+v6nbT1EERZ1Wptz4hg==LZAq0eODxsE4T2STzEpvQzJDqSYCyetjPThuITGmAX2V0Ijx39enZSbGV58OHKE/4Qyb9yJHb1GD070gz1OEGg==cDpb2i0A+ErXbw2Fj+3gK5Y/r1cpo/3kaLq1wngUBrgw8eC+w0nWQG/vFIK4Z0AK3vauVK8W70At8kNLe7Xxbw==gk8zedOmwRUQoM68su34GuyljbnWD22nY2JFB+iR/0CEvflhsAEGD7Hxmw0is9Qf3R2BYFkxSJf6m6k9+SNJ8A==jwHXlvV54v8jBY72XGpwqYFlhs5Meo4vKDoTO8VgAvWp08oAnyJcOrf1PpCBDRRZ90SGAZtXgQB3eq5MiWODPQ==GIZHE8ScFHEvKXuJbFHGaVOIwpKxT+Yx0J34pKUOKKK2FSJJmYPNn5/NaSo/Js31rXA5MGguDwotZxTI4Hq0Ug==dk42G2XrKlWhzEPAKje3pL8myVp", + "map": "", + "preliminaryFileName": "build/index.tsx_routes_component_useStyles_Iyy38y0K3Hw.js", + "sourcemapFileName": null + }, + "build/index.tsx_routes_component_useVisibleTask_HEFxKy9cwuk.js": { + "exports": ["_hW", "s_HEFxKy9cwuk"], + "facadeModuleId": "/src/routes/index.tsx_routes_component_useVisibleTask_HEFxKy9cwuk.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": ["/src/routes/index.tsx_routes_component_useVisibleTask_HEFxKy9cwuk.js"], + "name": "index.tsx_routes_component_useVisibleTask_HEFxKy9cwuk", "type": "chunk", "dynamicImports": [], - "fileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js", + "fileName": "build/index.tsx_routes_component_useVisibleTask_HEFxKy9cwuk.js", "implicitlyLoadedBefore": [], "importedBindings": { - "build/core.js": ["u"], - "build/app.js": ["b"], + "build/core.js": ["u", "b"], + "build/index.js": ["_auto_getLibA", "_auto_getLibB"], + "build/preloader.js": [], "build/preload-helper.js": [] }, - "imports": ["build/core.js", "build/app.js", "build/preload-helper.js"], + "imports": [ + "build/core.js", + "build/index.js", + "build/preloader.js", + "build/preload-helper.js" + ], "modules": { - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js": { - "code": "", - "originalLength": 263, + "/src/routes/index.tsx_routes_component_useVisibleTask_HEFxKy9cwuk.js": { + "code": "", + "originalLength": 409, "removedExports": [], - "renderedExports": ["App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU"], - "renderedLength": 164 + "renderedExports": ["s_HEFxKy9cwuk"], + "renderedLength": 729 } }, "referencedFiles": [], @@ -521,38 +2595,35 @@ "importedAssets": {}, "importedCss": {} }, - "code": "import { u as useLexicalScope } from \"./core.js\";\nimport { b as buildData } from \"./app.js\";\nimport \"./preload-helper.js\";\nconst App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU = () => {\n const [state] = useLexicalScope();\n return state.data = buildData(1e4);\n};\nexport {\n App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU\n};\n", + "code": "var _a;\nimport { u as useLexicalScope } from \"./core.js\";\nimport { b } from \"./core.js\";\nimport { _auto_getLibA as getLibA, _auto_getLibB as getLibB } from \"./index.js\";\nimport \"./preloader.js\";\nimport \"./preload-helper.js\";\nconsole.log(\">>> running\", \"/src/routes/index.tsx_routes_component_useVisibleTask_HEFxKy9cwuk.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/index.tsx_routes_component_useVisibleTask_HEFxKy9cwuk.js\");\nconst s_HEFxKy9cwuk = async ({ track }) => {\n const [count, message] = useLexicalScope();\n const lib = track(count) & 1 ? getLibA() : getLibB();\n message.value = (await lib).getMessage();\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"IFKcWqmqjf3Gy4OtmnQp3bbPFrSEbE0IyhP5xstHl5dOIaTj6Vuj2dJmlRDjrsmiq5Db39OuWFHtHGgk4ffEYA==Xar5U+Rk2qkpSRZCc164uM4N3Fe0tnZj5aZ4Qm548Kll17eim89XoDIaM3RUnZ9vQPnx1J8ucmuixXzUSFvYDg==\");\nexport {\n b as _hW,\n s_HEFxKy9cwuk\n};\n", "map": "", - "preliminaryFileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js", + "preliminaryFileName": "build/index.tsx_routes_component_useVisibleTask_HEFxKy9cwuk.js", "sourcemapFileName": null }, - "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js": { - "exports": ["App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q"], - "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js", + "build/index.tsx_routes_component_useTask_99K9SAWjPFQ.js": { + "exports": ["_hW", "s_99K9SAWjPFQ"], + "facadeModuleId": "/src/routes/index.tsx_routes_component_useTask_99K9SAWjPFQ.js", "isDynamicEntry": true, "isEntry": true, "isImplicitEntry": false, - "moduleIds": [ - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js" - ], - "name": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q", + "moduleIds": ["/src/routes/index.tsx_routes_component_useTask_99K9SAWjPFQ.js"], + "name": "index.tsx_routes_component_useTask_99K9SAWjPFQ", "type": "chunk", "dynamicImports": [], - "fileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js", + "fileName": "build/index.tsx_routes_component_useTask_99K9SAWjPFQ.js", "implicitlyLoadedBefore": [], "importedBindings": { - "build/core.js": ["u"], - "build/app.js": ["b"], - "build/preload-helper.js": [] + "build/core.js": ["u", "b"], + "build/preloader.js": [] }, - "imports": ["build/core.js", "build/app.js", "build/preload-helper.js"], + "imports": ["build/core.js", "build/preloader.js"], "modules": { - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js": { - "code": "", - "originalLength": 260, + "/src/routes/index.tsx_routes_component_useTask_99K9SAWjPFQ.js": { + "code": "", + "originalLength": 211, "removedExports": [], - "renderedExports": ["App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q"], - "renderedLength": 161 + "renderedExports": ["s_99K9SAWjPFQ"], + "renderedLength": 805 } }, "referencedFiles": [], @@ -560,45 +2631,42 @@ "importedAssets": {}, "importedCss": {} }, - "code": "import { u as useLexicalScope } from \"./core.js\";\nimport { b as buildData } from \"./app.js\";\nimport \"./preload-helper.js\";\nconst App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q = () => {\n const [state] = useLexicalScope();\n return state.data = buildData(1e3);\n};\nexport {\n App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q\n};\n", + "code": "var _a;\nimport { u as useLexicalScope } from \"./core.js\";\nimport { b } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/src/routes/index.tsx_routes_component_useTask_99K9SAWjPFQ.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/index.tsx_routes_component_useTask_99K9SAWjPFQ.js\");\nconst s_99K9SAWjPFQ = async () => {\n const [message] = useLexicalScope();\n message.value = \"loading...\";\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"LakPU94z6vuQAnXVp2wmxeB6YCQ7KUxPaLAbuBa5e85HnlUmLoOFNKPi3RxbdbMs29ryIyNbdvN4cxwZjtn2Fw==lsGz6dvB5yUK4JWV5BKyy9xAew1rjyff89JMM0gGbyTfJ+pSGKbrGo7m/ZhtUUFYoDSa+i72hMHj05n/f3PfPQ==2NxFBFYoXcUCRo+dFFQU/JJBRe6tbEod8kCBcSwIl1HjnGqswD406KQCHGeUq+GcsWH6BJvHBqEukJSNXvDIWQ==/wf84imxMluLsSs7PLYcUnkemdcRyf7Zv16k84PDDf+0c14vnr+gc0LDjNWEbvLBCqY92QiXTnJwWtjHuwlQ1Q==\");\nexport {\n b as _hW,\n s_99K9SAWjPFQ\n};\n", "map": "", - "preliminaryFileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js", + "preliminaryFileName": "build/index.tsx_routes_component_useTask_99K9SAWjPFQ.js", "sourcemapFileName": null }, - "build/app.tsx_App_component_jn5XSz7NZ88.js": { - "exports": ["App_component_jn5XSz7NZ88"], - "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_jn5XSz7NZ88.js", + "build/index.tsx_routes_component_handleClick_ep3t0fF0SDA.js": { + "exports": ["_hW", "s_ep3t0fF0SDA"], + "facadeModuleId": "/src/routes/index.tsx_routes_component_handleClick_ep3t0fF0SDA.js", "isDynamicEntry": true, "isEntry": true, "isImplicitEntry": false, - "moduleIds": [ - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_jn5XSz7NZ88.js" - ], - "name": "app.tsx_App_component_jn5XSz7NZ88", + "moduleIds": ["/src/routes/index.tsx_routes_component_handleClick_ep3t0fF0SDA.js"], + "name": "index.tsx_routes_component_handleClick_ep3t0fF0SDA", "type": "chunk", - "dynamicImports": [ - "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js", - "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js", - "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js", - "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js", - "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js", - "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js", - "build/app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js" - ], - "fileName": "build/app.tsx_App_component_jn5XSz7NZ88.js", + "dynamicImports": [], + "fileName": "build/index.tsx_routes_component_handleClick_ep3t0fF0SDA.js", "implicitlyLoadedBefore": [], "importedBindings": { - "build/preload-helper.js": ["_"], - "build/core.js": ["b", "_", "q"] + "build/core.js": ["u", "b"], + "build/index.js": ["_auto_getLibA", "_auto_getLibB"], + "build/preloader.js": [], + "build/preload-helper.js": [] }, - "imports": ["build/preload-helper.js", "build/core.js"], + "imports": [ + "build/core.js", + "build/index.js", + "build/preloader.js", + "build/preload-helper.js" + ], "modules": { - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_jn5XSz7NZ88.js": { - "code": "", - "originalLength": 11168, + "/src/routes/index.tsx_routes_component_handleClick_ep3t0fF0SDA.js": { + "code": "", + "originalLength": 418, "removedExports": [], - "renderedExports": ["App_component_jn5XSz7NZ88"], - "renderedLength": 11377 + "renderedExports": ["s_ep3t0fF0SDA"], + "renderedLength": 12348 } }, "referencedFiles": [], @@ -606,36 +2674,35 @@ "importedAssets": {}, "importedCss": {} }, - "code": "import { _ as __vitePreload } from \"./preload-helper.js\";\nimport { b as useStore, _ as _jsxQ, q as qrlDEV } from \"./core.js\";\nconst App_component_jn5XSz7NZ88 = () => {\n const state = useStore({\n data: [],\n selected: null\n });\n return /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"container\"\n }, [\n /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"jumbotron\"\n }, /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"row\"\n }, [\n /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"col-md-6\"\n }, /* @__PURE__ */ _jsxQ(\"h1\", null, null, \"Qwik Keyed\", 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 88,\n columnNumber: 13\n }), 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 87,\n columnNumber: 11\n }),\n /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"col-md-6\"\n }, /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"row\"\n }, /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"col-sm-6 smallpad\"\n }, [\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n id: \"run\",\n class: \"btn btn-primary btn-block\",\n type: \"button\",\n onClick$: /* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q.js\"), true ? [] : void 0), \"App_component_div_div_div_div_div_div_button_onClick_wEyctjlC58Q\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 1859,\n hi: 1895,\n displayName: \"app.tsx_App_component_div_div_div_div_div_div_button_onClick\"\n }, [\n state\n ])\n }, \"Create 1,000 rows\", 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 93,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n id: \"runlots\",\n class: \"btn btn-primary btn-block\",\n type: \"button\",\n onClick$: /* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU.js\"), true ? [] : void 0), \"App_component_div_div_div_div_div_div_button_onClick_1_WC1qsF4RHoU\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 2144,\n hi: 2181,\n displayName: \"app.tsx_App_component_div_div_div_div_div_div_button_onClick_1\"\n }, [\n state\n ])\n }, \"Create 10,000 rows\", 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 101,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n id: \"add\",\n class: \"btn btn-primary btn-block\",\n type: \"button\",\n onClick$: /* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI.js\"), true ? [] : void 0), \"App_component_div_div_div_div_div_div_button_onClick_2_cibDwmrlmRI\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 2427,\n hi: 2502,\n displayName: \"app.tsx_App_component_div_div_div_div_div_div_button_onClick_2\"\n }, [\n state\n ])\n }, \"Append 1,000 rows\", 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 109,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n id: \"update\",\n class: \"btn btn-primary btn-block\",\n type: \"button\",\n onClick$: /* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34.js\"), true ? [] : void 0), \"App_component_div_div_div_div_div_div_button_onClick_3_dHjr9JWbW34\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 2769,\n hi: 3038,\n displayName: \"app.tsx_App_component_div_div_div_div_div_div_button_onClick_3\"\n }, [\n state\n ])\n }, \"Update every 10th row\", 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 119,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n id: \"clear\",\n class: \"btn btn-primary btn-block\",\n type: \"button\",\n onClick$: /* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js\"), true ? [] : void 0), \"App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 3289,\n hi: 3312,\n displayName: \"app.tsx_App_component_div_div_div_div_div_div_button_onClick_4\"\n }, [\n state\n ])\n }, \"Clear\", 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 135,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n id: \"swaprows\",\n class: \"btn btn-primary btn-block\",\n type: \"button\",\n onClick$: /* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js\"), true ? [] : void 0), \"App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 3550,\n hi: 3842,\n displayName: \"app.tsx_App_component_div_div_div_div_div_div_button_onClick_5\"\n }, [\n state\n ])\n }, \"Swap Rows\", 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 143,\n columnNumber: 17\n })\n ], 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 92,\n columnNumber: 15\n }), 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 91,\n columnNumber: 13\n }), 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 90,\n columnNumber: 11\n })\n ], 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 86,\n columnNumber: 9\n }), 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 85,\n columnNumber: 7\n }),\n /* @__PURE__ */ _jsxQ(\"table\", null, {\n class: \"table table-hover table-striped test-data\"\n }, /* @__PURE__ */ _jsxQ(\"tbody\", null, null, state.data.map(({ id, label }) => {\n return /* @__PURE__ */ _jsxQ(\"tr\", {\n class: id === state.selected ? \"danger\" : \"\"\n }, null, [\n /* @__PURE__ */ _jsxQ(\"td\", null, {\n class: \"col-md-1\"\n }, id, 1, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 169,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"td\", null, {\n class: \"col-md-4\"\n }, /* @__PURE__ */ _jsxQ(\"a\", {\n onClick$: /* @__PURE__ */ qrlDEV(() => __vitePreload(() => import(\"./app.tsx_App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU.js\"), true ? [] : void 0), \"App_component_div_table_tbody_tr_td_a_onClick_DDeCLEw4BYU\", {\n file: \"/starters/apps/perf.prod/src/components/app/app.tsx\",\n lo: 4339,\n hi: 4357,\n displayName: \"app.tsx_App_component_div_table_tbody_tr_td_a_onClick\"\n }, [\n label\n ])\n }, null, label, 0, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 171,\n columnNumber: 19\n }), 1, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 170,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"td\", null, {\n class: \"col-md-1\"\n }, /* @__PURE__ */ _jsxQ(\"a\", null, null, /* @__PURE__ */ _jsxQ(\"span\", null, {\n class: \"glyphicon glyphicon-remove\",\n ariaHidden: \"true\"\n }, null, 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 183,\n columnNumber: 21\n }), 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 174,\n columnNumber: 19\n }), 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 173,\n columnNumber: 17\n }),\n /* @__PURE__ */ _jsxQ(\"td\", null, {\n class: \"col-md-6\"\n }, null, 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 189,\n columnNumber: 17\n })\n ], 1, id, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 168,\n columnNumber: 15\n });\n }), 1, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 165,\n columnNumber: 9\n }), 1, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 164,\n columnNumber: 7\n }),\n /* @__PURE__ */ _jsxQ(\"span\", null, {\n class: \"preloadicon glyphicon glyphicon-remove\",\n ariaHidden: \"true\"\n }, null, 3, null, {\n fileName: \"components/app/app.tsx\",\n lineNumber: 195,\n columnNumber: 7\n })\n ], 1, \"bK_0\");\n};\nexport {\n App_component_jn5XSz7NZ88\n};\n", + "code": "var _a;\nimport { u as useLexicalScope } from \"./core.js\";\nimport { b } from \"./core.js\";\nimport { _auto_getLibA as getLibA, _auto_getLibB as getLibB } from \"./index.js\";\nimport \"./preloader.js\";\nimport \"./preload-helper.js\";\nconsole.log(\">>> running\", \"/src/routes/index.tsx_routes_component_handleClick_ep3t0fF0SDA.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/index.tsx_routes_component_handleClick_ep3t0fF0SDA.js\");\nconst s_ep3t0fF0SDA = async () => {\n const [count, message] = useLexicalScope();\n count.value++;\n const lib = await (count.value & 1 ? getLibA() : getLibB());\n message.value = lib.getMessage();\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"dClPgcB77wBjI9zRh/l9fYHb/YfmpEYIYAHh/HlR2338qqnsM2ajN9i6ks+GjWNCD9qVEbFQfV/oUOFh8LlI5A==4f94jtG0zZ1WwvUce2C2fXarDcaJqYmx/PSlfT+IFq6iy9/RNQ/5oIrdVikmZiAUZfj6h1ai4m7qxYmie2Lhvg==JaNuihiP8Yl8Nv9x7XL4IhUIwHdKa2xezV2HXLk20q40//AbLNeKYhoCu7Fm21TS1VZT+vPzNbgublcg4ZVt7w==+kvxQ1nPDSdFC/EN238xi9k6SCCUoa7FSqL4SnH6lDr1eCjkwH/06bOoxL+e2gOf1hYel3hPAbpSRzvkH7WVKA==tz2XZ/OKSNobH+wXsYLpIewwVAj04P2hThrV0QLs/eJplTALw4gA7IsHRUegV8vq44o3urpUgIxz9YQywaLDJQ==bl4EaLNvMGUnjf5D1VYwdnMhMbaM1SSawWWr+RslXapj7fzLiwwUF6bpeMyNlNiE+xHSJ5JULfjd1N2puOv6wA==QjkRZWV4hynLMb5wPtzN5co9egqCEu3JCh3fPezz3JQhwuBsSI2L4hKistDfWvcU5Ktob2MHyy58cCXijarbrg==j9XfoF9IeuNCI5Iw3l01cm/KLOh82aLE/W/cqteU0Fe0E7a0GHwgXor5wLxx31l7bqrcEPNJqKkfrIlhEQ7FTQ==1K8CGX0Q3c5ZcWg+E6sznYXu6ayjgyJ8370zqI5qayZGx1Yehn02jTz6jEZG+SjW9olrYEP+vvDCODJYwSRF/Q==cRo56hjtVYhGdYFaEKno+W0vb2cKP8fdS+LA4vLem2cN6CoJsjMfALbQfSxHy5aIW6r+TM/hRlypazh5+Zs3nA==QRe61jDaR4sDRjKXCsmaHCNa2oNdBcz9GLk5HrKGYoKMSVUfh2MdvwEE/1lq7Z1SFWefTLI5deadfLLM77mdeA==p395ApwKs0wQ+Ch1A9A1jZId3jBNOSoFROFBwVfYcC5fv5EG5DPZZiaFm0UwMFHAXpCT/S9pxrsxk/U6yx7kug==Rq8UGRObPvTEAkfkrqt6XojLCDfEWvaSO04hCRArF5eN9lpUzyFhwfBiqLZZtoJc50V6QqqrGzHcPKJ6OXRpDQ==zkhQdJBxwDas9Xxn3UgEnNsD24smSZGGzsJSOAwpbLgjTlcxgz74Qx3rg51xnM0dG9dVkN1Xtiviru+HQTfk8g==pmCeZB4hf9sfa3O6cOpFO8bwEB3CVqSa/ivEKyLumU5cKqx20P1odcnm6IeccnGo489JJ5Xfu+NSc2dSOG3a/g==FZ9QUFZQnut4Qn/hgg3lI76f0zaiQfnWrX4RAXYvWLtZifl6QnIVOTOiD58kVq8teP2XmeNfpB9UKkXRIAwsQw==HQoaw7dSWGpxO9f7oAl30qaOVklkBO9MD6ibBlI40Pn78VNy/cOUC2Yerqkw24Ku/JBIQhjB6cid3lqCvYhoNw==tbJYKdTmA5vWa2n6PTlnhPurczZf2FJCzOS4M5CrWaexkQj+tvjHJ8Lo+dwcssmPY4wCxcdFGPl5LnhGouLlTQ==F92GVKGi35kT6p83vjJctT2ZajxRSVJlMaENezCI9p43HUo/3QrD4B6Tqq+czHf4iLQxb6nNzl9UZ+MkT/zUYQ==3U08CZ2crD8ees1fx1W0XhO+MMwrcCKgMlf3y9WQsAAtFJxhvJ8pjykDb/sNAcgX7ruikTIsbfQSotNlz9JRog==SZXMH0s8R+UHEADuxvFi1+oMn6s7TKZmE5/BJ0hrb0xEusKAAu0k92IKj3Tyk472nq8GX8ZdowRmlsTkvtcB7g==56PyxWxfBaMbyFUb22VO4Nsw4rKH61zsyMGR3MHPbfQhrjrxwxShnPHgFBglZjhGfz8fKhyQlagZLczFoh6N2w==Zh+UOoU3cd5BKaEKzBMyqbf4kpolVoN11y3uXuUqif79OSUAit0JXvz28iPHHCyt20/Q5MfFqJR1EYYafq6CxA==yrzfNZ710pAIdi66FEU2D8jKS6phQrYuua/wdHOsFQqG4cE7gjgjoWD5KCzBnZnz1vhlhuj9uVRAlKoCVQInfA==Bktd8g/3cE5R+ciqmJVdbft34ZoHSJUFSP4LTTgDIYD+b5aVkJLkwI7JEPcQLFI0T0ilXhFPEDVsnOgcZst9KA==0VjHASWlYmLpjHvZqVkufMJGFQ0mKCAcOn3467P6YLzwK3j9ksqZ4E4RS1xe/NAEVPEtiU3Dy5VlMwMeB+IuHQ==Ks8OILqr93r+QeZOZeg2Pf6XuxDQCYcedrTXFrM788jnIp8YgRwla48xI7BmLIWUrAvSgWDAXR16yCwqZhemUw==qEBnG45CSZHeciG5cCU1FwoQ9CIbbOf6ycMEp9UbdWmabOrID3KbAfNdKXPa5atGvPOpSHyX4gppAyYOxxX7RQ==ukOXyM16xntm4mZIfpfcCsiheWs0NB9MSoJf62NWDNYOisH33rHbLUkOo9oLdkFaTuoZm6iPFjMfVAbd9UvcmA==OTZyNn69Jyj/F/20CNLCZDuGSbzou+z4JnAU540gLhWsMSIAy2pqwJQuhIE67RcqQsCCGPmN0S1YzKcNHSZJYQ==WApq+rR38mVJignQ7VtHwi8M7ORWY6yJkuw7BIEy+WLStTzy5no3ofwvmubcQhgpcCAmaGxnlU9FTVBj3O/1qQ==i2nTTy1HizQvrG81zEuN5mS2sPlnfim1FbSTaHBnBFVIGmbL058teAcgvwjsNlB9Iuh90rz0Xg9YPfdsFigUuQ==fUlEIxf2n51sWd5Rdk/ycZefPS3abm2vSYikmPgU4x3RS84HuKr3fB6c7fKkDZSsLiWLgPWTOfLo6IimdViOww==Fykh9/JQ2pjziOGOBYZdOmZhPc/UfI7Wm05lprLS9Krx+KL45cqVKGwOPRFs+Ruyy7NgYcWNWQYMeO7sx367ew==wW29oe1NYzE7cQYHdg/nBBb7vopTVthCCiMHGBYBEqaQYF5eEm9yZDiUtvYps0b1/El9sYVXfk2JB+g4SDLoTw==398cnC0bNUKCTArSoJGHTYWyfPxau7cy2ishg/HJQOsFb5FPzdk5Io+fcXhKFoBnKW92v9GNuPqzLXwpUgN3OA==j8ilbGaa2vfGylYb7eGkeUhdQSiC0C9jSLZ15QsnIHcxtxG00nL8HkHtdsvkZkEYDlDKynBVK8ywU9nT76w+lw==WDlNWTkeShOd2m/R++eZj8BS59HmeDX8nfE2apS1p2fcd81JkSS7JDfY/1rqfJsl7S2hHbhcDk09iHDuN/fdag==7twqDbY0gurjXg+L111Gh9D8N0pI9cTjxVaUqrqWF0MWKrILh4XmtemgKir4m1iD8nVclr1NmrshsJQ/PIlQgQ==wo2meVrwfos5l+SrrTa1Wjw0TtEcH+GJNrcGefxwEK7fwKgTAMK6ajGfmX7pyQKHF8wqHWKilmOwGnSvHvK0OA==5zja+jw42+u8Kknc2+12I2yFlNeFcS1zgIbeT1KeuSM8K+kSob/ttSV2f0c7ya6oWLW1XNh+MlK/bTTukOqIwg==Q3b2wUwYpBu62xGVGR4j97nCu1wo9K3psybX8SqgSWna79FiLAboyG1xCRbySbeeqqFM4H3r9oA+O1bFxGx9kg==sQvy4fk0cl1BQxFjEX/ea+ZxyxmPnw//pkWHJ7RNAD2FrFYTAhC+N2yRInZZsBzb53DZ1RMj+OHcYiFKu6z/sg==G3z2bZfk9tNRxbq5V0WUZXzIbDEKrp03ExEGf92P3jIJmX85zmrastlh7hcVGZUaw/QofPqV4tqtWC3AArDDZw==YCAxlII1bQPq0y0tExX8z0jJ31SBiTHBlmuKwkTxKlByu42ccVi0Zf52RphNbq3eHpoq8OBTiUSuaQSskyMMDg==IIKes1LibMh/Oa/HlvjavzFpas+GTWKSmGajCIY50ewuDS1b9uzK622ziQikxSbaVflVxPhAgJGSyPuSsH888Q==VF3t2qGvyeLuG45RsYvbO0MgkH5z+O6ajLdK/m4dNHTaXPj0v/2Mg666LiaWo9LWgdbhj9EI7c8Vx075dn0WRw==VRqtZ9FmDdjdEZS3592p/xyId/FC7vhldV", "map": "", - "preliminaryFileName": "build/app.tsx_App_component_jn5XSz7NZ88.js", + "preliminaryFileName": "build/index.tsx_routes_component_handleClick_ep3t0fF0SDA.js", "sourcemapFileName": null }, - "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js": { - "exports": ["App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA"], - "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js", + "build/index.tsx_routes_component_div_button_onClick_BjxcCeNQ9ak.js": { + "exports": ["s_BjxcCeNQ9ak"], + "facadeModuleId": "/src/routes/index.tsx_routes_component_div_button_onClick_BjxcCeNQ9ak.js", "isDynamicEntry": true, "isEntry": true, "isImplicitEntry": false, - "moduleIds": [ - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js" - ], - "name": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA", + "moduleIds": ["/src/routes/index.tsx_routes_component_div_button_onClick_BjxcCeNQ9ak.js"], + "name": "index.tsx_routes_component_div_button_onClick_BjxcCeNQ9ak", "type": "chunk", "dynamicImports": [], - "fileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js", + "fileName": "build/index.tsx_routes_component_div_button_onClick_BjxcCeNQ9ak.js", "implicitlyLoadedBefore": [], "importedBindings": { - "build/core.js": ["u"] + "build/core.js": ["u"], + "build/preloader.js": [] }, - "imports": ["build/core.js"], + "imports": ["build/core.js", "build/preloader.js"], "modules": { - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js": { - "code": "", - "originalLength": 210, + "/src/routes/index.tsx_routes_component_div_button_onClick_BjxcCeNQ9ak.js": { + "code": "", + "originalLength": 155, "removedExports": [], - "renderedExports": ["App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA"], - "renderedLength": 150 + "renderedExports": ["s_BjxcCeNQ9ak"], + "renderedLength": 5387 } }, "referencedFiles": [], @@ -643,36 +2710,77 @@ "importedAssets": {}, "importedCss": {} }, - "code": "import { u as useLexicalScope } from \"./core.js\";\nconst App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA = () => {\n const [state] = useLexicalScope();\n return state.data = [];\n};\nexport {\n App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA\n};\n", + "code": "var _a;\nimport { u as useLexicalScope } from \"./core.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/src/routes/index.tsx_routes_component_div_button_onClick_BjxcCeNQ9ak.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/index.tsx_routes_component_div_button_onClick_BjxcCeNQ9ak.js\");\nconst s_BjxcCeNQ9ak = () => {\n const [count] = useLexicalScope();\n return count.value--;\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"8zjWCE+i3ue9ct+YfjDt/82LMneRNOvB2MoNm2FFjRq8GbOFvM4sSqWXWKE8tO7yZgRRGZ/bKGifb9/q9ZIxcA==mSCqohJHnhSqVXJmS4OtehpjJE/03kgOEyyMKWORdV5KCNIRJZClFhtJRc8qp1aSua29dbldIbv6yNgHUvX9gw==nx7VjwuFmJvH4WhvjJtfkJWu3aUPxEMP9iRyAgodfRTJ2WhYEnTj4hM5rZOngsOWZygmPt+V3MN2JxXexMjcsA==NpjHEE53gu7KFc8KGNW0CQMwIZ60NBkENWCGGX6O5f2fz9T9HTcpNtgI1dWT0YDfanKXw7l9Rmz1dZvXhQMPgA==3Vhnet5kcDu3icHsoAWoEtJNnWgd2/o11s0L7Vthw+jw4Rbge9NNsKYm8qAgSF6gbrCOm8P++8pJYZD2SELucA==/yx2gJ/wmK6bKaT/Sa2AhXEIoEYbeBMxtsevZjse9al7x/qU8OmKum2QBXVwNWYWLe4kw/b92ppJ6ghHmDAixA==HUWiNEGIqSMMpNPuYDeeQ22T1sIEyesLoeD0Hzyhn/Hr1wujogqvXUvVg40tnzgMqXpg/LpD+uf3G/Dyau6ROQ==85OUussTgsVvvAce/ItHLuOdRDpfVrEmlnuebnKFem2MnOOpsN1mp9uM/Sr58IkZEERyElAPKyM7RU4RzHrY2g==fDHE4WdatvjAn4l/qacdy23pgKZ7UBhVPmBzte6OSK2CvDEpyYioj/c+aXxXgnPDs07ZsezRldtS2eX8kFH6Gw==Shf/0011Rc5jZ6oGlQFpc6IH0oZUAWVkf+J1JcGH5ptSUkxf0RgXMqeU76Sj9cLW4wHMBPE0XE0YtYu4puPB0g==cltTQx3gudG6ngndOcIq3Km/dDcmR6x+xmR1Nv6i5sBl2jw8Hf97QmBNjS5R6yYVWu/RN1YxBxSz4PxyUNdzqg==boHbtcvnSvzUxlCVsYbe+6OfeNmqC1uYqHtA1EFMFLWnFtgIfSzF7Vvk22i0OTl48NH3VE4VtUfvweNkMZQYhQ==9bIUhYzImOrE5EB+nidff8w6/5kCYwt3DY4E0G8lQ2tcGOBwIwBZrLFz+KWGi3JDDCAoUyUBo/x7fVDa8vazTg==Eehfuv2Eh62pijOsbwojWduDe9kBEgeidBFQbWnTC9lzpC8aKVUDaCC3n50S+nEft5v6FI6NnqeUD+Mb/UU+Hw==t/HGtey7G+LRzOgu2ItAPXKVrdnLSLXCJ3ak8XfaFRqyLs74FpRM8KzGbJzFPlRK2vLW7T7DrtPtJ+YQjn5jMA==JCtZpolJGdLCGg0dOc9tgtWMoenGRvBlf9BIwohrTxPQWS77ymHJ6lxuassYZfG3rcHyAR1LsbkTqUjP1Fo4fg==FRfKqKKc6aAoDMxqzBH2Knub2czfeIyaoQQ6Cxy6bnZ28hJp/eSTRrZz0ZBKar+XDyM3cTUGul8cN4n0rXkKUQ==s6JxSJEdWKNqXtLTxXJBMpF64itg/Bkl2UMfTzuYsWuqFW6fJHqn0RkDm7Nu474sqC5o8Ic8W3hhWROnXhCwRg==PNXZB7x0zaossFuHs4t9dM2QPdGDUgBW85zAAfqxt8AWkfwv2PPYJBzVtbx7gdLBlieIcxosdtxxmxTFJFBFpQ==LmfyXIua9pv1n5ni5Two54AgXhfFAZCFNM5xG7dlcktwBuGYajDocXYKMN9+/w/QVNq8PT6tZtVQE1CJU6Kyrg==j3D2D1OKEE5baffP+X2yCOygVQd7pggw3n46dxaQQOYTDTGmzThQZvqXG+O5lmpVokVJNHBCnzWif2OwSUK94w==3VSapPyuFUwCfxkW69DCyyhPtYIrXfs58l39yezMNcV68JRfJMWGhk7qS61CYRBSf0xnmHcnC5rAEU+TaRbW7w==/1tkKHyVd/SAk3bnfFFWLiKmojAB8vqVgti1GUx7/Ne53tAgx5x7msBlOVxP+x8JEaXK0+ir0atVdf+26VpjaA==Of/hVIcDM8KoLC35I8JPFpxUgqg10Uj6TP7ASBHK+XkUpjbz4ly/iJppIjvZDMLG/B76KqstMUcvEdAH/DOPkg==j+48uoe5nPChGxga2xvc26Er3X8ANElynr8euicPeVZi1okxSyRfNmddNQSzldt7t8H/bBgbzJAtKmOhGqE17g==8gWSf4+8t4K1XIiF8jXmtcTS/ZMbSjPowJM3FNAUqDf5CN0Y9/5nB/iZO06rGcHjUuwugRtIFVkAMALwKKkscw==0BYSVYmPl9cqQSim3HjX0Kldeo6pJ9NKBKI78bBaWk5m1MDehgL1EhaSXkBF74p8BfBVwVqMrcsxuhRz8xN3Xw==GlOl9ogihelOTMQdyeLC8piT5kCrftt91l8yaMlNYhK8QyY8bKUwCB4stQJoQyJVC32W0juDpqOqwsViKO98Uw==u3KE1UiB3gyVACbuT7pwICwrZqAMDr4WFwtawi2jKwrjt3PBRUgPB83MOBHRFLYH/GZr3o+EIQYcDtk1iDIlAg==ONp/pxKnCiINTe+QuIFgS2GM4j+E11qbaTjuJDU/DZfrBD6L/7Slmgc+yuXXMltY77kD8uA/DzByws8JBWd0KQ==agDsE1sNULYw3UhC+imNVQ0JdrAjIBU7bfTjdyi0z2V2W4gZKolCD5dJrQw3Jq1c43NHQquD7N/kxa4iyUy+Cw==qveIHAQ1cyc8N+wsGcJxBvJ4o9+St7xXCwcaVAlXMsm90aKcgKgN83lBT7ZhDjg0dWlrGCLpb+Zp86Y/lwxExQ==IX874TIYOCuccmvIp5BqXtqQ8mM/tQTbMDfeRvtkmJuBB4b8RkrJL0RVofZPbRQ6ExU6gPONTJI4xwUE2xiPQg==VLQbBlnRTveH5I26CHS3brVWx5fCYtNTbbnK1HJ1pxDNS+q2ZqhZHbgY96rrsa7R8oAajmenJISG1Vds1AB4gQ==VN2hNVErCjvDcUWldX0xPADHNXjVPdtnZKT1EYfaL4X23ewnPK5BvJm2eM3ZjW9cFj+ZP+q/p0s1afAEzvxkIg==cYNAsWf9zOfTZZ/xEKosNn/Ta1XnOFfKmQ1u/4pcDmyFl7y6w3+DPEqXLzBIp2+HnGp4y8LCvElwj2rXiPXDDw==bwZ6Jw6k6E89u57uo+lqa9YLhkkWdt8/9p9VJSlx7xj+FA1LaaxLR0jzW+9HKg1c8oa9v0s737wWDYTheg6Wfw==jLRf7uyNhWWi5P2D5CXXI72n6S+pt2CWQj+phSX/ClYqs8AW10C5cPNzNVTJTxb0/q/L+IfuD6qHscKYGGvv+A==qtX0/RpG9mrVmSqntrTiVnm0H7VsllcMEz/9m82NSDu+aw9EQLXz8bhsLDhcrowcsukrj6vkgN1LUWbXIYQc/Q==cdMJNRXtaEb8I3oRNHvPlR9IpKaeZnWW3/IAmqU6svi2WLrz2+eQWO2HKIY4HAfvtrQpscoj1C7DVc2l1NEy0g==2Etr3/Ad61SSWvAu6R5iDcBa/CDfFNdlAJ7IwzHKvddABSmcYb68CRMtLP+WMoPpXXpnCy/dJHrF2UQZqQLUbg==Nh+eFV2chH3D/GDqWI7JF2NO3XCUeEu3zCr7x/IxV1glZPepfBp4U5SDEaNJPRKxxz5xkpKcBG3gb/TmnCpoTQ==4tvLA6rj3/hIfsr17j7pJmgvx7IUrhOktJ64aPhIzkWscwnNIQs6CqIP114xGs0f7/EpFjK7FRBIUV0yyekcEA==jNk2UbKSyYVNwEque9gm5OGQZ4h6sPmUCEm/CsZWKt0rTdmNi4sfUIdaXRnlAA6ylTCYfXBRrv6aESc+K23mZQ==32t760LEwzjfoszEprnwQOuU/Vvyk1vyxda3v36cIxDc/9pgMzxIp8YMGNeRDIr8Sv11Un9srWeRvKXd/wmAFg==Ec/mRXqb56ADy5IRRx8nngses7dBvlPzKJ2vq62pYhGYBOyL9UyKDZrQ5D8Xa+lLPlHZKm6UE6XTnh/KHzbjbA==ivJH2I9watiV+Whpj7iShdbUn0l4xvrRip5v1vAWhqELx6pAV++xeL5baJoA37X2yQKb/DNzHZxd8XPADkT/eQ==oxoig/hpip47dza/BlqQUAhT6tjsr5sO/DB5jY7GMgKCY9IYuCyD1Qp9FAFkCqTQQerKXnW2h4HVnv23HFxSMA==V8FGw0UniHYDgndbqSnxlGaENsHPLpzCDEQyA4LSqPWAkABZK9ZUnm6i2l4EH5h1t4DhD0wpZnJxB/V2AtVtMg==Py0jH+Hnu9QVmya/Bm/ZgwE3SIpzgPSVdpkfEjl66GxK/fnNTcNX7pVlAm8Xsqm2suUQaz4XuMHQRAk5fjhwvg==YFUFy", "map": "", - "preliminaryFileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_4_5fYbrS6ABNA.js", + "preliminaryFileName": "build/index.tsx_routes_component_div_button_onClick_BjxcCeNQ9ak.js", "sourcemapFileName": null }, - "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js": { - "exports": ["App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI"], - "facadeModuleId": "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js", + "build/index.tsx_form_component_useStyles_0pasaG6nmEA.js": { + "exports": ["s_0pasaG6nmEA"], + "facadeModuleId": "/src/routes/form/index.tsx_form_component_useStyles_0pasaG6nmEA.js", "isDynamicEntry": true, "isEntry": true, "isImplicitEntry": false, - "moduleIds": [ - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js" - ], - "name": "app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI", + "moduleIds": ["/src/routes/form/index.tsx_form_component_useStyles_0pasaG6nmEA.js"], + "name": "index.tsx_form_component_useStyles_0pasaG6nmEA", "type": "chunk", "dynamicImports": [], - "fileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js", + "fileName": "build/index.tsx_form_component_useStyles_0pasaG6nmEA.js", + "implicitlyLoadedBefore": [], + "importedBindings": {}, + "imports": [], + "modules": { + "/src/routes/form/index.tsx_form_component_useStyles_0pasaG6nmEA.js": { + "code": "", + "originalLength": 1657, + "removedExports": [], + "renderedExports": ["s_0pasaG6nmEA"], + "renderedLength": 2184 + } + }, + "referencedFiles": [], + "viteMetadata": { + "importedAssets": {}, + "importedCss": {} + }, + "code": "var _a;\nconsole.log(\">>> running\", \"/src/routes/form/index.tsx_form_component_useStyles_0pasaG6nmEA.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/form/index.tsx_form_component_useStyles_0pasaG6nmEA.js\");\nconst s_0pasaG6nmEA = `\n .form-container {\n max-width: 42rem;\n margin: 0 auto;\n }\n\n .title {\n font-size: 1.875rem;\n font-weight: bold;\n margin-bottom: 1rem;\n }\n\n .success-message {\n background-color: #dcfce7;\n border: 1px solid #86efac;\n color: #15803d;\n padding: 0.75rem 1rem;\n border-radius: 0.375rem;\n margin-bottom: 1rem;\n }\n\n .form {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n }\n\n .form-group {\n display: flex;\n flex-direction: column;\n }\n\n .label {\n display: block;\n font-size: 0.875rem;\n font-weight: 500;\n color: #374151;\n margin-bottom: 0.25rem;\n }\n\n .input,\n .textarea {\n width: 100%;\n padding: 0.5rem;\n border: 1px solid #d1d5db;\n border-radius: 0.375rem;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n }\n\n .input:focus,\n .textarea:focus {\n outline: none;\n border-color: #3b82f6;\n box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);\n }\n\n .textarea {\n min-height: 6rem;\n }\n\n .submit-button {\n display: inline-flex;\n justify-content: center;\n padding: 0.5rem 1rem;\n font-size: 0.875rem;\n font-weight: 500;\n color: white;\n background-color: #2563eb;\n border: none;\n border-radius: 0.375rem;\n cursor: pointer;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n align-self: flex-start;\n }\n\n .submit-button:hover {\n background-color: #1d4ed8;\n }\n\n .submit-button:focus {\n outline: none;\n box-shadow: 0 0 0 2px white, 0 0 0 4px #2563eb;\n }\n `;\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"0ghf+nnFfGhn9lmN4sYVCTo8Nd/kWTki7QjDoHILwYTVXUfcRldGNYVpsbvoCce13PIZO4oR0bLyZddpR/8ySA==w+Gpd0aFBoKMrbTj/QiWG+xPYzqJebHUzNv3GC2qF8MU1K1sVVZ0ZiGY5CI97p5ZlYnsFuu3GQMq93Z1QBFBLQ==\");\nexport {\n s_0pasaG6nmEA\n};\n", + "map": "", + "preliminaryFileName": "build/index.tsx_form_component_useStyles_0pasaG6nmEA.js", + "sourcemapFileName": null + }, + "build/index.tsx_form_component_ds9jIPT1g9s.js": { + "exports": ["s_ds9jIPT1g9s"], + "facadeModuleId": "/src/routes/form/index.tsx_form_component_ds9jIPT1g9s.js", + "isDynamicEntry": true, + "isEntry": true, + "isImplicitEntry": false, + "moduleIds": ["/src/routes/form/index.tsx_form_component_ds9jIPT1g9s.js"], + "name": "index.tsx_form_component_ds9jIPT1g9s", + "type": "chunk", + "dynamicImports": ["build/index.tsx_form_component_useStyles_0pasaG6nmEA.js"], + "fileName": "build/index.tsx_form_component_ds9jIPT1g9s.js", "implicitlyLoadedBefore": [], "importedBindings": { - "build/core.js": ["u"] + "build/preload-helper.js": ["_"], + "build/qwik-city.js": ["N"], + "build/core.js": ["e", "h", "a", "_", "B", "q"], + "build/index3.js": ["useFormAction"], + "build/preloader.js": [] }, - "imports": ["build/core.js"], + "imports": [ + "build/preload-helper.js", + "build/qwik-city.js", + "build/core.js", + "build/index3.js", + "build/preloader.js" + ], "modules": { - "/starters/apps/perf.prod/src/components/app/app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js": { - "code": "", - "originalLength": 343, + "/src/routes/form/index.tsx_form_component_ds9jIPT1g9s.js": { + "code": "", + "originalLength": 3160, "removedExports": [], - "renderedExports": ["App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI"], - "renderedLength": 283 + "renderedExports": ["s_ds9jIPT1g9s"], + "renderedLength": 64262 } }, "referencedFiles": [], @@ -680,28 +2788,27 @@ "importedAssets": {}, "importedCss": {} }, - "code": "import { u as useLexicalScope } from \"./core.js\";\nconst App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI = () => {\n const [state] = useLexicalScope();\n const d = state.data.slice();\n if (d.length > 998) {\n const tmp = d[1];\n d[1] = d[998];\n d[998] = tmp;\n state.data = d;\n }\n};\nexport {\n App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI\n};\n", + "code": "var _a;\nimport { _ as __vitePreload } from \"./preload-helper.js\";\nimport { N as Form } from \"./qwik-city.js\";\nimport { e as _jsxBranch, h as useStylesQrl, a as _jsxQ, _ as _jsxC, B as _IMMUTABLE, q as qrl } from \"./core.js\";\nimport { useFormAction } from \"./index3.js\";\nimport \"./preloader.js\";\nconsole.log(\">>> running\", \"/src/routes/form/index.tsx_form_component_ds9jIPT1g9s.js\");\n(globalThis._loaded || (globalThis._loaded = [])).push(\"/src/routes/form/index.tsx_form_component_ds9jIPT1g9s.js\");\nconst s_ds9jIPT1g9s = () => {\n var _a2;\n _jsxBranch();\n useStylesQrl(/* @__PURE__ */ qrl(() => __vitePreload(() => import(\"./index.tsx_form_component_useStyles_0pasaG6nmEA.js\"), true ? [] : void 0), \"s_0pasaG6nmEA\"));\n const action = useFormAction();\n return /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"form-container\"\n }, [\n /* @__PURE__ */ _jsxQ(\"h1\", null, {\n class: \"title\"\n }, \"Contact Form\", 3, null),\n ((_a2 = action.value) == null ? void 0 : _a2.success) ? /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"success-message\"\n }, \"Thank you for your submission!\", 3, \"sc_0\") : /* @__PURE__ */ _jsxC(Form, {\n action,\n class: \"form\",\n children: [\n /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"form-group\"\n }, [\n /* @__PURE__ */ _jsxQ(\"label\", null, {\n for: \"name\",\n class: \"label\"\n }, \"Name\", 3, null),\n /* @__PURE__ */ _jsxQ(\"input\", null, {\n id: \"name\",\n name: \"name\",\n type: \"text\",\n class: \"input\",\n required: true\n }, null, 3, null)\n ], 3, null),\n /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"form-group\"\n }, [\n /* @__PURE__ */ _jsxQ(\"label\", null, {\n for: \"email\",\n class: \"label\"\n }, \"Email\", 3, null),\n /* @__PURE__ */ _jsxQ(\"input\", null, {\n id: \"email\",\n name: \"email\",\n type: \"email\",\n class: \"input\",\n required: true\n }, null, 3, null)\n ], 3, null),\n /* @__PURE__ */ _jsxQ(\"div\", null, {\n class: \"form-group\"\n }, [\n /* @__PURE__ */ _jsxQ(\"label\", null, {\n for: \"message\",\n class: \"label\"\n }, \"Message\", 3, null),\n /* @__PURE__ */ _jsxQ(\"textarea\", null, {\n id: \"message\",\n name: \"message\",\n class: \"textarea\",\n required: true\n }, null, 3, null)\n ], 3, null),\n /* @__PURE__ */ _jsxQ(\"button\", null, {\n type: \"submit\",\n class: \"submit-button\"\n }, \"Submit\", 3, null)\n ],\n [_IMMUTABLE]: {\n action: _IMMUTABLE,\n class: _IMMUTABLE\n }\n }, 3, \"sc_1\")\n ], 1, \"sc_2\");\n};\n(_a = globalThis._fakeBulk) == null ? void 0 : _a.call(globalThis, \"nSAoVof+4jTD4WZso0ofXRXry5PtcAebYCfkZe/hLcCpzLnG0lSv7yh5p/77oAiARcWTYiJlnDz6FQPAzu53rg==QVqI/osX13ngRwEXcHENtuN4uoDN7gBgmdzwbFbxIzqXwNSQX8v+tl217vVWX5XpS3tLTgl6EUHGwZ0P0pZvYQ==0/GnOUDyFYqjtXpNiGKRZ71Vcslah4Lg+MUW396BkpbL3hrFrQvvb5Azy/JAnum4pLwXN5PQna3pl8Xo/QNSag==K/HUVLc/a4EG/x23XTh9dxAR4pSyaKZSsjzP0WBJfIOZ/PE1a51WYJ1W3mMcDeduuF5ygqz4EfaaBKiHch0p5w==OYr8n1nV/wnKgNkAcMCON1xISs8XTVy/8aUKowetIgWTsnJJr2rF86a2fmhZbYphA/UPjfYl4jcN0BrzuKDvtg==WEuloRy5Duk09KzWCfYfv4uLVtHqyLhGhknfNraAno9+sGYP+Mqor1hZS30C6mI6sJAlwr5HTC8uX7sM0dfJTA==KO5MJWUhSRijj97HZs5b/tHEWkg0KWu4ESYHEcd53Kv1+jcYliXpNjmZush2RA0IbQ1WcGqaQhh7RcqybS2bNg==punHw7nUy1KpICt9JCLJQUhhrXnNqOqhFKJTD9GyXBiKgDf5qHVfF5Rhe3sJIJTxhAfQc1KrwhPDIiyp6jSPXA==M9yO715EQq2sNo5B9/Rs/+ashZL/RH9qMZt+YAKib95om495zi+r6r52EqNTZeDAmPOkFEEdEKGOZ1/bh7dpyQ==74DG5er9f4u9z+Otye9PV2v93crD1IRJnC6aI6wA5OBckIoqe/MVTTdrpl44m0j/TPpJ0KEmhCJ+RW1mRR7oEg==dIw90guHlQtHDzctX92w9Xo0gRK4qN/8beRYmrmi4xqztoJnDaL9JYqzMYfk557+J284ul3V81/x89q4sr409w==5hRJlk4mjUdObzH07Zc1W9c4jf8ZtZ2gQdRIGMmGQgW+b1g/zWK5mveB1715nRjD0zE9Z0CTbGvcMHT1uUvHmQ==boqe5X51ZNPN95+jf/1qPrO+fFIezjshJOzwaIxXuuegvfiCFF/MKQLeNhGXA2odkEo3edU8abJc4UAK2aDUjw==R7CCIn6xwqIvnuYP6acDV3f2mUErqRzvh+sB9IQ6ymKGckW5GNwj5DeQ/7O71O2GEwUBgbcxXn/0tsPwXOB70g==rXDl5hErxIvCiUajwyS+TSZ5yR4W0yO6zNN29kzai+nL+wTD0zNakl0njAyYX6FVG/xKcm7q+6IUDy4v2J6GmQ==QRDdGl+mFIbPX5IWF3y0mp2CUDb3vl5hslQnZJsmwNX4wH9EGKsLLNR6OE1smFrVBnpq7IJMQ+DqjM2BlrdF1w==5LO/Vpm44dxZ/gpSuXwJzQR9ssGNEvfoejHkx7tbRmLxzQfDLENcAlwjKxKsreq5arsIl9FKlBopgsEj49p16A==h0xzmxfVxD9E0IJiTS+Da7+u5Au7e9bl67W6s8C/rquebDjZNHD6eZMF7CWJoGyFp7fZ1hnjfI+IXYw74Sx/Fw==tVo9Dd3+BUp7K1oh9s3WoDam1aRIuT/r973Dg+W2aBVRBR5/suXhvnSjtJcEa2TKAE+0I0gIf/0xuTcifYh6VA==Zfk0VZRQC9zH3BA0RiVCrvemEv2PgBgyRrbzdP4IVZ6qyexyfgG2mD0sy0Oaaf6lZaAI4YKAPgGMeAmmitQ/Xw==Gzvt3BZcyKnmiyGCmjWW7PY3rjkiAbzulwU7lAK2HvvKeFH4ol+RswcldW6uL+2WUFahThpYt/7+HOQEjGoXtA==b6zYE3Nfk3ofaczkjLYQaY7HWCdcVnuA9bmNAU8olca/xMH9iwiG6LW/gGIAXgnXLX6sTIDgeNLQ2JUiEvr3uQ==1zvVQVv5JxDN1SqD7cXnDaWstm65EaYXkkBpeirwzD4Znt7CrS3SJ1XUVGwJBifcoucY0bl6XEJq", "map": "", - "preliminaryFileName": "build/app.tsx_App_component_div_div_div_div_div_div_button_onClick_5_rpMfjSetIeI.js", + "preliminaryFileName": "build/index.tsx_form_component_ds9jIPT1g9s.js", "sourcemapFileName": null }, - "assets/97WVOY9i-style.css": { - "fileName": "assets/97WVOY9i-style.css", - "name": "style.css", - "needsCodeReference": false, - "source": "", - "type": "asset" - }, "q-manifest.json": { "fileName": "q-manifest.json", + "names": [], "needsCodeReference": false, - "source": "", + "originalFileName": null, + "originalFileNames": [], + "source": "", "type": "asset" }, - "build/q-bundle-graph-uiknax.json": { - "fileName": "build/q-bundle-graph-uiknax.json", + "build/q-bundle-graph-3c2vzr.json": { + "fileName": "build/q-bundle-graph-3c2vzr.json", + "names": [], "needsCodeReference": false, - "source": "", + "originalFileName": null, + "originalFileNames": [], + "source": "", "type": "asset" } } diff --git a/packages/qwik/src/optimizer/src/plugins/plugin.ts b/packages/qwik/src/optimizer/src/plugins/plugin.ts index bd2d2bb274c..3c6eb30f0ce 100644 --- a/packages/qwik/src/optimizer/src/plugins/plugin.ts +++ b/packages/qwik/src/optimizer/src/plugins/plugin.ts @@ -17,10 +17,13 @@ import type { TransformModulesOptions, TransformOutput, SmartEntryStrategy, + ServerQwikManifest, } from '../types'; import { createLinter, type QwikLinter } from './eslint-plugin'; import type { LoadResult, OutputBundle, ResolveIdResult, TransformResult } from 'rollup'; -import { isWin } from './vite-utils'; +import { isWin, parseId } from './vite-utils'; +import type { BundleGraphAdder } from '..'; +import { convertManifestToBundleGraph } from './bundle-graph'; const REG_CTX_NAME = ['server']; @@ -775,7 +778,8 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) { const injections: GlobalInjections[] = []; const addInjection = (b: GlobalInjections) => injections.push(b); - const generateManifest = async () => { + + const generateManifest = async (extra?: Partial) => { const optimizer = getOptimizer(); const path = optimizer.sys.path; @@ -792,6 +796,9 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) { opts, debug ); + if (extra) { + Object.assign(manifest, extra); + } for (const symbol of Object.values(manifest.symbols)) { if (symbol.origin) { @@ -855,8 +862,18 @@ export const isDev = ${JSON.stringify(isDev)}; async function getQwikServerManifestModule(isServer: boolean) { const manifest = isServer ? opts.manifestInput : null; + let serverManifest: ServerQwikManifest | null = null; + if (manifest?.manifestHash) { + serverManifest = { + manifestHash: manifest.manifestHash, + injections: manifest.injections, + bundleGraph: manifest.bundleGraph, + mapping: manifest.mapping, + preloader: manifest.preloader, + }; + } return `// @qwik-client-manifest -export const manifest = ${JSON.stringify(manifest)};\n`; +export const manifest = ${JSON.stringify(serverManifest)};\n`; } function setSourceMapSupport(sourcemap: boolean) { @@ -889,8 +906,12 @@ export const manifest = ${JSON.stringify(manifest)};\n`; } function manualChunks(id: string, { getModuleInfo }: Rollup.ManualChunkMeta) { - // The preloader has to stay in a separate chunk - if (id.endsWith(QWIK_PRELOADER_REAL_ID)) { + // The preloader has to stay in a separate chunk if it's a client build + // the vite preload helper must be included or to prevent breaking circular dependencies + if ( + opts.target === 'client' && + (id.endsWith(QWIK_PRELOADER_REAL_ID) || id === '\0vite/preload-helper.js') + ) { return 'qwik-preloader'; } @@ -906,6 +927,59 @@ export const manifest = ${JSON.stringify(manifest)};\n`; return null; } + async function generateManifest( + ctx: Rollup.PluginContext, + rollupBundle: OutputBundle, + bundleGraphAdders?: Set, + manifestExtra?: Partial + ) { + const outputAnalyzer = createOutputAnalyzer(rollupBundle); + const manifest = await outputAnalyzer.generateManifest(manifestExtra); + + manifest.platform = { + ...manifestExtra?.platform, + rollup: ctx.meta?.rollupVersion || '', + env: optimizer.sys.env, + os: optimizer.sys.os, + }; + if (optimizer.sys.env === 'node') { + manifest.platform!.node = process.versions.node; + } + + const assetsDir = opts.assetsDir; + const useAssetsDir = !!assetsDir && assetsDir !== '_astro'; + const bundleGraph = convertManifestToBundleGraph(manifest, bundleGraphAdders); + ctx.emitFile({ + type: 'asset', + fileName: optimizer.sys.path.join( + useAssetsDir ? assetsDir : '', + 'build', + `q-bundle-graph-${manifest.manifestHash}.js` + ), + source: `export const B=${JSON.stringify(bundleGraph)}`, + }); + + manifest.bundleGraph = bundleGraph; + + const manifestStr = JSON.stringify(manifest, null, '\t'); + ctx.emitFile({ + fileName: Q_MANIFEST_FILENAME, + type: 'asset', + source: manifestStr, + }); + + if (typeof opts.manifestOutput === 'function') { + await opts.manifestOutput(manifest); + } + + if (typeof opts.transformedModuleOutput === 'function') { + await opts.transformedModuleOutput(getTransformedOutputs()); + } + + // TODO get rid of this with the vite environment api + return manifestStr; + } + return { buildStart, createOutputAnalyzer, @@ -929,6 +1003,7 @@ export const manifest = ${JSON.stringify(manifest)};\n`; configureServer, handleHotUpdate, manualChunks, + generateManifest, }; } @@ -958,20 +1033,6 @@ function isAdditionalFile(mod: TransformModule) { return mod.isEntry || mod.segment; } -export function parseId(originalId: string) { - const [pathId, query] = originalId.split('?'); - const queryStr = query || ''; - return { - originalId, - pathId, - query: queryStr ? `?${query}` : '', - params: new URLSearchParams(queryStr), - }; -} - -export const getSymbolHash = (symbolName: string) => - /_([a-z0-9]+)($|\.js($|\?))/.exec(symbolName)?.[1]; - const TRANSFORM_EXTS = { '.jsx': true, '.ts': true, diff --git a/packages/qwik/src/optimizer/src/plugins/rollup.ts b/packages/qwik/src/optimizer/src/plugins/rollup.ts index f03e73d7c20..ba3002f4d24 100644 --- a/packages/qwik/src/optimizer/src/plugins/rollup.ts +++ b/packages/qwik/src/optimizer/src/plugins/rollup.ts @@ -3,23 +3,21 @@ import type { Rollup } from 'vite'; import type { Diagnostic, EntryStrategy, + Optimizer, OptimizerOptions, QwikManifest, - TransformModuleInput, TransformModule, - Optimizer, + TransformModuleInput, } from '../types'; import { createQwikPlugin, + type ExperimentalFeatures, type NormalizedQwikPluginOptions, type QwikBuildMode, type QwikBuildTarget, - type QwikPluginOptions, - Q_MANIFEST_FILENAME, - type ExperimentalFeatures, type QwikPlugin, + type QwikPluginOptions, } from './plugin'; -import { versions } from '../versions'; type QwikRollupPluginApi = { getOptimizer: () => Optimizer; @@ -121,33 +119,7 @@ export function qwikRollup(qwikRollupOpts: QwikRollupPluginOptions = {}): any { const opts = qwikPlugin.getOptions(); if (opts.target === 'client') { - // client build - const optimizer = qwikPlugin.getOptimizer(); - const outputAnalyzer = qwikPlugin.createOutputAnalyzer(rollupBundle); - const manifest = await outputAnalyzer.generateManifest(); - manifest.platform = { - ...versions, - rollup: this.meta?.rollupVersion || '', - env: optimizer.sys.env, - os: optimizer.sys.os, - }; - if (optimizer.sys.env === 'node') { - manifest.platform.node = process.versions.node; - } - - if (typeof opts.manifestOutput === 'function') { - await opts.manifestOutput(manifest); - } - - if (typeof opts.transformedModuleOutput === 'function') { - await opts.transformedModuleOutput(qwikPlugin.getTransformedOutputs()); - } - - this.emitFile({ - type: 'asset', - fileName: Q_MANIFEST_FILENAME, - source: JSON.stringify(manifest, null, 2), - }); + await qwikPlugin.generateManifest(this, rollupBundle); } }, }; diff --git a/packages/qwik/src/optimizer/src/plugins/vite-dev-server.ts b/packages/qwik/src/optimizer/src/plugins/vite-dev-server.ts index 9bc5dfdec80..06cd2b831d7 100644 --- a/packages/qwik/src/optimizer/src/plugins/vite-dev-server.ts +++ b/packages/qwik/src/optimizer/src/plugins/vite-dev-server.ts @@ -5,15 +5,21 @@ import { magenta } from 'kleur/colors'; import type { Connect, ViteDevServer } from 'vite'; import { SYNC_QRL } from '../../../core/qrl/qrl-class'; -import type { OptimizerSystem, Path, QwikManifest, SymbolMapper, SymbolMapperFn } from '../types'; +import type { + OptimizerSystem, + Path, + ServerQwikManifest, + SymbolMapper, + SymbolMapperFn, +} from '../types'; import clickToComponent from './click-to-component.html?raw'; import errorHost from './error-host.html?raw'; import imageDevTools from './image-size-runtime.html?raw'; import perfWarning from './perf-warning.html?raw'; -import { parseId, type NormalizedQwikPluginOptions } from './plugin'; +import { type NormalizedQwikPluginOptions } from './plugin'; import type { QwikViteDevResponse } from './vite'; import { VITE_ERROR_OVERLAY_STYLES } from './vite-error'; -import { formatError } from './vite-utils'; +import { formatError, parseId } from './vite-utils'; function getOrigin(req: IncomingMessage) { const { PROTOCOL_HEADER, HOST_HEADER } = process.env; @@ -135,13 +141,10 @@ export async function configureDevServer( const render: Render = ssrModule.default ?? ssrModule.render; if (typeof render === 'function') { - const manifest: QwikManifest = { + const manifest: ServerQwikManifest = { manifestHash: '', - symbols: {}, mapping: {}, - bundles: {}, injections: [], - version: '1', }; const added = new Set(); @@ -208,7 +211,6 @@ export async function configureDevServer( snapshot: !isClientDevOnly, manifest: isClientDevOnly ? undefined : manifest, symbolMapper: isClientDevOnly ? undefined : symbolMapper, - prefetchStrategy: null, serverData, containerAttributes: { ...serverData.containerAttributes }, }; diff --git a/packages/qwik/src/optimizer/src/plugins/vite-utils.ts b/packages/qwik/src/optimizer/src/plugins/vite-utils.ts index 36f4f6b49e7..6c1c8c3de58 100644 --- a/packages/qwik/src/optimizer/src/plugins/vite-utils.ts +++ b/packages/qwik/src/optimizer/src/plugins/vite-utils.ts @@ -149,3 +149,17 @@ export function generateCodeFrame( export function isWin(os: string): boolean { return os === 'win32'; } + +export function parseId(originalId: string) { + const [pathId, query] = originalId.split('?'); + const queryStr = query || ''; + return { + originalId, + pathId, + query: queryStr ? `?${query}` : '', + params: new URLSearchParams(queryStr), + }; +} + +export const getSymbolHash = (symbolName: string) => + /_([a-zA-Z0-9]+)($|\.js($|\?))/.exec(symbolName)?.[1]; diff --git a/packages/qwik/src/optimizer/src/plugins/vite.ts b/packages/qwik/src/optimizer/src/plugins/vite.ts index 52ef84e9503..a1c90a66169 100644 --- a/packages/qwik/src/optimizer/src/plugins/vite.ts +++ b/packages/qwik/src/optimizer/src/plugins/vite.ts @@ -9,8 +9,7 @@ import type { QwikManifest, TransformModule, } from '../types'; -import { versions } from '../versions'; -import { convertManifestToBundleGraph, type BundleGraphAdder } from './bundle-graph'; +import { type BundleGraphAdder } from './bundle-graph'; import { getImageSizeServer } from './image-size-server'; import { CLIENT_OUT_DIR, @@ -20,11 +19,9 @@ import { QWIK_CORE_SERVER, QWIK_JSX_DEV_RUNTIME_ID, QWIK_JSX_RUNTIME_ID, - Q_MANIFEST_FILENAME, SSR_OUT_DIR, TRANSFORM_REGEX, createQwikPlugin, - parseId, type ExperimentalFeatures, type NormalizedQwikPluginOptions, type QwikBuildMode, @@ -34,6 +31,7 @@ import { } from './plugin'; import { createRollupError, normalizeRollupOutputOptions } from './rollup'; import { VITE_DEV_CLIENT_QS, configureDevServer, configurePreviewServer } from './vite-dev-server'; +import { parseId } from './vite-utils'; const DEDUPE = [QWIK_CORE_ID, QWIK_JSX_RUNTIME_ID, QWIK_JSX_DEV_RUNTIME_ID]; @@ -486,7 +484,7 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any { } return qwikPlugin.transform(this, code, id, transformOpts); }, - }; + } as const satisfies VitePlugin; const vitePluginPost: VitePlugin = { name: 'vite-plugin-qwik-post', @@ -499,7 +497,6 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any { if (opts.target === 'client') { // client build - const outputAnalyzer = qwikPlugin.createOutputAnalyzer(rollupBundle); for (const [fileName, b] of Object.entries(rollupBundle)) { if (b.type === 'asset') { @@ -543,53 +540,17 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any { } } - for (const i of injections) { - outputAnalyzer.addInjection(i); - } - - const optimizer = qwikPlugin.getOptimizer(); - const manifest = await outputAnalyzer.generateManifest(); - manifest.platform = { - ...versions, - vite: '', - rollup: this.meta?.rollupVersion || '', - env: optimizer.sys.env, - os: optimizer.sys.os, - }; - if (optimizer.sys.env === 'node') { - manifest.platform.node = process.versions.node; - } + const clientManifestStr = await qwikPlugin.generateManifest( + this, + rollupBundle, + bundleGraphAdders, + { + injections, + platform: { vite: '' }, + } + ); - const clientManifestStr = JSON.stringify(manifest, null, 2); - this.emitFile({ - type: 'asset', - fileName: Q_MANIFEST_FILENAME, - source: clientManifestStr, - }); - const assetsDir = qwikPlugin.getOptions().assetsDir || ''; - const useAssetsDir = !!assetsDir && assetsDir !== '_astro'; const sys = qwikPlugin.getSys(); - - const bundleGraph = convertManifestToBundleGraph(manifest, bundleGraphAdders); - - this.emitFile({ - type: 'asset', - fileName: sys.path.join( - useAssetsDir ? assetsDir : '', - 'build', - `q-bundle-graph-${manifest.manifestHash}.json` - ), - source: JSON.stringify(bundleGraph), - }); - - if (typeof opts.manifestOutput === 'function') { - await opts.manifestOutput(manifest); - } - - if (typeof opts.transformedModuleOutput === 'function') { - await opts.transformedModuleOutput(qwikPlugin.getTransformedOutputs()); - } - if (tmpClientManifestPath && sys.env === 'node') { // Client build should write the manifest to a tmp dir const fs: typeof import('fs') = await sys.dynamicImport('node:fs'); @@ -728,7 +689,7 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any { return false; } }, - }; + } as const satisfies VitePlugin; return [vitePluginPre, vitePluginPost]; } @@ -866,7 +827,7 @@ const findQwikRoots = async ( } catch (e) { console.error(e); } - } catch (e) { + } catch { // ignore errors if package.json not found } prevPackageJsonDir = packageJsonDir; diff --git a/packages/qwik/src/optimizer/src/types.ts b/packages/qwik/src/optimizer/src/types.ts index 85143cf6e2c..f9e01262dba 100644 --- a/packages/qwik/src/optimizer/src/types.ts +++ b/packages/qwik/src/optimizer/src/types.ts @@ -223,18 +223,32 @@ export interface QwikManifest { mapping: { [symbolName: string]: string }; /** All code bundles, used to know the import graph */ bundles: { [fileName: string]: QwikBundle }; - /** The preloader bundle */ + /** All bundles in a compact graph format with probabilities */ + bundleGraph?: QwikBundleGraph; + /** The preloader bundle fileName */ preloader?: string; /** CSS etc to inject in the document head */ injections?: GlobalInjections[]; + /** The version of the manifest */ version: string; + /** The options used to build the manifest */ options?: { target?: string; buildMode?: string; - entryStrategy?: { [key: string]: any }; + entryStrategy?: { type: EntryStrategy['type'] }; }; + /** The platform used to build the manifest */ platform?: { [name: string]: string }; } +/** + * The manifest values that are needed for SSR. + * + * @public + */ +export type ServerQwikManifest = Pick< + QwikManifest, + 'manifestHash' | 'injections' | 'bundleGraph' | 'mapping' | 'preloader' +>; /** * Bundle graph. @@ -273,6 +287,10 @@ export interface QwikSymbol { export interface QwikBundle { /** Size of the bundle */ size: number; + /** Total size of this bundle's static import graph */ + total: number; + /** Interactivity score of the bundle */ + interactivity?: number; /** Symbols in the bundle */ symbols?: string[]; /** Direct imports */ @@ -326,4 +344,5 @@ export interface Path { export interface ResolvedManifest { mapper: SymbolMapper; manifest: QwikManifest; + injections: GlobalInjections[]; } diff --git a/packages/qwik/src/server/api.md b/packages/qwik/src/server/api.md index e2cc92f48c0..69df40207ed 100644 --- a/packages/qwik/src/server/api.md +++ b/packages/qwik/src/server/api.md @@ -43,11 +43,18 @@ export type InOrderStreaming = InOrderAuto | InOrderDisabled | InOrderDirect; // @public (undocumented) export interface PrefetchImplementation { + debug?: boolean; linkFetchPriority?: 'auto' | 'low' | 'high' | null; + // @deprecated (undocumented) linkInsert?: 'js-append' | 'html-append' | null; linkRel?: 'prefetch' | 'preload' | 'modulepreload' | null; + maxPreloads?: number; + maxSimultaneousPreloads?: number; + minPreloadProbability?: number; + minProbability?: number; + // @deprecated (undocumented) prefetchEvent?: 'always' | null; - // @deprecated + // @deprecated (undocumented) workerFetchInsert?: 'always' | 'no-link-support' | null; } @@ -56,8 +63,6 @@ export interface PrefetchResource { // (undocumented) imports: PrefetchResource[]; // (undocumented) - priority: boolean; - // (undocumented) url: string; } @@ -109,8 +114,6 @@ export interface RenderResult { prefetchResources: PrefetchResource[]; // (undocumented) snapshotResult: SnapshotResult | undefined; - // @internal - _symbols?: string[]; } // @public (undocumented) @@ -164,21 +167,21 @@ export interface RenderToStringResult extends RenderResult { // Warning: (ae-forgotten-export) The symbol "ResolvedManifest_2" needs to be exported by the entry point index.d.ts // -// @public (undocumented) -export function resolveManifest(manifest: QwikManifest | ResolvedManifest_2 | undefined): ResolvedManifest_2 | undefined; +// @public +export function resolveManifest(manifest?: Partial | undefined): ResolvedManifest_2 | undefined; // @public (undocumented) export interface SerializeDocumentOptions { // (undocumented) debug?: boolean; // (undocumented) - manifest?: QwikManifest | ResolvedManifest; + manifest?: Partial; // (undocumented) symbolMapper?: SymbolMapperFn; } // @public (undocumented) -export function setServerPlatform(manifest: QwikManifest | ResolvedManifest | undefined): Promise; +export function setServerPlatform(manifest?: Partial): Promise; // @public (undocumented) export interface StreamingOptions { diff --git a/packages/qwik/src/server/index.ts b/packages/qwik/src/server/index.ts index 635ea650ff3..56ff59e61c9 100644 --- a/packages/qwik/src/server/index.ts +++ b/packages/qwik/src/server/index.ts @@ -30,7 +30,7 @@ export { versions } from './utils'; export { getQwikLoaderScript, getQwikPrefetchWorkerScript } from './scripts'; /** @public */ -export async function setServerPlatform(manifest: QwikManifest | ResolvedManifest | undefined) { +export async function setServerPlatform(manifest?: Partial) { const platform = createPlatform({ manifest }, resolveManifest(manifest)); setPlatform(platform); } diff --git a/packages/qwik/src/server/prefetch-implementation.ts b/packages/qwik/src/server/prefetch-implementation.ts index c6b5ebbb09c..66dc59a879c 100644 --- a/packages/qwik/src/server/prefetch-implementation.ts +++ b/packages/qwik/src/server/prefetch-implementation.ts @@ -1,218 +1,113 @@ -import { Fragment, jsx, type JSXNode } from '@builder.io/qwik'; -import { flattenPrefetchResources, getMostReferenced, workerFetchScript } from './prefetch-utils'; -import type { - PrefetchImplementation, - PrefetchResource, - PrefetchStrategy, - QwikManifest, -} from './types'; +import { Fragment, jsx, type JSXNode, type PropsOf } from '@builder.io/qwik'; +import type { ResolvedManifest } from '../optimizer/src/types'; +import { expandBundles } from './prefetch-strategy'; +import type { PrefetchImplementation, PrefetchStrategy } from './types'; -export function applyPrefetchImplementation( +export function includePreloader( base: string, - manifest: QwikManifest | undefined, + manifest: ResolvedManifest | undefined, prefetchStrategy: PrefetchStrategy | undefined, - prefetchResources: PrefetchResource[], + referencedBundles: string[], nonce?: string ): JSXNode | null { - // if prefetchStrategy is undefined, use defaults - // set default if implementation wasn't provided - const prefetchImpl = normalizePrefetchImplementation(prefetchStrategy?.implementation); - - const prefetchNodes: JSXNode[] = []; - - if (prefetchImpl.prefetchEvent === 'always') { - prefetchUrlsEvent(base, prefetchNodes, prefetchResources, nonce); - } - - if (prefetchImpl.linkInsert === 'html-append') { - linkHtmlImplementation( - base, - manifest?.manifestHash, - nonce, - prefetchNodes, - prefetchResources, - prefetchImpl - ); - } - - if (prefetchImpl.linkInsert === 'js-append') { - linkJsImplementation(base, manifest, nonce, prefetchNodes, prefetchResources, prefetchImpl); - } else if (prefetchImpl.workerFetchInsert === 'always') { - workerFetchImplementation(prefetchNodes, prefetchResources, nonce); + if (referencedBundles.length === 0) { + return null; } - - if (prefetchNodes.length > 0) { - return jsx(Fragment, { children: prefetchNodes }); - } - - return null; -} - -function prefetchUrlsEvent( - base: string, - prefetchNodes: JSXNode[], - prefetchResources: PrefetchResource[], - nonce?: string -) { - const mostReferenced = getMostReferenced(prefetchResources); - for (const url of mostReferenced) { - prefetchNodes.push( - jsx('link', { - rel: 'modulepreload', - href: base + url, - nonce, - }) - ); + const { + maxPreloads, + linkRel, + linkFetchPriority, + minProbability, + debug, + maxSimultaneousPreloads, + minPreloadProbability, + } = normalizePrefetchImplementation(prefetchStrategy?.implementation); + let allowed = maxPreloads; + + const nodes: JSXNode[] = []; + + if (import.meta.env.DEV) { + // Vite dev server active + // in dev, all bundles are absolute paths from the base url, not /build + base = import.meta.env.BASE_URL; + if (base.endsWith('/')) { + base = base.slice(0, -1); + } } - // TODO: convert links to bundles - // prefetchNodes.push( - // jsx('script', { - // 'q:type': 'prefetch-bundles', - // dangerouslySetInnerHTML: - // prefetchUrlsEventScript(base, prefetchResources), - // nonce, - // }) - // ); -} - -/** Creates the `` within the rendered html */ -function linkHtmlImplementation( - base: string, - manifestHash: string | undefined, - nonce: string | undefined, - prefetchNodes: JSXNode[], - prefetchResources: PrefetchResource[], - prefetchImpl: Required -) { - const urls = flattenPrefetchResources(prefetchResources); - const rel = prefetchImpl.linkRel || 'modulepreload'; - if (manifestHash) { - prefetchNodes.push( - jsx('link', { - rel: 'fetch', - id: `qwik-bg-${manifestHash}`, - href: `${base}q-bundle-graph-${manifestHash}.json`, - as: 'fetch', - crossorigin: 'anonymous', - fetchpriority: prefetchImpl.linkFetchPriority || undefined, - }) - ); - } - for (const [url, priority] of urls) { - const fetchpriority = priority - ? prefetchImpl.linkFetchPriority || 'high' - : prefetchImpl.linkFetchPriority === 'low' - ? 'low' - : undefined; - prefetchNodes.push( - jsx('link', { - href: base + url, - rel, - fetchpriority, - nonce, - // TODO: add integrity - }) - ); + const makeLink = (base: string, href: string) => { + const linkProps: PropsOf<'link'> = { + rel: linkRel!, + href: `${base}${href}`, + }; + if (linkRel !== 'modulepreload') { + linkProps['fetchPriority'] = linkFetchPriority!; + linkProps['as'] = 'script'; + } + return jsx('link', linkProps as any); + }; + + const preloadChunk = manifest?.manifest.preloader; + const manifestHash = manifest?.manifest.manifestHash; + if (allowed && preloadChunk) { + allowed--; + nodes.push(makeLink(base, preloadChunk!)); + if (allowed && manifestHash) { + allowed--; + nodes.push(makeLink(base, `q-bundle-graph-${manifestHash}.js`)); + } } -} - -/** - * Uses the preloader chunk to add the `` elements at runtime. This allows core to simply - * import the preloader as well and have all the state there, plus it makes it easy to write a - * complex implementation. - */ -function linkJsImplementation( - base: string, - manifest: QwikManifest | undefined, - nonce: string | undefined, - prefetchNodes: JSXNode[], - prefetchResources: PrefetchResource[], - prefetchImpl: Required -) { - const preloadChunk = manifest?.preloader; - if (!preloadChunk) { - return linkHtmlImplementation( - base, - manifest?.manifestHash, - nonce, - prefetchNodes, - prefetchResources, - prefetchImpl - ); + if (allowed) { + const expandedBundles = expandBundles(referencedBundles, manifest); + // Keep the same as in expandBundles (but *10) + let probability = 8; + const tenXMinProbability = minProbability * 10; + for (const bundleOrProbability of expandedBundles) { + if (typeof bundleOrProbability === 'string') { + if (probability < tenXMinProbability) { + break; + } + nodes.push(makeLink(base, bundleOrProbability)); + if (--allowed === 0) { + break; + } + } else { + probability = bundleOrProbability; + } + } } - const manifestHash = manifest.manifestHash; - const urlMap = flattenPrefetchResources(prefetchResources); - - // TODO modulepreload the preloader before ssr, optional because we can't predict if we need it - - // TODO order imports by size/number of dependents? - if (urlMap.size) { - const urls = [...urlMap.keys()]; - - // Already fetch the first 7 urls while we wait for the preloader to load - for (const url of urls.slice(0, 7)) { - prefetchNodes.push( - jsx('link', { - href: base + url, - // not modulepreload, we don't want to fetch dependencies yet - rel: 'preload', - as: 'script', - fetchpriority: 'low', - }) - ); + if (preloadChunk) { + const opts: string[] = []; + if (debug) { + opts.push('d:1'); } - - // preload the bundle graph at low priority - // TODO make this a .js file so we can modulepreload it - prefetchNodes.push( - jsx('link', { - rel: 'fetch', - id: `qwik-bg-${manifestHash}`, - href: `${base}q-bundle-graph-${manifestHash}.json`, - as: 'fetch', - crossorigin: 'anonymous', - fetchpriority: 'low', - }) - ); - - // We request all the urls as low priority, so that newly needed resources are loaded first - // We use a Promise so the script doesn't block the initial page load - const script = - `const d=Date.now();console.log('preloader loading',d);` + - `import("${base}${preloadChunk}").then(({l,p})=>{` + - (`console.log('preloader start',Date.now()-d);` + - `l(${JSON.stringify(base)},${JSON.stringify(manifestHash)});` + - `p(${JSON.stringify([...urlMap.keys()])});`) + - `})`; - - prefetchNodes.push( + if (maxSimultaneousPreloads) { + opts.push(`P:${maxSimultaneousPreloads}`); + } + if (minPreloadProbability) { + opts.push(`Q:${minPreloadProbability}`); + } + const optsStr = opts.length ? `,{${opts.join(',')}}` : ''; + /** + * Uses the preloader chunk to add the `` elements at runtime. This allows core to simply + * import the preloader as well and have all the state there, plus it makes it easy to write a + * complex implementation. + */ + nodes.push( jsx('script', { type: 'module', 'q:type': 'link-js', - dangerouslySetInnerHTML: script, + dangerouslySetInnerHTML: `import("${base}${preloadChunk}").then(({l,p})=>{l(${JSON.stringify(base)}${manifestHash ? `,${JSON.stringify(manifestHash)}` : ''}${optsStr});p(${JSON.stringify(referencedBundles)});})`, nonce, }) ); } -} -function workerFetchImplementation( - prefetchNodes: JSXNode[], - prefetchResources: PrefetchResource[], - nonce?: string -) { - let s = `const u=${JSON.stringify(flattenPrefetchResources(prefetchResources).keys())};`; - s += workerFetchScript(); + if (nodes.length > 0) { + return jsx(Fragment, { children: nodes }); + } - prefetchNodes.push( - jsx('script', { - type: 'module', - 'q:type': 'prefetch-worker', - dangerouslySetInnerHTML: s, - nonce, - }) - ); + return null; } function normalizePrefetchImplementation( @@ -222,9 +117,14 @@ function normalizePrefetchImplementation( } const PrefetchImplementationDefault: Required = { - linkInsert: 'js-append', + maxPreloads: import.meta.env.DEV ? 10 : 5, + minProbability: 0.6, + debug: false, + maxSimultaneousPreloads: 5, + minPreloadProbability: 0.25, linkRel: 'modulepreload', - linkFetchPriority: null, - workerFetchInsert: null, - prefetchEvent: null, + linkFetchPriority: undefined!, + linkInsert: undefined!, + workerFetchInsert: undefined!, + prefetchEvent: undefined!, }; diff --git a/packages/qwik/src/server/prefetch-strategy.ts b/packages/qwik/src/server/prefetch-strategy.ts index d6ac5d2e6d7..a55ba67579c 100644 --- a/packages/qwik/src/server/prefetch-strategy.ts +++ b/packages/qwik/src/server/prefetch-strategy.ts @@ -1,117 +1,71 @@ -import type { - PrefetchResource, - QwikManifest, - RenderToStringOptions, - SnapshotResult, -} from './types'; -import { getBuildBase } from './utils'; - import type { ResolvedManifest } from '@builder.io/qwik/optimizer'; +import { getQueue, preload, resetQueue } from '../core/preloader/queue'; import type { QRLInternal } from '../core/qrl/qrl-class'; +import { flattenPrefetchResources } from './prefetch-utils'; +import type { RenderToStringOptions, SnapshotResult } from './types'; +import { getPlatform } from '@builder.io/qwik'; +import { getSymbolHash } from './platform'; -export function getPrefetchResources( +const getBundles = (snapshotResult: SnapshotResult | null) => { + const platform = getPlatform(); + return (snapshotResult?.qrls as QRLInternal[]) + ?.map((qrl) => { + const symbol = qrl.$refSymbol$ || qrl.$symbol$; + const chunk = qrl.$chunk$; + const result = platform.chunkForSymbol(symbol, chunk, qrl.dev?.file); + if (result) { + return result[1]; + } + return chunk; + }) + .filter(Boolean) as string[]; +}; +/** Returns paths to preload relative to the buildBase, with probabilities */ +export function getPreloadPaths( snapshotResult: SnapshotResult | null, opts: RenderToStringOptions, resolvedManifest: ResolvedManifest | undefined -): PrefetchResource[] { - if (!resolvedManifest) { +): string[] { + const prefetchStrategy = opts.prefetchStrategy; + if (prefetchStrategy === null) { return []; } - const prefetchStrategy = opts.prefetchStrategy; - const buildBase = getBuildBase(opts); - - if (prefetchStrategy !== null) { - // do nothing if opts.prefetchStrategy is explicitly set to null - - if ( - !prefetchStrategy || - !prefetchStrategy.symbolsToPrefetch || - prefetchStrategy.symbolsToPrefetch === 'auto' - ) { - // DEFAULT 'events-document' - // if prefetchStrategy is undefined - // or prefetchStrategy.symbolsToPrefetch is undefined - // get event QRLs used in this document - return getAutoPrefetch(snapshotResult, resolvedManifest, buildBase); - } + if (!resolvedManifest?.manifest.bundleGraph) { + return getBundles(snapshotResult); + } - if (typeof prefetchStrategy.symbolsToPrefetch === 'function') { - // call user option symbolsToPrefetch() - try { - return prefetchStrategy.symbolsToPrefetch({ manifest: resolvedManifest.manifest }); - } catch (e) { - console.error('getPrefetchUrls, symbolsToPrefetch()', e); - } + // TODO should we deprecate this? + if (typeof prefetchStrategy?.symbolsToPrefetch === 'function') { + // call user option symbolsToPrefetch() + try { + const prefetchResources = prefetchStrategy.symbolsToPrefetch({ + manifest: resolvedManifest.manifest, + }); + return flattenPrefetchResources(prefetchResources); + } catch (e) { + console.error('getPrefetchUrls, symbolsToPrefetch()', e); } } - // no urls to prefetch - return []; -} -function getAutoPrefetch( - snapshotResult: SnapshotResult | null, - resolvedManifest: ResolvedManifest, - buildBase: string -) { - const prefetchResources: PrefetchResource[] = []; - const qrls = snapshotResult?.qrls; - const { mapper, manifest } = resolvedManifest; - const urls = new Map(); + // If we have a bundle graph, all we need is the symbols + return (snapshotResult?.qrls as QRLInternal[]) + ?.map((qrl) => getSymbolHash(qrl.$refSymbol$ || qrl.$symbol$)) + .filter(Boolean) as string[]; +} - if (Array.isArray(qrls)) { - for (const qrl of qrls) { - const qrlSymbolName = qrl.getHash(); - const resolvedSymbol = mapper[qrlSymbolName]; - if (resolvedSymbol) { - const bundleFileName = resolvedSymbol[1]; - addBundle(manifest, urls, prefetchResources, buildBase, bundleFileName, true); - } - } +export const expandBundles = (names: string[], resolvedManifest?: ResolvedManifest) => { + if (!resolvedManifest?.manifest.bundleGraph) { + return [...new Set(names)]; } - return prefetchResources; -} -function addBundle( - manifest: QwikManifest, - urls: Map, - prefetchResources: PrefetchResource[], - buildBase: string, - bundleFileName: string, - priority: boolean -) { - let prefetchResource = urls.get(bundleFileName); - if (!prefetchResource) { - prefetchResource = { - url: bundleFileName, - imports: [], - priority, - }; - urls.set(bundleFileName, prefetchResource); + resetQueue(); - const bundle = manifest.bundles[bundleFileName]; - if (bundle) { - if (Array.isArray(bundle.imports)) { - for (const importedFilename of bundle.imports) { - addBundle( - manifest, - urls, - prefetchResource.imports, - buildBase, - importedFilename, - priority - ); - } - } - if (Array.isArray(bundle.dynamicImports) && bundle.dynamicImports.length < 10) { - for (const importedFilename of bundle.dynamicImports) { - addBundle(manifest, urls, prefetchResource.imports, buildBase, importedFilename, false); - } - } - } + let probability = 0.8; + for (const name of names) { + preload(name, probability); + // later bundles have less probability + probability *= 0.95; } - prefetchResources.push(prefetchResource); -} -export const isQrl = (value: any): value is QRLInternal => { - return typeof value === 'function' && typeof value.getSymbol === 'function'; + return getQueue(); }; diff --git a/packages/qwik/src/server/prefetch-utils.ts b/packages/qwik/src/server/prefetch-utils.ts index 3ac94f21eae..f1c7b3d5137 100644 --- a/packages/qwik/src/server/prefetch-utils.ts +++ b/packages/qwik/src/server/prefetch-utils.ts @@ -1,39 +1,13 @@ import type { PrefetchResource } from './types'; -// TODO deprecation message and fallback to preload -export function workerFetchScript() { - const fetch = `Promise.all(e.data.map(u=>fetch(u))).finally(()=>{setTimeout(postMessage({}),9999)})`; - - const workerBody = `onmessage=(e)=>{${fetch}}`; - - const blob = `new Blob(['${workerBody}'],{type:"text/javascript"})`; - - const url = `URL.createObjectURL(${blob})`; - - let s = `const w=new Worker(${url});`; - - // `u` variable must somehow get within this closure - s += `w.postMessage(u.map(u=>new URL(u,origin)+''));`; - s += `w.onmessage=()=>{w.terminate()};`; - - return s; -} - -export function prefetchUrlsEventScript(base: string, prefetchResources: PrefetchResource[]) { - // TODO convert links to bundles - // see how preload() can be leveraged during - return ''; -} - export function flattenPrefetchResources(prefetchResources: PrefetchResource[]) { - const urls = new Map(); - const addPrefetchResource = (prefetchResources: PrefetchResource[]) => { - if (Array.isArray(prefetchResources)) { + const urls: string[] = []; + const addPrefetchResource = (prefetchResources?: PrefetchResource[]) => { + if (prefetchResources) { for (const prefetchResource of prefetchResources) { - const prev = urls.get(prefetchResource.url); - if (!prev) { - urls.set(prefetchResource.url, prefetchResource.priority); - if (prev === undefined) { + if (!urls.includes(prefetchResource.url)) { + urls.push(prefetchResource.url); + if (prefetchResource.imports) { addPrefetchResource(prefetchResource.imports); } } @@ -49,7 +23,7 @@ export function getMostReferenced(prefetchResources: PrefetchResource[]) { const common = new Map(); let total = 0; const addPrefetchResource = (prefetchResources: PrefetchResource[], visited: Set) => { - if (Array.isArray(prefetchResources)) { + if (prefetchResources) { for (const prefetchResource of prefetchResources) { const count = common.get(prefetchResource.url) || 0; common.set(prefetchResource.url, count + 1); diff --git a/packages/qwik/src/server/prefetch-utils.unit.ts b/packages/qwik/src/server/prefetch-utils.unit.ts index 0e697778a2f..95b4ef0ffd1 100644 --- a/packages/qwik/src/server/prefetch-utils.unit.ts +++ b/packages/qwik/src/server/prefetch-utils.unit.ts @@ -7,14 +7,7 @@ test('flattenPrefetchResources, no imports', () => { { url: 'b.js', imports: [], priority: true }, { url: 'c.js', imports: [], priority: false }, ]; - assert.deepEqual( - flattenPrefetchResources(p), - new Map([ - ['a.js', false], - ['b.js', true], - ['c.js', false], - ]) - ); + assert.deepEqual(flattenPrefetchResources(p), ['a.js', 'b.js', 'c.js']); }); test('flattenPrefetchResources, w/ imports', () => { @@ -53,15 +46,5 @@ test('flattenPrefetchResources, w/ imports', () => { priority: false, }, ]; - assert.deepEqual( - flattenPrefetchResources(p), - new Map([ - ['a.js', false], - ['x.js', false], - ['y.js', false], - ['b.js', true], - ['c.js', false], - ['z.js', false], - ]) - ); + assert.deepEqual(flattenPrefetchResources(p), ['a.js', 'x.js', 'y.js', 'b.js', 'c.js', 'z.js']); }); diff --git a/packages/qwik/src/server/preloading.md b/packages/qwik/src/server/preloading.md index fe8ae0f87a0..868791367f4 100644 --- a/packages/qwik/src/server/preloading.md +++ b/packages/qwik/src/server/preloading.md @@ -1,7 +1,5 @@ # Preloading -(this is wip, need to explain how bundlegraph stores score modifiers and how they are used) - When a user clicks a button, we want the code to be already there. When they navigate to a new page, we want this to be as fast as possible. @@ -33,25 +31,56 @@ To know which code is most likely to be needed, we can use the bundle scoring sy We will err on the side of caution, so if we are not sure, we will preload the bundle. This is better than making the user wait. In the end, all of the code that might be needed for the current page should be preloaded. However, we should make sure that bundles that are more likely to be needed are preloaded first. +However, on low-end devices, we may decide to not preload low-likelihood bundles. + ### Bundle scoring system +Ideally, we have a function that gets the current DOM, the browser position and the user interaction history and returns the likelihoods of the bundles. Perhaps one day we can use a neural network to do this, but for now we use heuristics. + Each bundle gets some metrics that will be used to score it. The scoring should help us decide which bundles to preload first. +### Metrics + - Interactivity: How much impact to interactivity is there if the bundle is missing? - - score 0 to 5 + - score 0.0 to 1.0 + - this is determined at build time - a click handler is very interactive, but the Task that gets executed by the signal that changes might also be important - - qwik core is not interactive directly, so it gets 0. + - qwik core and other libraries are not interactive directly, so it gets 0. However, it will get higher probability via its importers. - Size: How much code is there to download? - - score: percentage of total js bundle size, including all static imports and their static imports etc + - score: number of seconds it takes to download at slow mobile speed, currently set at 200kbps, so 1 second is 200/8 = 25kB + - this is determined at build time - heavy bundles that are not interactive should not be preloaded at all, but very interactive bundles should be preloaded before others, so that smaller bundles can download in parallel. -- Likelihood of being needed - - score: chance % of being needed in the next 5 seconds - - this is a guess, based on the type of symbols in the bundle and the type of the bundle - - dependencies get the sum score of their importers - - Insights can be used to improve this guess, but this is only implemented for Link SPA preloading and bundle bundling. - - this also changes during execution: importing one bundle can increase the score of another. The extreme case is direct imports, they are then 100% sure to be needed. -## Available techniques +### Likelihood calculation + +We're interested in the probability of a bundle being needed in the next 5 seconds (0.0 to 1.0) + +- this is a runtime guess, based on the interactivity of the bundle, for handlers its location on the page, and any the already-loaded bundles that import it +- Insights can be used to improve this guess with real data, but this is only implemented for Link SPA preloading and bundle bundling. +- this also changes during execution: importing one bundle can increase the score of another. The extreme case is direct imports, they are then 100% sure to be needed. +- dependencies get adjustments when importers run. For example, if an importer bundle has a probability of `0.5` and it imports this bundle with a probability of `0.5`, the probability is `0.5 * 0.5 = 0.25`. If another bundle has a probability of `0.25` and imports this bundle with a probability of `0.75`, the probability is `0.25 * 0.75 = 0.1875`. When both importers are present, the total probability is `1 - (1 - 0.5) * (1 - 0.25) = 0.6875`. + +Generally speaking, the probability of a bundle loading because of importers is `1 - (1 - p1) * (1 - p2) * ...` where `p1`, `p2`, ... are the probabilities of each importer not importing this bundle, which is the product of the probabilities of each importer not importing this bundle). Furthermore we have the base probability of the bundle. That can be seen as an importer with a probability of `1`. + +So, for each bundle we keep track of the inverse probability. We start with the 1 - the base probability, and when an importer runs, we multiply with its inverse probability. + +The likelihood score is then derived as follows: + +- `score = 0.5 * probability + 0.5 * interactivity` + +This means that a bundle that is both interactive and has a high probability will get a high score, but a bundle that is only interactive or only likely will get a lower score. + +### SSR probability + +During SSR, we discover the QRL segments that are used in the result, both from event handlers and from state (e.g. computed signals). We then use this to calculate the probability of their bundle being needed. + +For event handlers, we can use the type of event for interactivity scoring as well as probability of the handler being called. Visible tasks and computed signal QRLs will have a high probability, onClick handlers will have a medium probability but high interactivity, etc. + +We can then generate a list of bundles in their order of likelihood. We provide this list to the preloader, and anything with a score > 0.8 will get an initial preload tag. + +This will e.g. automatically add the bundle containing Qwik Core as a tag to the result. + +## Available techniques in the browser Note, currently we are only interested in preloading code, not assets. Furthermore, we only target browsers that support ES bundles. @@ -81,11 +110,26 @@ It is a possible workaround for when devices don't support bundle preloading. We ### Bundle graph -Ideally, we have a function that gets the current DOM, the browser position and the user interaction history and returns the likelihoods of the bundles. Perhaps one day we can use a neural network to do this, but for now we use simple heuristics. +For each bundle, we have a list of probabilities for other bundles. We encode these as a list of numbers and call it the **bundle graph**: a positive number is the index of the dependency, and a negative number is the probability times `-10` and applies to the bundles following it. For example, `['foo', 'bar', 'myBundle', 0, -6, 1]` means that the bundles `foo` and `bar` don't import anything, and importing `myBundle` gives a 100% chance to import `foo` (meaning it's a direct import) and a 60% chance to import `bar`. + +This is made at build time. For dynamic imports, we have to guess the probability of the bundle if it is not provided by Insights. + +A bundle has many origins. The probability is the highest of the probabilities of the origins. + +Some examples of factors influencing the probability: + +- a `component$` will be 100% likely to import its own hook qrls +- ... -For each bundle, we have a list of scoring modifiers for other bundles. These are numbers that are added to the score of the bundle. +### Runtime probability -We encode the scores in a "bundle graph". This is a compact representation of the known bundles and how they influence the likelihood of other bundles. +All bundles start with no probability, meaning their inverse probability `iP` is `1`. + +Suppose we think a bundle has a 60% chance of being needed. This updates its `iP` to `0.4` and all its imports get their `iP` multiplied by `(1 - 0.6 * probability)`. + +When the bundle gets imported, this changes to 100%, `iP = 0`. This again updates it and all its imports. However, the imports were already adjusted, so the `iP` of the imports is now multiplied by `(1 - 1 * probability)/(1 - 0.6 * probability)`. + +In other words, when you change the `iP` of a bundle, you have to adjust the `iP` of all its imports by `( 1 - (1 - iP)*probability)/(1 - (1 - prevIP)*probability)`. ### SSR @@ -95,12 +139,19 @@ Then, we inject a script tag that imports the preloader and passes the list of l Note that the preloader is a small bundle in a separate bundle that is also imported by Qwik itself, so its state is available instantly. Qwik can then request preloading for QRL segments etc. We tell the bundler to make sure to keep the preloader in a separate bundle. -Once the preloader is loaded, it will start downloading the bundles in the background in order of their score. It also downloads the bundle graph so to have a complete picture. +Once the preloader is loaded, it will start downloading the bundles in the background in order of their score. It also downloads the bundle graph as soon as possible. ### QRL preloading -When a QRL is created, we tell the preloader about the symbol with low priority. +When a QRL is created, we tell the preloader about the symbol having a 60% chance. +When a it is called, we change the probability to 100%. ### Link preloading -When a Link is visible, we can preload the likely bundles of the target page. The scoring could also use the popularity of target page, but this is not implemented yet. +When a Link is visible and preload is requested, we preload its route destination. The Qwik Router and Insights plugins will have added these to the bundle graph. The scoring could also use the popularity of target page, but this is not implemented yet. + +### Actually preloading + +When a bundle's `iP` is below `1`, we put it in a sorted set. Our first naive implementation will just sort the array of desired bundles by increasing `iP`. + +The lower the `iP`, the more urgent the preload is. For bundles with `iP < 0.05`, we preload all. For the rest, we take the number of active preloads. If `preloadCount < 2 + 20 * (1 - iP)`, we preload it. diff --git a/packages/qwik/src/server/render.ts b/packages/qwik/src/server/render.ts index d93d8c8994a..a3c1d0937a8 100644 --- a/packages/qwik/src/server/render.ts +++ b/packages/qwik/src/server/render.ts @@ -3,11 +3,10 @@ import { _pauseFromContexts, _renderSSR, Fragment, jsx, type JSXNode } from '@bu import { isDev } from '@builder.io/qwik'; import type { QContext } from '../core/state/context'; import { QInstance } from '../core/util/markers'; -import { getValidManifest } from '../optimizer/src/manifest'; import type { ResolvedManifest, SymbolMapper } from '../optimizer/src/types'; import { getSymbolHash, setServerPlatform } from './platform'; -import { applyPrefetchImplementation } from './prefetch-implementation'; -import { getPrefetchResources } from './prefetch-strategy'; +import { includePreloader } from './prefetch-implementation'; +import { getPreloadPaths } from './prefetch-strategy'; import { getQwikLoaderScript } from './scripts'; import type { QwikManifest, @@ -18,6 +17,8 @@ import type { StreamWriter, } from './types'; import { createTimer, getBuildBase } from './utils'; +import { manifest as builtManifest } from '@qwik-client-manifest'; +import { initPreloader } from '../core/preloader/bundle-graph'; const DOCTYPE = ''; @@ -119,12 +120,16 @@ export async function renderToStream( } } - if (!opts.manifest) { + if (!resolvedManifest) { console.warn( `Missing client manifest, loading symbols in the client might 404. Please ensure the client build has run and generated the manifest for the server build.` ); } await setServerPlatform(opts, resolvedManifest); + const bundleGraph = resolvedManifest?.manifest.bundleGraph; + if (bundleGraph) { + initPreloader(bundleGraph); + } const injections = resolvedManifest?.manifest.injections; const beforeContent = injections @@ -172,14 +177,14 @@ export async function renderToStream( const children: (JSXNode | null)[] = []; if (opts.prefetchStrategy !== null) { // skip prefetch implementation if prefetchStrategy === null - const prefetchResources = getPrefetchResources(snapshotResult, opts, resolvedManifest); + const preloadBundles = getPreloadPaths(snapshotResult, opts, resolvedManifest); const base = containerAttributes['q:base']!; - if (prefetchResources.length > 0) { - const prefetchImpl = applyPrefetchImplementation( + if (preloadBundles.length > 0) { + const prefetchImpl = includePreloader( base, - resolvedManifest?.manifest, + resolvedManifest, opts.prefetchStrategy, - prefetchResources, + preloadBundles, opts.serverData?.nonce ); if (prefetchImpl) { @@ -263,7 +268,6 @@ export async function renderToStream( snapshot: snapshotTime, firstFlush: firstFlushTime, }, - _symbols: renderSymbols, }; return result; } @@ -311,25 +315,30 @@ export async function renderToString( }; } -/** @public */ +/** + * Merges a given manifest with the built manifest and provides mappings for symbols. + * + * @public + */ export function resolveManifest( - manifest: QwikManifest | ResolvedManifest | undefined + manifest?: Partial | undefined ): ResolvedManifest | undefined { - if (!manifest) { - return undefined; - } - if ('mapper' in manifest) { - return manifest; + const mergedManifest = (manifest ? { ...builtManifest, ...manifest } : builtManifest) as + | ResolvedManifest + | QwikManifest; + + if (!mergedManifest || 'mapper' in mergedManifest) { + return mergedManifest; } - manifest = getValidManifest(manifest); - if (manifest) { + if (mergedManifest!.mapping) { const mapper: SymbolMapper = {}; - Object.entries(manifest.mapping).forEach(([symbol, bundleFilename]) => { + Object.entries(mergedManifest.mapping).forEach(([symbol, bundleFilename]) => { mapper[getSymbolHash(symbol)] = [symbol, bundleFilename]; }); return { mapper, - manifest, + manifest: mergedManifest, + injections: mergedManifest.injections || [], }; } return undefined; diff --git a/packages/qwik/src/server/server-modules.d.ts b/packages/qwik/src/server/server-modules.d.ts index 733ebdc2882..081815adccc 100644 --- a/packages/qwik/src/server/server-modules.d.ts +++ b/packages/qwik/src/server/server-modules.d.ts @@ -1,5 +1,5 @@ declare module '@qwik-client-manifest' { - const manifest: import('.').QwikManifest; + const manifest: import('.').ServerQwikManifest; export { manifest }; } // MD diff --git a/packages/qwik/src/server/types.ts b/packages/qwik/src/server/types.ts index 61e9ae2449b..8cb16af2bf8 100644 --- a/packages/qwik/src/server/types.ts +++ b/packages/qwik/src/server/types.ts @@ -1,14 +1,14 @@ import type { SnapshotResult, StreamWriter } from '@builder.io/qwik'; import type { QwikManifest, - SymbolMapperFn, - SymbolMapper, ResolvedManifest, + SymbolMapper, + SymbolMapperFn, } from '@builder.io/qwik/optimizer'; /** @public */ export interface SerializeDocumentOptions { - manifest?: QwikManifest | ResolvedManifest; + manifest?: Partial; symbolMapper?: SymbolMapperFn; debug?: boolean; } @@ -22,43 +22,52 @@ export interface PrefetchStrategy { /** @public */ export interface PrefetchImplementation { /** - * `js-append`: Use JS runtime to create each `` and append to the head. - * - * `html-append`: Render each `` within html, appended at the end of the body. + * Maximum number of preload links to add during SSR. These instruct the browser to preload likely + * bundles before the preloader script is active. This includes the 2 preloads used for the + * preloader script itself and the bundle information. Setting this to 0 will disable all preload + * links. * - * Defaults to `js-append`. + * Defaults to `5` */ - linkInsert?: 'js-append' | 'html-append' | null; - /** Value of the `` attribute when link is used. Defaults to `modulepreload`. */ - linkRel?: 'prefetch' | 'preload' | 'modulepreload' | null; - /** Value of the `` attribute when link is used. Defaults to `null`. */ - linkFetchPriority?: 'auto' | 'low' | 'high' | null; + maxPreloads?: number; /** - * `always`: Always include the worker fetch JS runtime. - * - * `no-link-support`: Only include the worker fetch JS runtime when the browser doesn't support - * `` prefetch/preload/modulepreload. + * The minimum probability of a bundle to be added as a preload link during SSR. * - * Defaults to `null`. + * Defaults to `0.6` (60% probability) + */ + minProbability?: number; + /** + * If true, the preloader will log debug information to the console. * - * @deprecated Use `linkInsert` instead + * Defaults to `false` */ - workerFetchInsert?: 'always' | 'no-link-support' | null; + debug?: boolean; /** - * Dispatch a `qprefetch` event with detail data containing the bundles that should be prefetched. - * The event dispatch script will be inlined into the document's HTML so any listeners of this - * event should already be ready to handle the event. + * Maximum number of simultaneous preload links that the preloader will maintain. * - * This implementation will inject a script similar to: + * Defaults to `5` + */ + maxSimultaneousPreloads?: number; + /** + * The minimum probability for a bundle to be added to the preload queue. * - * ``` - * - * ``` + * Defaults to `0.25` (25% probability) + */ + minPreloadProbability?: number; + /** + * Value of the `` attribute when links are added. The preloader itself will + * autodetect which attribute to use based on the browser capabilities. * - * By default, the `prefetchEvent` implementation will be set to `null`. + * Defaults to `modulepreload`. */ + linkRel?: 'prefetch' | 'preload' | 'modulepreload' | null; + /** Value of the `` attribute when links are added. Defaults to `null`. */ + linkFetchPriority?: 'auto' | 'low' | 'high' | null; + /** @deprecated No longer used. */ + linkInsert?: 'js-append' | 'html-append' | null; + /** @deprecated No longer used. */ + workerFetchInsert?: 'always' | 'no-link-support' | null; + /** @deprecated No longer used. */ prefetchEvent?: 'always' | null; } @@ -73,7 +82,6 @@ export type SymbolsToPrefetch = 'auto' | ((opts: { manifest: QwikManifest }) => export interface PrefetchResource { url: string; imports: PrefetchResource[]; - priority: boolean; } /** @public */ @@ -102,8 +110,6 @@ export interface RenderResult { snapshotResult: SnapshotResult | undefined; isStatic: boolean; manifest?: QwikManifest; - /** @internal TODO: Move to snapshotResult */ - _symbols?: string[]; } /** @public */ @@ -201,4 +207,4 @@ export type RenderToStream = (opts: RenderToStreamOptions) => Promise 'preloader.mjs', + }, + rollupOptions: { + external: ['@builder.io/qwik/build'], + }, + minify: 'terser', + terserOptions: { + compress: { + dead_code: true, + unused: true, + conditionals: true, }, - async transform(code, id) { - const result = await transform(code, { - sourcefile: id, - target: 'es2017', - format: 'esm', - loader: 'ts', - }); - - // Rename $properties$ to short names but leave the rest legible - // The final app will minify it when needed - - const minified = await minify(result.code, { - compress: { - // Trying to eliminate the enum declaration and failing - dead_code: true, - unused: true, - conditionals: true, - }, - mangle: { - toplevel: false, - module: false, - keep_fnames: true, - properties: { - regex: '^\\$.+\\$$', - }, - }, - }); - return minified.code; + mangle: { + toplevel: false, + module: false, + keep_fnames: true, + properties: { + regex: '^\\$.+\\$$', + }, }, }, - ], - }; - - const output: OutputOptions = { - dir: config.distQwikPkgDir, - format: 'es', - entryFileNames: `preloader.mjs`, - exports: 'named', - }; - - const build = await rollup(input); - - await build.write(output); + outDir: config.distQwikPkgDir, + }, + }); const preloaderSize = await fileSize(join(config.distQwikPkgDir, 'preloader.mjs')); console.log(`🐮 preloader:`, preloaderSize); diff --git a/scripts/submodule-server.ts b/scripts/submodule-server.ts index e0b1828822f..e97d28da0e7 100644 --- a/scripts/submodule-server.ts +++ b/scripts/submodule-server.ts @@ -27,6 +27,8 @@ export async function submoduleServer(config: BuildConfig) { external: [ /* no Node.js built-in externals allowed! */ '@builder.io/qwik-dom', '@builder.io/qwik/build', + '@builder.io/qwik/preloader', + '@qwik-client-manifest', ], }; @@ -69,6 +71,11 @@ export async function submoduleServer(config: BuildConfig) { 'globalThis.IS_ESM': 'false', 'globalThis.QWIK_VERSION': JSON.stringify(config.distVersion), 'globalThis.QWIK_DOM_VERSION': JSON.stringify(qwikDomVersion), + // We need to get rid of the import.meta.env values + // Vite's base url + 'import.meta.env.BASE_URL': '"globalThis.BASE_URL||\'/\'"', + // Vite's devserver mode + 'import.meta.env.DEV': 'false', }, }); @@ -129,6 +136,9 @@ if (typeof require !== 'function' && typeof location !== 'undefined' && typeof n } return self.qwikBuild; } + if (path === '@qwik-client-manifest') { + return {}; + } throw new Error('Unable to require() path "' + path + '" from a browser environment.'); }; }`; diff --git a/starters/adapters/aws-lambda/src/entry_aws-lambda.tsx b/starters/adapters/aws-lambda/src/entry_aws-lambda.tsx index c224994f42a..5bb7a06b51f 100755 --- a/starters/adapters/aws-lambda/src/entry_aws-lambda.tsx +++ b/starters/adapters/aws-lambda/src/entry_aws-lambda.tsx @@ -14,14 +14,13 @@ import { type PlatformAwsLambda, } from "@builder.io/qwik-city/middleware/aws-lambda"; import qwikCityPlan from "@qwik-city-plan"; -import { manifest } from "@qwik-client-manifest"; import render from "./entry.ssr"; declare global { interface QwikCityPlatform extends PlatformAwsLambda {} } -export const { handle } = createQwikCity({ render, qwikCityPlan, manifest }); +export const { handle } = createQwikCity({ render, qwikCityPlan }); export const qwikApp = serverless({ handle }, { binary: true }); // handler is the default export for the lambda functions diff --git a/starters/adapters/azure-swa/src/entry.azure-swa.tsx b/starters/adapters/azure-swa/src/entry.azure-swa.tsx index 24845399608..96772095ea0 100644 --- a/starters/adapters/azure-swa/src/entry.azure-swa.tsx +++ b/starters/adapters/azure-swa/src/entry.azure-swa.tsx @@ -12,11 +12,10 @@ import { type PlatformAzure, } from "@builder.io/qwik-city/middleware/azure-swa"; import qwikCityPlan from "@qwik-city-plan"; -import { manifest } from "@qwik-client-manifest"; import render from "./entry.ssr"; declare global { interface QwikCityPlatform extends PlatformAzure {} } -export default createQwikCity({ render, qwikCityPlan, manifest }); +export default createQwikCity({ render, qwikCityPlan }); diff --git a/starters/adapters/bun/src/entry.bun.ts b/starters/adapters/bun/src/entry.bun.ts index 48859260f24..c9fdfb512dd 100644 --- a/starters/adapters/bun/src/entry.bun.ts +++ b/starters/adapters/bun/src/entry.bun.ts @@ -10,14 +10,12 @@ */ import { createQwikCity } from "@builder.io/qwik-city/middleware/bun"; import qwikCityPlan from "@qwik-city-plan"; -import { manifest } from "@qwik-client-manifest"; import render from "./entry.ssr"; // Create the Qwik City Bun middleware const { router, notFound, staticFile } = createQwikCity({ render, qwikCityPlan, - manifest, }); // Allow for dynamic port diff --git a/starters/adapters/cloud-run/src/entry.cloud-run.tsx b/starters/adapters/cloud-run/src/entry.cloud-run.tsx index 4c0437f133c..ea46c1f591a 100644 --- a/starters/adapters/cloud-run/src/entry.cloud-run.tsx +++ b/starters/adapters/cloud-run/src/entry.cloud-run.tsx @@ -12,7 +12,6 @@ import { type PlatformNode, } from "@builder.io/qwik-city/middleware/node"; import qwikCityPlan from "@qwik-city-plan"; -import { manifest } from "@qwik-client-manifest"; import { createServer } from "node:http"; import render from "./entry.ssr"; @@ -53,7 +52,6 @@ const DEFAULT_HEADERS = { const { router, notFound, staticFile } = createQwikCity({ render, qwikCityPlan, - manifest, static: { cacheControl: "public, max-age=31557600", }, diff --git a/starters/adapters/cloudflare-pages/src/entry.cloudflare-pages.tsx b/starters/adapters/cloudflare-pages/src/entry.cloudflare-pages.tsx index 510a6309229..0f3b524f489 100644 --- a/starters/adapters/cloudflare-pages/src/entry.cloudflare-pages.tsx +++ b/starters/adapters/cloudflare-pages/src/entry.cloudflare-pages.tsx @@ -12,13 +12,12 @@ import { type PlatformCloudflarePages, } from "@builder.io/qwik-city/middleware/cloudflare-pages"; import qwikCityPlan from "@qwik-city-plan"; -import { manifest } from "@qwik-client-manifest"; import render from "./entry.ssr"; declare global { interface QwikCityPlatform extends PlatformCloudflarePages {} } -const fetch = createQwikCity({ render, qwikCityPlan, manifest }); +const fetch = createQwikCity({ render, qwikCityPlan }); export { fetch }; diff --git a/starters/adapters/deno/src/entry.deno.ts b/starters/adapters/deno/src/entry.deno.ts index 96a4d5d2331..533145fae5b 100644 --- a/starters/adapters/deno/src/entry.deno.ts +++ b/starters/adapters/deno/src/entry.deno.ts @@ -10,14 +10,12 @@ */ import { createQwikCity } from "@builder.io/qwik-city/middleware/deno"; import qwikCityPlan from "@qwik-city-plan"; -import { manifest } from "@qwik-client-manifest"; import render from "./entry.ssr"; // Create the Qwik City Deno middleware const { router, notFound, staticFile } = createQwikCity({ render, qwikCityPlan, - manifest, }); // Allow for dynamic port diff --git a/starters/adapters/express/src/entry.express.tsx b/starters/adapters/express/src/entry.express.tsx index adce1df37d8..4fe1c3a3732 100644 --- a/starters/adapters/express/src/entry.express.tsx +++ b/starters/adapters/express/src/entry.express.tsx @@ -13,7 +13,6 @@ import { } from "@builder.io/qwik-city/middleware/node"; import "dotenv/config"; import qwikCityPlan from "@qwik-city-plan"; -import { manifest } from "@qwik-client-manifest"; import render from "./entry.ssr"; import express from "express"; import { fileURLToPath } from "node:url"; @@ -34,7 +33,6 @@ const PORT = process.env.PORT ?? 3000; const { router, notFound } = createQwikCity({ render, qwikCityPlan, - manifest, // getOrigin(req) { // // If deploying under a proxy, you may need to build the origin from the request headers // // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto diff --git a/starters/adapters/firebase/src/entry-firebase.tsx b/starters/adapters/firebase/src/entry-firebase.tsx index 5b76d90f337..5ceb6ad2731 100755 --- a/starters/adapters/firebase/src/entry-firebase.tsx +++ b/starters/adapters/firebase/src/entry-firebase.tsx @@ -12,11 +12,10 @@ import { type PlatformFirebase, } from "@builder.io/qwik-city/middleware/firebase"; import qwikCityPlan from "@qwik-city-plan"; -import { manifest } from "@qwik-client-manifest"; import render from "./entry.ssr"; declare global { interface QwikCityPlatform extends PlatformFirebase {} } -export default createQwikCity({ render, qwikCityPlan, manifest }); +export default createQwikCity({ render, qwikCityPlan }); diff --git a/starters/adapters/netlify-edge/src/entry.netlify-edge.tsx b/starters/adapters/netlify-edge/src/entry.netlify-edge.tsx index 0f1d56b983e..1c2cb9e3aaf 100644 --- a/starters/adapters/netlify-edge/src/entry.netlify-edge.tsx +++ b/starters/adapters/netlify-edge/src/entry.netlify-edge.tsx @@ -12,11 +12,10 @@ import { type PlatformNetlify, } from "@builder.io/qwik-city/middleware/netlify-edge"; import qwikCityPlan from "@qwik-city-plan"; -import { manifest } from "@qwik-client-manifest"; import render from "./entry.ssr"; declare global { interface QwikCityPlatform extends PlatformNetlify {} } -export default createQwikCity({ render, qwikCityPlan, manifest }); +export default createQwikCity({ render, qwikCityPlan }); diff --git a/starters/adapters/node-server/src/entry.node-server.tsx b/starters/adapters/node-server/src/entry.node-server.tsx index 2117664097c..2f9ac8cbe9d 100644 --- a/starters/adapters/node-server/src/entry.node-server.tsx +++ b/starters/adapters/node-server/src/entry.node-server.tsx @@ -10,7 +10,6 @@ import { createQwikCity } from "@builder.io/qwik-city/middleware/node"; import qwikCityPlan from "@qwik-city-plan"; import render from "./entry.ssr"; -import { manifest } from "@qwik-client-manifest"; import { createServer } from "node:http"; // Allow for dynamic port @@ -20,7 +19,6 @@ const PORT = process.env.PORT ?? 3004; const { router, notFound, staticFile } = createQwikCity({ render, qwikCityPlan, - manifest, }); const server = createServer(); diff --git a/starters/adapters/vercel-edge/src/entry.vercel-edge.tsx b/starters/adapters/vercel-edge/src/entry.vercel-edge.tsx index e96dcabc4d0..564ec04f4da 100644 --- a/starters/adapters/vercel-edge/src/entry.vercel-edge.tsx +++ b/starters/adapters/vercel-edge/src/entry.vercel-edge.tsx @@ -12,11 +12,10 @@ import { type PlatformVercel, } from "@builder.io/qwik-city/middleware/vercel-edge"; import qwikCityPlan from "@qwik-city-plan"; -import { manifest } from "@qwik-client-manifest"; import render from "./entry.ssr"; declare global { interface QwikCityPlatform extends PlatformVercel {} } -export default createQwikCity({ render, qwikCityPlan, manifest }); +export default createQwikCity({ render, qwikCityPlan }); diff --git a/starters/apps/base/src/entry.ssr.tsx b/starters/apps/base/src/entry.ssr.tsx index e3de501302b..53210851238 100644 --- a/starters/apps/base/src/entry.ssr.tsx +++ b/starters/apps/base/src/entry.ssr.tsx @@ -14,12 +14,10 @@ import { renderToStream, type RenderToStreamOptions, } from "@builder.io/qwik/server"; -import { manifest } from "@qwik-client-manifest"; import Root from "./root"; export default function (opts: RenderToStreamOptions) { return renderToStream(, { - manifest, ...opts, // Use container attributes to set attributes on the html tag. containerAttributes: { diff --git a/starters/apps/library/src/entry.ssr.tsx b/starters/apps/library/src/entry.ssr.tsx index 3e6b49f7f25..4e13a960f62 100644 --- a/starters/apps/library/src/entry.ssr.tsx +++ b/starters/apps/library/src/entry.ssr.tsx @@ -14,12 +14,8 @@ import { renderToStream, type RenderToStreamOptions, } from "@builder.io/qwik/server"; -import { manifest } from "@qwik-client-manifest"; import Root from "./root"; export default function (opts: RenderToStreamOptions) { - return renderToStream(, { - manifest, - ...opts, - }); + return renderToStream(, opts); } diff --git a/starters/apps/perf.prod/src/entry.ssr.tsx b/starters/apps/perf.prod/src/entry.ssr.tsx index 157aef038bf..ff7cd0aa4db 100644 --- a/starters/apps/perf.prod/src/entry.ssr.tsx +++ b/starters/apps/perf.prod/src/entry.ssr.tsx @@ -1,5 +1,4 @@ import { renderToStream, RenderToStreamOptions } from "@builder.io/qwik/server"; -import { manifest } from "@qwik-client-manifest"; import Root from "./root"; /** @@ -8,8 +7,5 @@ import Root from "./root"; export default function (opts: RenderToStreamOptions) { // Render the Root component to a string // Pass in the manifest that was generated from the client build - return renderToStream(, { - manifest, - ...opts, - }); + return renderToStream(, opts); } diff --git a/starters/apps/preloader-test/src/entry.ssr.tsx b/starters/apps/preloader-test/src/entry.ssr.tsx index e79c92c6ca6..a352452f078 100644 --- a/starters/apps/preloader-test/src/entry.ssr.tsx +++ b/starters/apps/preloader-test/src/entry.ssr.tsx @@ -14,16 +14,16 @@ import { renderToStream, type RenderToStreamOptions, } from "@builder.io/qwik/server"; -import { manifest } from "@qwik-client-manifest"; import Root from "./root"; export default function (opts: RenderToStreamOptions) { return renderToStream(, { - manifest, prefetchStrategy: { implementation: { - linkInsert: "js-append", - // linkInsert: "html-append", + debug: true, + minProbability: 0.5, + maxSimultaneousPreloads: 10, + minPreloadProbability: 0.25, }, }, ...opts, diff --git a/starters/apps/qwikcity-test.prod/src/entry.ssr.tsx b/starters/apps/qwikcity-test.prod/src/entry.ssr.tsx index 0678d23f348..ed9135193e1 100644 --- a/starters/apps/qwikcity-test.prod/src/entry.ssr.tsx +++ b/starters/apps/qwikcity-test.prod/src/entry.ssr.tsx @@ -2,12 +2,10 @@ import { renderToStream, type RenderToStreamOptions, } from "@builder.io/qwik/server"; -import { manifest } from "@qwik-client-manifest"; import Root from "./root"; export default function (opts: RenderToStreamOptions) { return renderToStream(, { - manifest, base: "/qwikcity-test.prod/build/", ...opts, }); diff --git a/starters/apps/qwikcity-test/src/entry.ssr.tsx b/starters/apps/qwikcity-test/src/entry.ssr.tsx index 16f3ba6d857..0d261d15df6 100644 --- a/starters/apps/qwikcity-test/src/entry.ssr.tsx +++ b/starters/apps/qwikcity-test/src/entry.ssr.tsx @@ -2,12 +2,10 @@ import { renderToStream, type RenderToStreamOptions, } from "@builder.io/qwik/server"; -import { manifest } from "@qwik-client-manifest"; import Root from "./root"; export default function (opts: RenderToStreamOptions) { return renderToStream(, { - manifest, base: "/qwikcity-test/build/", ...opts, }); diff --git a/starters/apps/starter-partytown-test/src/entry.ssr.tsx b/starters/apps/starter-partytown-test/src/entry.ssr.tsx index 341fb95149c..66be20a3716 100644 --- a/starters/apps/starter-partytown-test/src/entry.ssr.tsx +++ b/starters/apps/starter-partytown-test/src/entry.ssr.tsx @@ -1,13 +1,9 @@ import { renderToStream, RenderOptions } from "@builder.io/qwik/server"; -import { manifest } from "@qwik-client-manifest"; import Root from "./root"; /** * Qwik server-side render function. */ export default function (opts: RenderOptions) { - return renderToStream(, { - manifest, - ...opts, - }); + return renderToStream(, opts); } diff --git a/starters/apps/todo-test/src/entry.ssr.tsx b/starters/apps/todo-test/src/entry.ssr.tsx index e4cc90b3468..33d548523ef 100644 --- a/starters/apps/todo-test/src/entry.ssr.tsx +++ b/starters/apps/todo-test/src/entry.ssr.tsx @@ -2,7 +2,6 @@ import { renderToStream, type RenderToStreamOptions, } from "@builder.io/qwik/server"; -import { manifest } from "@qwik-client-manifest"; import { Root } from "./root"; /** @@ -11,8 +10,5 @@ import { Root } from "./root"; export default function (opts: RenderToStreamOptions) { // Render the Root component to a string // Pass in the manifest that was generated from the client build - return renderToStream(, { - manifest, - ...opts, - }); + return renderToStream(, opts); } diff --git a/starters/e2e/e2e.containers.spec.ts b/starters/e2e/e2e.containers.spec.ts index 01534d4a001..685d225dfcf 100644 --- a/starters/e2e/e2e.containers.spec.ts +++ b/starters/e2e/e2e.containers.spec.ts @@ -38,10 +38,8 @@ test.describe("container", () => { }); test("dynamic preload", async ({ page }) => { - const container = page.locator(".inline-container container"); - const hash = await container.getAttribute("q:manifest-hash"); - const bundleLink = page.locator(`link#qwik-bg-${hash}`).first(); - await expect(bundleLink).toHaveAttribute("href"); - // We don't have a way to check if other modules are preloaded, because the link goes away + const preloaderScript = page.locator(`script[q\\:type='link-js']`).first(); + await expect(preloaderScript).toBeDefined(); + // We don't have a way to check if modules are preloaded, because the links go away }); }); diff --git a/starters/e2e/e2e.signals.spec.ts b/starters/e2e/e2e.signals.spec.ts index 56fe1e5cff1..fec5f7b0f69 100644 --- a/starters/e2e/e2e.signals.spec.ts +++ b/starters/e2e/e2e.signals.spec.ts @@ -450,7 +450,8 @@ test.describe("signals", () => { await expect(result).toHaveAttribute("data-value", "collision"); }); - test("issue 4228", async ({ page }) => { + // This test is flaky, we have it working well in v2 + test.skip("issue 4228", async ({ page }) => { const buttonA = page.locator("#issue-4228-button-a"); const buttonB = page.locator("#issue-4228-button-b"); const buttonC = page.locator("#issue-4228-button-c"); diff --git a/starters/features/localize/src/entry.ssr.tsx b/starters/features/localize/src/entry.ssr.tsx index 9d1df383f39..206791b0011 100644 --- a/starters/features/localize/src/entry.ssr.tsx +++ b/starters/features/localize/src/entry.ssr.tsx @@ -14,13 +14,11 @@ import { renderToStream, type RenderToStreamOptions, } from "@builder.io/qwik/server"; -import { manifest } from "@qwik-client-manifest"; import Root from "./root"; import { extractBase } from "./routes/[locale]/i18n-utils"; export default function (opts: RenderToStreamOptions) { return renderToStream(, { - manifest, ...opts, base: extractBase, // determine the base URL for the client code // Use container attributes to set attributes on the html tag. From a3fa86b92778593b06ea5c1d44f4e39418c99aa5 Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Fri, 4 Apr 2025 17:47:13 +0200 Subject: [PATCH 17/17] feat(qwik cli): add service-worker --- starters/features/service-worker/package.json | 13 +++++++++++++ .../service-worker/src/routes/service-worker.ts | 14 ++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 starters/features/service-worker/package.json create mode 100644 starters/features/service-worker/src/routes/service-worker.ts diff --git a/starters/features/service-worker/package.json b/starters/features/service-worker/package.json new file mode 100644 index 00000000000..100939437b1 --- /dev/null +++ b/starters/features/service-worker/package.json @@ -0,0 +1,13 @@ +{ + "description": "Add a service worker to your app", + "__qwik__": { + "displayName": "Feature: Service Worker", + "priority": 10, + "docs": [], + "nextSteps": { + "lines": [ + "Now, make sure that you have `` in your `src/root.tsx`" + ] + } + } +} diff --git a/starters/features/service-worker/src/routes/service-worker.ts b/starters/features/service-worker/src/routes/service-worker.ts new file mode 100644 index 00000000000..76ec4177023 --- /dev/null +++ b/starters/features/service-worker/src/routes/service-worker.ts @@ -0,0 +1,14 @@ +/* + * WHAT IS THIS FILE? + * + * Any file called "service-worker" is automatically bundled and registered with Qwik Router, as long as you add `` in your `root.tsx`. + * + * Here you register the events that your service worker will handle. + * + * MDN has documentation at https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API + */ +export declare const self: ServiceWorkerGlobalScope; + +addEventListener("install", () => self.skipWaiting()); + +addEventListener("activate", () => self.clients.claim());