Skip to content

Commit 5d5c54a

Browse files
committed
chore(preload): make js-append work & set default
1 parent 1c0df10 commit 5d5c54a

File tree

6 files changed

+69
-76
lines changed

6 files changed

+69
-76
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
}
8383
],
8484
"kind": "Interface",
85-
"content": "```typescript\nexport interface PrefetchImplementation \n```\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[linkFetchPriority?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n'auto' \\| 'low' \\| 'high' \\| null\n\n\n</td><td>\n\n_(Optional)_ Value of the `<link fetchpriority=\"...\">` attribute when link is used. Defaults to `null`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\n[linkInsert?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n'js-append' \\| 'html-append' \\| null\n\n\n</td><td>\n\n_(Optional)_ `js-append`<!-- -->: Use JS runtime to create each `<link>` and append to the body.\n\n`html-append`<!-- -->: Render each `<link>` within html, appended at the end of the body.\n\nDefaults to `html-append`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\n[linkRel?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n'prefetch' \\| 'preload' \\| 'modulepreload' \\| null\n\n\n</td><td>\n\n_(Optional)_ Value of the `<link rel=\"...\">` attribute when link is used. Defaults to `modulepreload`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\n[prefetchEvent?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n'always' \\| null\n\n\n</td><td>\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<script type=\"module\">\n document.dispatchEvent(new CustomEvent(\"qprefetch\", { detail:{ \"bundles\": [...] } }))\n</script>\n```\nBy default, the `prefetchEvent` implementation will be set to `null`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\n[workerFetchInsert?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n'always' \\| 'no-link-support' \\| null\n\n\n</td><td>\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 `<link>` prefetch/preload/modulepreload.\n\nDefaults to `null`<!-- -->.\n\n\n</td></tr>\n</tbody></table>",
85+
"content": "```typescript\nexport interface PrefetchImplementation \n```\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[linkFetchPriority?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n'auto' \\| 'low' \\| 'high' \\| null\n\n\n</td><td>\n\n_(Optional)_ Value of the `<link fetchpriority=\"...\">` attribute when link is used. Defaults to `null`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\n[linkInsert?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n'js-append' \\| 'html-append' \\| null\n\n\n</td><td>\n\n_(Optional)_ `js-append`<!-- -->: Use JS runtime to create each `<link>` and append to the head.\n\n`html-append`<!-- -->: Render each `<link>` within html, appended at the end of the body.\n\nDefaults to `js-append`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\n[linkRel?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n'prefetch' \\| 'preload' \\| 'modulepreload' \\| null\n\n\n</td><td>\n\n_(Optional)_ Value of the `<link rel=\"...\">` attribute when link is used. Defaults to `modulepreload`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\n[prefetchEvent?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n'always' \\| null\n\n\n</td><td>\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<script type=\"module\">\n document.dispatchEvent(new CustomEvent(\"qprefetch\", { detail:{ \"bundles\": [...] } }))\n</script>\n```\nBy default, the `prefetchEvent` implementation will be set to `null`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\n[workerFetchInsert?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n'always' \\| 'no-link-support' \\| null\n\n\n</td><td>\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 `<link>` prefetch/preload/modulepreload.\n\nDefaults to `null`<!-- -->.\n\n\n</td></tr>\n</tbody></table>",
8686
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/server/types.ts",
8787
"mdFile": "qwik.prefetchimplementation.md"
8888
},
@@ -152,7 +152,7 @@
152152
}
153153
],
154154
"kind": "Interface",
155-
"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[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)_ 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</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>",
155+
"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[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>",
156156
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/server/types.ts",
157157
"mdFile": "qwik.renderoptions.md"
158158
},

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,11 +258,11 @@ _(Optional)_ Value of the `<link fetchpriority="...">` attribute when link is us
258258

259259
</td><td>
260260

261-
_(Optional)_ `js-append`: Use JS runtime to create each `<link>` and append to the body.
261+
_(Optional)_ `js-append`: Use JS runtime to create each `<link>` and append to the head.
262262

263263
`html-append`: Render each `<link>` within html, appended at the end of the body.
264264

265-
Defaults to `html-append`.
265+
Defaults to `js-append`.
266266

267267
</td></tr>
268268
<tr><td>
@@ -647,9 +647,7 @@ QwikPrefetchServiceWorkerOptions
647647
648648
</td><td>
649649
650-
_(Optional)_ Specifies if the Qwik Prefetch Service Worker script is added to the document or not.
651-
652-
Defaults to `{ include: false }`. NOTE: This may be change in the future.
650+
_(Optional)_
653651
654652
</td></tr>
655653
<tr><td>

packages/qwik/src/core/qrl/preload.ts

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -101,24 +101,26 @@ export const loadBundleGraph = (element: Element) => {
101101
});
102102
};
103103

104-
let canModulePreload: boolean | null = null;
105-
const makePreloadLink = (bundle: BundleImport, priority: boolean) => {
106-
const link = document.createElement('link');
107-
if (canModulePreload === null) {
108-
if (link.relList.supports('modulepreload')) {
109-
canModulePreload = true;
110-
} else {
111-
canModulePreload = false;
104+
// we stringify this in prefetch-implementation.ts
105+
export const makeMakePreloadLink = /*@__PURE__*/
106+
(canModulePreload: boolean | null) => (url: string, priority: boolean) => {
107+
const link = document.createElement('link');
108+
if (canModulePreload === null) {
109+
if (link.relList.supports('modulepreload')) {
110+
canModulePreload = true;
111+
} else {
112+
canModulePreload = false;
113+
}
112114
}
113-
}
114-
link.rel = canModulePreload ? 'modulepreload' : 'preload';
115-
link.href = bundle.$url$!;
116-
link.fetchPriority = priority ? 'high' : 'low';
117-
if (!canModulePreload) {
118-
link.as = 'script';
119-
}
120-
document.head.appendChild(link);
121-
};
115+
link.rel = canModulePreload ? 'modulepreload' : 'preload';
116+
link.href = url;
117+
link.fetchPriority = priority ? 'high' : 'low';
118+
if (!canModulePreload) {
119+
link.as = 'script';
120+
}
121+
document.head.appendChild(link);
122+
};
123+
const makePreloadLink = makeMakePreloadLink(null);
122124

123125
const prioritizeLink = (url: string) => {
124126
const link = document.querySelector(`link[href="${url}"]`) as HTMLLinkElement | null;
@@ -135,9 +137,9 @@ const preloadBundle = (bundle: BundleImport, priority: boolean) => {
135137
}
136138
if (bundle.$url$) {
137139
if (bundle.$state$ === BundleImportState.None) {
138-
makePreloadLink(bundle, priority);
140+
makePreloadLink(bundle.$url$, priority);
139141
} else if (priority && bundle.$state$ === BundleImportState.Low) {
140-
prioritizeLink(bundle.$url$!);
142+
prioritizeLink(bundle.$url$);
141143
} else {
142144
return;
143145
}

packages/qwik/src/server/api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export interface PrefetchImplementation {
4747
linkInsert?: 'js-append' | 'html-append' | null;
4848
linkRel?: 'prefetch' | 'preload' | 'modulepreload' | null;
4949
prefetchEvent?: 'always' | null;
50+
// @deprecated
5051
workerFetchInsert?: 'always' | 'no-link-support' | null;
5152
}
5253

@@ -90,6 +91,8 @@ export interface RenderOptions extends SerializeDocumentOptions {
9091
prefetchStrategy?: PrefetchStrategy | null;
9192
qwikLoader?: QwikLoaderOptions;
9293
// Warning: (ae-forgotten-export) The symbol "QwikPrefetchServiceWorkerOptions" needs to be exported by the entry point index.d.ts
94+
//
95+
// @deprecated (undocumented)
9396
qwikPrefetchServiceWorker?: QwikPrefetchServiceWorkerOptions;
9497
// (undocumented)
9598
serverData?: Record<string, any>;

packages/qwik/src/server/prefetch-implementation.ts

Lines changed: 35 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Fragment, jsx, type JSXNode } from '@builder.io/qwik';
22
import { flattenPrefetchResources, getMostReferenced, workerFetchScript } from './prefetch-utils';
33
import type { PrefetchImplementation, PrefetchResource, PrefetchStrategy } from './types';
4+
import { makeMakePreloadLink } from '../core/qrl/preload';
45

56
export function applyPrefetchImplementation(
67
base: string,
@@ -31,7 +32,7 @@ export function applyPrefetchImplementation(
3132
}
3233

3334
if (prefetchImpl.linkInsert === 'js-append') {
34-
linkJsImplementation(prefetchNodes, prefetchResources, prefetchImpl, nonce);
35+
linkJsImplementation(base, manifestHash, nonce, prefetchNodes, prefetchResources, prefetchImpl);
3536
} else if (prefetchImpl.workerFetchInsert === 'always') {
3637
workerFetchImplementation(prefetchNodes, prefetchResources, nonce);
3738
}
@@ -90,7 +91,7 @@ function linkHtmlImplementation(
9091
href: `${base}q-bundle-graph-${manifestHash}.json`,
9192
as: 'fetch',
9293
crossorigin: 'anonymous',
93-
priority: prefetchImpl.linkFetchPriority || undefined,
94+
fetchpriority: prefetchImpl.linkFetchPriority || undefined,
9495
})
9596
);
9697
}
@@ -119,57 +120,48 @@ function linkHtmlImplementation(
119120
* TODO use idle event
120121
*/
121122
function linkJsImplementation(
123+
base: string,
124+
manifestHash: string | undefined,
125+
nonce: string | undefined,
122126
prefetchNodes: JSXNode[],
123127
prefetchResources: PrefetchResource[],
124-
prefetchImpl: Required<PrefetchImplementation>,
125-
nonce?: string
128+
prefetchImpl: Required<PrefetchImplementation>
126129
) {
127-
const rel = prefetchImpl.linkRel || 'modulepreload';
128-
const priority = prefetchImpl.linkFetchPriority;
129-
let s = ``;
130-
131-
if (prefetchImpl.workerFetchInsert === 'no-link-support') {
132-
s += `let supportsLinkRel = true;`;
133-
}
134-
135-
s += `const u=${JSON.stringify(flattenPrefetchResources(prefetchResources).keys())};`;
136-
s += `u.map((u,i)=>{`;
137-
138-
s += `const l=document.createElement('link');`;
139-
s += `l.setAttribute("href",u);`;
140-
s += `l.setAttribute("rel","${rel}");`;
141-
if (priority) {
142-
s += `l.setAttribute("fetchpriority","${priority}");`;
143-
}
144-
145-
if (prefetchImpl.workerFetchInsert === 'no-link-support') {
146-
s += `if(i===0){`;
147-
s += `try{`;
148-
s += `supportsLinkRel=l.relList.supports("${rel}");`;
149-
s += `}catch(e){}`;
150-
s += `}`;
151-
}
152-
153-
s += `document.body.appendChild(l);`;
154-
155-
s += `});`;
156-
157-
if (prefetchImpl.workerFetchInsert === 'no-link-support') {
158-
s += `if(!supportsLinkRel){`;
159-
s += workerFetchScript();
160-
s += `}`;
130+
const injector = makeMakePreloadLink.toString();
131+
const urls = flattenPrefetchResources(prefetchResources);
132+
const fetchPriority = prefetchImpl.linkFetchPriority;
133+
const forceLow = fetchPriority === 'low';
134+
const prio = [];
135+
const low = [];
136+
for (const [url, priority] of urls) {
137+
if (!priority || forceLow) {
138+
low.push(url);
139+
} else {
140+
prio.push(url);
141+
}
161142
}
162143

163-
if (prefetchImpl.workerFetchInsert === 'always') {
164-
s += workerFetchScript();
165-
}
144+
// Maybe this needs to be delayed
145+
const script = `
146+
var _=(${injector})(null);
147+
${prio.length ? `${JSON.stringify(prio)}.forEach(u=>_(u,1));` : ''}
148+
${low.length ? `${JSON.stringify(low)}.forEach(u=>_(u,0));` : ''}
149+
`.replaceAll(/^\s+|\s*\n/gm, '');
166150

167151
prefetchNodes.push(
168152
jsx('script', {
169153
type: 'module',
170154
'q:type': 'link-js',
171-
dangerouslySetInnerHTML: s,
155+
dangerouslySetInnerHTML: script,
172156
nonce,
157+
}),
158+
jsx('link', {
159+
rel: 'fetch',
160+
id: `qwik-bg-${manifestHash}`,
161+
href: `${base}q-bundle-graph-${manifestHash}.json`,
162+
as: 'fetch',
163+
crossorigin: 'anonymous',
164+
fetchpriority: prefetchImpl.linkFetchPriority || undefined,
173165
})
174166
);
175167
}
@@ -199,7 +191,7 @@ function normalizePrefetchImplementation(
199191
}
200192

201193
const PrefetchImplementationDefault: Required<PrefetchImplementation> = {
202-
linkInsert: 'html-append',
194+
linkInsert: 'js-append',
203195
linkRel: 'modulepreload',
204196
linkFetchPriority: null,
205197
workerFetchInsert: null,

packages/qwik/src/server/types.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ export interface PrefetchStrategy {
2222
/** @public */
2323
export interface PrefetchImplementation {
2424
/**
25-
* `js-append`: Use JS runtime to create each `<link>` and append to the body.
25+
* `js-append`: Use JS runtime to create each `<link>` and append to the head.
2626
*
2727
* `html-append`: Render each `<link>` within html, appended at the end of the body.
2828
*
29-
* Defaults to `html-append`.
29+
* Defaults to `js-append`.
3030
*/
3131
linkInsert?: 'js-append' | 'html-append' | null;
3232
/** Value of the `<link rel="...">` attribute when link is used. Defaults to `modulepreload`. */
@@ -40,6 +40,8 @@ export interface PrefetchImplementation {
4040
* `<link>` prefetch/preload/modulepreload.
4141
*
4242
* Defaults to `null`.
43+
*
44+
* @deprecated Use `linkInsert` instead
4345
*/
4446
workerFetchInsert?: 'always' | 'no-link-support' | null;
4547
/**
@@ -142,11 +144,7 @@ export interface RenderOptions extends SerializeDocumentOptions {
142144
*/
143145
qwikLoader?: QwikLoaderOptions;
144146

145-
/**
146-
* Specifies if the Qwik Prefetch Service Worker script is added to the document or not.
147-
*
148-
* Defaults to `{ include: false }`. NOTE: This may be change in the future.
149-
*/
147+
/** @deprecated Use `prefetchStrategy` instead */
150148
qwikPrefetchServiceWorker?: QwikPrefetchServiceWorkerOptions;
151149

152150
prefetchStrategy?: PrefetchStrategy | null;

0 commit comments

Comments
 (0)