Skip to content

Commit 3f49ff2

Browse files
committed
perf(preloader): init asap
this doesn't seem to interfere with LCP because it's small and async, and it allows listening to qwikloader events sooner.
1 parent c7f23d1 commit 3f49ff2

File tree

6 files changed

+77
-60
lines changed

6 files changed

+77
-60
lines changed

packages/docs/src/routes/api/qwik-server/api.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@
166166
}
167167
],
168168
"kind": "Interface",
169-
"content": "```typescript\nexport interface RenderOptions extends SerializeDocumentOptions \n```\n**Extends:** [SerializeDocumentOptions](#serializedocumentoptions)\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\n[base?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring \\| ((options: [RenderOptions](#renderoptions)<!-- -->) =&gt; string)\n\n\n</td><td>\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</td></tr>\n<tr><td>\n\n[containerAttributes?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nRecord&lt;string, string&gt;\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\n[containerTagName?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\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</td></tr>\n<tr><td>\n\n[locale?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring \\| ((options: [RenderOptions](#renderoptions)<!-- -->) =&gt; string)\n\n\n</td><td>\n\n_(Optional)_ Language to use when rendering the document.\n\n\n</td></tr>\n<tr><td>\n\n[prefetchStrategy?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[PrefetchStrategy](#prefetchstrategy) \\| null\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\n[preloader?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[PreloaderOptions](#preloaderoptions) \\| boolean\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\n[qwikLoader?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[QwikLoaderOptions](#qwikloaderoptions)\n\n\n</td><td>\n\n_(Optional)_ Specifies if the Qwik Loader script is added to the document or not.\n\nDefaults to `{ include: true }`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\n[qwikPrefetchServiceWorker?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nQwikPrefetchServiceWorkerOptions\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\n[serverData?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nRecord&lt;string, any&gt;\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\n[snapshot?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_ Defaults to `true`\n\n\n</td></tr>\n</tbody></table>",
169+
"content": "```typescript\nexport interface RenderOptions extends SerializeDocumentOptions \n```\n**Extends:** [SerializeDocumentOptions](#serializedocumentoptions)\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\n[base?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring \\| ((options: [RenderOptions](#renderoptions)<!-- -->) =&gt; string)\n\n\n</td><td>\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</td></tr>\n<tr><td>\n\n[containerAttributes?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nRecord&lt;string, string&gt;\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\n[containerTagName?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\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</td></tr>\n<tr><td>\n\n[locale?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring \\| ((options: [RenderOptions](#renderoptions)<!-- -->) =&gt; string)\n\n\n</td><td>\n\n_(Optional)_ Language to use when rendering the document.\n\n\n</td></tr>\n<tr><td>\n\n[prefetchStrategy?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[PrefetchStrategy](#prefetchstrategy) \\| null\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\n[preloader?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[PreloaderOptions](#preloaderoptions) \\| false\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\n[qwikLoader?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[QwikLoaderOptions](#qwikloaderoptions)\n\n\n</td><td>\n\n_(Optional)_ Specifies if the Qwik Loader script is added to the document or not.\n\nDefaults to `{ include: true }`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\n[qwikPrefetchServiceWorker?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nQwikPrefetchServiceWorkerOptions\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\n[serverData?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nRecord&lt;string, any&gt;\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\n[snapshot?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_ Defaults to `true`\n\n\n</td></tr>\n</tbody></table>",
170170
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/server/types.ts",
171171
"mdFile": "qwik.renderoptions.md"
172172
},

packages/docs/src/routes/api/qwik-server/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ _(Optional)_
715715
716716
</td><td>
717717
718-
[PreloaderOptions](#preloaderoptions) \| boolean
718+
[PreloaderOptions](#preloaderoptions) \| false
719719
720720
</td><td>
721721

packages/qwik/src/server/preload-impl.ts

Lines changed: 72 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,73 @@ import type { PreloaderOptions, RenderToStreamOptions, SnapshotResult } from './
55
import { initPreloader } from '../core/preloader/bundle-graph';
66
import { getPreloadPaths } from './preload-strategy';
77

8+
export const preloaderPre = (
9+
base: string,
10+
resolvedManifest: ResolvedManifest | undefined,
11+
options: PreloaderOptions | false | undefined,
12+
beforeContent: JSXNode<string>[],
13+
nonce?: string
14+
) => {
15+
const preloadChunk = resolvedManifest?.manifest?.preloader;
16+
if (preloadChunk && options !== false) {
17+
// Initialize the SSR preloader
18+
const preloaderOpts: Parameters<typeof initPreloader>[1] =
19+
typeof options === 'object'
20+
? {
21+
debug: options.debug,
22+
preloadProbability: options.ssrPreloadProbability,
23+
}
24+
: undefined;
25+
initPreloader(resolvedManifest?.manifest.bundleGraph, preloaderOpts);
26+
27+
// Add the preloader script to the head
28+
const opts: string[] = [];
29+
if (options?.debug) {
30+
opts.push('d:1');
31+
}
32+
if (options?.maxIdlePreloads) {
33+
opts.push(`P:${options.maxIdlePreloads}`);
34+
}
35+
if (options?.preloadProbability) {
36+
opts.push(`Q:${options.preloadProbability}`);
37+
}
38+
const optsStr = opts.length ? `,{${opts.join(',')}}` : '';
39+
40+
const hash = resolvedManifest?.manifest.manifestHash;
41+
42+
const script =
43+
`let b=fetch("${base}q-bundle-graph-${hash}.json");` +
44+
`import("${base}${preloadChunk}").then(({l})=>` +
45+
`l(${JSON.stringify(base)},b${optsStr})` +
46+
`);`;
47+
48+
beforeContent.push(
49+
/**
50+
* We add modulepreloads even when the script is at the top because they already fire during
51+
* html download
52+
*/
53+
jsx('link', { rel: 'modulepreload', href: `${base}${preloadChunk}` }),
54+
jsx('link', {
55+
rel: 'preload',
56+
href: `${base}q-bundle-graph-${resolvedManifest?.manifest.manifestHash}.json`,
57+
as: 'fetch',
58+
crossorigin: 'anonymous',
59+
}),
60+
jsx('script', {
61+
type: 'module',
62+
async: true,
63+
dangerouslySetInnerHTML: script,
64+
nonce,
65+
})
66+
);
67+
68+
const core = resolvedManifest?.manifest.core;
69+
if (core) {
70+
beforeContent.push(jsx('link', { rel: 'modulepreload', href: `${base}${core}` }));
71+
}
72+
}
73+
};
74+
875
export const includePreloader = (
976
base: string,
1077
resolvedManifest: ResolvedManifest | undefined,
@@ -15,8 +82,9 @@ export const includePreloader = (
1582
if (referencedBundles.length === 0 || options === false) {
1683
return null;
1784
}
18-
const { ssrPreloads, ssrPreloadProbability, debug, maxIdlePreloads, preloadProbability } =
19-
normalizePreLoaderOptions(typeof options === 'boolean' ? undefined : options);
85+
const { ssrPreloads, ssrPreloadProbability } = normalizePreLoaderOptions(
86+
typeof options === 'boolean' ? undefined : options
87+
);
2088
let allowed = ssrPreloads;
2189

2290
const nodes: JSXNode[] = [];
@@ -68,28 +136,12 @@ export const includePreloader = (
68136
`document.head.appendChild(e)` +
69137
`});`
70138
: '';
71-
const opts: string[] = [];
72-
if (debug) {
73-
opts.push('d:1');
74-
}
75-
if (maxIdlePreloads) {
76-
opts.push(`P:${maxIdlePreloads}`);
77-
}
78-
if (preloadProbability) {
79-
opts.push(`Q:${preloadProbability}`);
80-
}
81-
const optsStr = opts.length ? `,{${opts.join(',')}}` : '';
82139
// We are super careful not to interfere with the page loading.
83140
const script =
84-
// First we wait for the onload event
85-
`let b=fetch("${base}q-bundle-graph-${manifestHash}.json");` +
86141
insertLinks +
142+
// First we wait for the onload event
87143
`window.addEventListener('load',f=>{` +
88-
`f=_=>{` +
89-
`import("${base}${preloadChunk}").then(({l,p})=>{` +
90-
`l(${JSON.stringify(base)},b${optsStr});` +
91-
`p(${JSON.stringify(referencedBundles)});` +
92-
`})};` +
144+
`f=_=>import("${base}${preloadChunk}").then(({p})=>p(${JSON.stringify(referencedBundles)}));` +
93145
// then we ask for idle callback
94146
`try{requestIdleCallback(f,{timeout:2000})}` +
95147
// some browsers don't support requestIdleCallback
@@ -121,41 +173,6 @@ export const includePreloader = (
121173
return null;
122174
};
123175

124-
export const preloaderPre = (
125-
base: string,
126-
resolvedManifest: ResolvedManifest | undefined,
127-
options: PreloaderOptions | boolean | undefined,
128-
beforeContent: JSXNode<string>[]
129-
) => {
130-
const preloadChunk = resolvedManifest?.manifest?.preloader;
131-
if (preloadChunk && options !== false) {
132-
const bundleGraph = resolvedManifest?.manifest.bundleGraph;
133-
if (bundleGraph) {
134-
const preloaderOpts: Parameters<typeof initPreloader>[1] =
135-
typeof options === 'object'
136-
? {
137-
debug: options.debug,
138-
preloadProbability: options.ssrPreloadProbability,
139-
}
140-
: undefined;
141-
initPreloader(bundleGraph, preloaderOpts);
142-
}
143-
const core = resolvedManifest?.manifest.core;
144-
beforeContent.push(
145-
jsx('link', { rel: 'modulepreload', href: `${base}${preloadChunk}` }),
146-
jsx('link', {
147-
rel: 'preload',
148-
href: `${base}q-bundle-graph-${resolvedManifest?.manifest.manifestHash}.json`,
149-
as: 'fetch',
150-
crossorigin: 'anonymous',
151-
})
152-
);
153-
if (core) {
154-
beforeContent.push(jsx('link', { rel: 'modulepreload', href: `${base}${core}` }));
155-
}
156-
}
157-
};
158-
159176
export const preloaderPost = (
160177
base: string,
161178
snapshotResult: SnapshotResult,

packages/qwik/src/server/qwik.server.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export interface RenderOptions extends SerializeDocumentOptions {
101101
// @deprecated (undocumented)
102102
prefetchStrategy?: PrefetchStrategy | null;
103103
// (undocumented)
104-
preloader?: PreloaderOptions | boolean;
104+
preloader?: PreloaderOptions | false;
105105
qwikLoader?: QwikLoaderOptions;
106106
// Warning: (ae-forgotten-export) The symbol "QwikPrefetchServiceWorkerOptions" needs to be exported by the entry point index.d.ts
107107
//

packages/qwik/src/server/render.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export async function renderToStream(
152152
})
153153
);
154154
}
155-
preloaderPre(buildBase, resolvedManifest, opts.preloader, beforeContent);
155+
preloaderPre(buildBase, resolvedManifest, opts.preloader, beforeContent, opts.serverData?.nonce);
156156

157157
const renderTimer = createTimer();
158158
const renderSymbols: string[] = [];

packages/qwik/src/server/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export interface RenderOptions extends SerializeDocumentOptions {
157157
*/
158158
qwikLoader?: QwikLoaderOptions;
159159

160-
preloader?: PreloaderOptions | boolean;
160+
preloader?: PreloaderOptions | false;
161161

162162
/** @deprecated Use `preloader` instead */
163163
qwikPrefetchServiceWorker?: QwikPrefetchServiceWorkerOptions;

0 commit comments

Comments
 (0)