Skip to content

Commit dd220fc

Browse files
committed
fix(core): bundlegraph as asset
- don't assume path of bundlegraph during render - move bundlegraph to standard assets location - refactor preloader script output
1 parent fa62bc2 commit dd220fc

File tree

10 files changed

+122
-65
lines changed

10 files changed

+122
-65
lines changed

.changeset/good-mammals-grab.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@builder.io/qwik': minor
3+
---
4+
5+
FIX: the preloader bundle graph file is now built as an asset. This is cleaner and avoids i18n translation of the file.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@
451451
}
452452
],
453453
"kind": "Interface",
454-
"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<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[bundleGraph?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[QwikBundleGraph](#qwikbundlegraph)\n\n\n</td><td>\n\n_(Optional)_ All bundles in a compact graph format with probabilities\n\n\n</td></tr>\n<tr><td>\n\n[bundles](#)\n\n\n</td><td>\n\n\n</td><td>\n\n{ \\[fileName: string\\]: [QwikBundle](#qwikbundle)<!-- -->; }\n\n\n</td><td>\n\nAll code bundles, used to know the import graph\n\n\n</td></tr>\n<tr><td>\n\n[core?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\n_(Optional)_ The Qwik core bundle fileName\n\n\n</td></tr>\n<tr><td>\n\n[injections?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[GlobalInjections](#globalinjections)<!-- -->\\[\\]\n\n\n</td><td>\n\n_(Optional)_ CSS etc to inject in the document head\n\n\n</td></tr>\n<tr><td>\n\n[manifestHash](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\nContent hash of the manifest, if this changes, the code changed\n\n\n</td></tr>\n<tr><td>\n\n[mapping](#)\n\n\n</td><td>\n\n\n</td><td>\n\n{ \\[symbolName: string\\]: string; }\n\n\n</td><td>\n\nWhere QRLs are located\n\n\n</td></tr>\n<tr><td>\n\n[options?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n{ target?: string; buildMode?: string; entryStrategy?: { type: [EntryStrategy](#entrystrategy)<!-- -->\\['type'\\]; }; }\n\n\n</td><td>\n\n_(Optional)_ The options used to build the manifest\n\n\n</td></tr>\n<tr><td>\n\n[platform?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n{ \\[name: string\\]: string; }\n\n\n</td><td>\n\n_(Optional)_ The platform used to build the manifest\n\n\n</td></tr>\n<tr><td>\n\n[preloader?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\n_(Optional)_ The preloader bundle fileName\n\n\n</td></tr>\n<tr><td>\n\n[symbols](#)\n\n\n</td><td>\n\n\n</td><td>\n\n{ \\[symbolName: string\\]: [QwikSymbol](#qwiksymbol)<!-- -->; }\n\n\n</td><td>\n\nQRL symbols\n\n\n</td></tr>\n<tr><td>\n\n[version](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\nThe version of the manifest\n\n\n</td></tr>\n</tbody></table>",
454+
"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<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[bundleGraph?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[QwikBundleGraph](#qwikbundlegraph)\n\n\n</td><td>\n\n_(Optional)_ All bundles in a compact graph format with probabilities\n\n\n</td></tr>\n<tr><td>\n\n[bundleGraphPath?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\n_(Optional)_ The bundle graph fileName\n\n\n</td></tr>\n<tr><td>\n\n[bundles](#)\n\n\n</td><td>\n\n\n</td><td>\n\n{ \\[fileName: string\\]: [QwikBundle](#qwikbundle)<!-- -->; }\n\n\n</td><td>\n\nAll code bundles, used to know the import graph\n\n\n</td></tr>\n<tr><td>\n\n[core?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\n_(Optional)_ The Qwik core bundle fileName\n\n\n</td></tr>\n<tr><td>\n\n[injections?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[GlobalInjections](#globalinjections)<!-- -->\\[\\]\n\n\n</td><td>\n\n_(Optional)_ CSS etc to inject in the document head\n\n\n</td></tr>\n<tr><td>\n\n[manifestHash](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\nContent hash of the manifest, if this changes, the code changed\n\n\n</td></tr>\n<tr><td>\n\n[mapping](#)\n\n\n</td><td>\n\n\n</td><td>\n\n{ \\[symbolName: string\\]: string; }\n\n\n</td><td>\n\nWhere QRLs are located\n\n\n</td></tr>\n<tr><td>\n\n[options?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n{ target?: string; buildMode?: string; entryStrategy?: { type: [EntryStrategy](#entrystrategy)<!-- -->\\['type'\\]; }; }\n\n\n</td><td>\n\n_(Optional)_ The options used to build the manifest\n\n\n</td></tr>\n<tr><td>\n\n[platform?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n{ \\[name: string\\]: string; }\n\n\n</td><td>\n\n_(Optional)_ The platform used to build the manifest\n\n\n</td></tr>\n<tr><td>\n\n[preloader?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\n_(Optional)_ The preloader bundle fileName\n\n\n</td></tr>\n<tr><td>\n\n[symbols](#)\n\n\n</td><td>\n\n\n</td><td>\n\n{ \\[symbolName: string\\]: [QwikSymbol](#qwiksymbol)<!-- -->; }\n\n\n</td><td>\n\nQRL symbols\n\n\n</td></tr>\n<tr><td>\n\n[version](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\nThe version of the manifest\n\n\n</td></tr>\n</tbody></table>",
455455
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts",
456456
"mdFile": "qwik.qwikmanifest.md"
457457
},

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1433,6 +1433,21 @@ _(Optional)_ All bundles in a compact graph format with probabilities
14331433
</td></tr>
14341434
<tr><td>
14351435

1436+
[bundleGraphPath?](#)
1437+
1438+
</td><td>
1439+
1440+
</td><td>
1441+
1442+
string
1443+
1444+
</td><td>
1445+
1446+
_(Optional)_ The bundle graph fileName
1447+
1448+
</td></tr>
1449+
<tr><td>
1450+
14361451
[bundles](#)
14371452

14381453
</td><td>

packages/qwik/src/optimizer/src/manifest.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,8 @@ export function generateManifestFromBundles(
394394
injections: GlobalInjections[],
395395
outputBundles: OutputBundle,
396396
opts: NormalizedQwikPluginOptions,
397-
debug: (...args: any[]) => void
397+
debug: (...args: any[]) => void,
398+
canonPath: (p: string) => string
398399
) {
399400
const manifest: QwikManifest = {
400401
manifestHash: '',
@@ -411,9 +412,6 @@ export function generateManifestFromBundles(
411412
},
412413
};
413414

414-
const buildPath = path.resolve(opts.rootDir, opts.outDir, 'build');
415-
const canonPath = (p: string) =>
416-
path.relative(buildPath, path.resolve(opts.rootDir, opts.outDir, p));
417415
const getBundleName = (name: string) => {
418416
const bundle = outputBundles[name];
419417
if (!bundle) {

packages/qwik/src/optimizer/src/plugins/bundle-graph.unit.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ describe('convertManifestToBundleGraph', () => {
162162
[],
163163
outputBundles.bundles as any,
164164
{ rootDir: '/', outDir: '/' } as any,
165-
console.error
165+
console.error,
166+
(p) => path.relative('build', p)
166167
);
167168

168169
// Interactivity scores

packages/qwik/src/optimizer/src/plugins/plugin.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -776,15 +776,28 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) {
776776
return null;
777777
};
778778

779+
type OutputAnalyzer = {
780+
addInjection: (b: GlobalInjections) => void;
781+
generateManifest: (extra?: Partial<QwikManifest>) => Promise<QwikManifest>;
782+
canonPath: (p: string) => string;
783+
};
784+
779785
const createOutputAnalyzer = (rollupBundle: OutputBundle) => {
780786
const injections: GlobalInjections[] = [];
781787

782-
const addInjection = (b: GlobalInjections) => injections.push(b);
788+
const outputAnalyzer: OutputAnalyzer = {
789+
addInjection: (b: GlobalInjections) => injections.push(b),
790+
} as Partial<OutputAnalyzer> as OutputAnalyzer;
783791

784-
const generateManifest = async (extra?: Partial<QwikManifest>) => {
792+
outputAnalyzer.generateManifest = async (extra?: Partial<QwikManifest>) => {
785793
const optimizer = getOptimizer();
786794
const path = optimizer.sys.path;
787795

796+
const buildPath = path.resolve(opts.rootDir, opts.outDir, 'build');
797+
const canonPath = (p: string) =>
798+
path.relative(buildPath, path.resolve(opts.rootDir, opts.outDir, p));
799+
outputAnalyzer.canonPath = canonPath;
800+
788801
const segments = Array.from(clientResults.values())
789802
.flatMap((r) => r.modules)
790803
.map((mod) => mod.segment)
@@ -796,7 +809,8 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) {
796809
injections,
797810
rollupBundle,
798811
opts,
799-
debug
812+
debug,
813+
canonPath
800814
);
801815
if (extra) {
802816
Object.assign(manifest, extra);
@@ -824,7 +838,7 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) {
824838
return manifest;
825839
};
826840

827-
return { addInjection, generateManifest };
841+
return outputAnalyzer;
828842
};
829843

830844
const getOptions = () => opts;
@@ -871,6 +885,7 @@ export const isDev = ${JSON.stringify(isDev)};
871885
mapping: manifest.mapping,
872886
preloader: manifest.preloader,
873887
core: manifest.core,
888+
bundleGraphPath: manifest.bundleGraphPath,
874889
};
875890
}
876891
return `// @qwik-client-manifest
@@ -947,19 +962,13 @@ export const manifest = ${JSON.stringify(serverManifest)};\n`;
947962
manifest.platform!.node = process.versions.node;
948963
}
949964

950-
const assetsDir = opts.assetsDir;
951-
const useAssetsDir = !!assetsDir && assetsDir !== '_astro';
952965
const bundleGraph = convertManifestToBundleGraph(manifest, bundleGraphAdders);
953-
ctx.emitFile({
966+
const bgAsset = ctx.emitFile({
954967
type: 'asset',
955-
fileName: optimizer.sys.path.join(
956-
useAssetsDir ? assetsDir : '',
957-
'build',
958-
`q-bundle-graph-${manifest.manifestHash}.json`
959-
),
968+
name: 'bundle-graph.json',
960969
source: JSON.stringify(bundleGraph),
961970
});
962-
971+
manifest.bundleGraphPath = outputAnalyzer.canonPath(ctx.getFileName(bgAsset));
963972
manifest.bundleGraph = bundleGraph;
964973

965974
const manifestStr = JSON.stringify(manifest, null, '\t');

packages/qwik/src/optimizer/src/qwik.optimizer.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ export type QwikBundleGraph = Array<string | number>;
183183
// @public
184184
export interface QwikManifest {
185185
bundleGraph?: QwikBundleGraph;
186+
bundleGraphPath?: string;
186187
bundles: {
187188
[fileName: string]: QwikBundle;
188189
};

packages/qwik/src/optimizer/src/types.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ export interface QwikManifest {
225225
bundles: { [fileName: string]: QwikBundle };
226226
/** All bundles in a compact graph format with probabilities */
227227
bundleGraph?: QwikBundleGraph;
228+
/** The bundle graph fileName */
229+
bundleGraphPath?: string;
228230
/** The preloader bundle fileName */
229231
preloader?: string;
230232
/** The Qwik core bundle fileName */
@@ -249,7 +251,13 @@ export interface QwikManifest {
249251
*/
250252
export type ServerQwikManifest = Pick<
251253
QwikManifest,
252-
'manifestHash' | 'injections' | 'bundleGraph' | 'mapping' | 'preloader' | 'core'
254+
| 'manifestHash'
255+
| 'injections'
256+
| 'bundleGraph'
257+
| 'bundleGraphPath'
258+
| 'mapping'
259+
| 'preloader'
260+
| 'core'
253261
>;
254262

255263
/**

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

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

8+
const simplifyPath = (base: string, path: string | null | undefined) => {
9+
if (path == null) {
10+
return null;
11+
}
12+
const segments = `${base}${path}`.split('/');
13+
const simplified = [];
14+
for (const segment of segments) {
15+
if (segment === '..' && simplified.length > 0) {
16+
simplified.pop();
17+
} else {
18+
simplified.push(segment);
19+
}
20+
}
21+
return simplified.join('/');
22+
};
23+
824
export const preloaderPre = (
925
base: string,
1026
resolvedManifest: ResolvedManifest | undefined,
1127
options: PreloaderOptions | false | undefined,
1228
beforeContent: JSXNode<string>[],
1329
nonce?: string
1430
) => {
15-
const preloadChunk = resolvedManifest?.manifest?.preloader;
16-
if (preloadChunk && options !== false) {
31+
const preloaderPath = simplifyPath(base, resolvedManifest?.manifest?.preloader);
32+
const bundleGraphPath = simplifyPath(base, resolvedManifest?.manifest.bundleGraphPath);
33+
if (preloaderPath && bundleGraphPath && options !== false) {
1734
// Initialize the SSR preloader
1835
const preloaderOpts: Parameters<typeof initPreloader>[1] =
1936
typeof options === 'object'
@@ -37,11 +54,9 @@ export const preloaderPre = (
3754
}
3855
const optsStr = opts.length ? `,{${opts.join(',')}}` : '';
3956

40-
const hash = resolvedManifest?.manifest.manifestHash;
41-
4257
const script =
43-
`let b=fetch("${base}q-bundle-graph-${hash}.json");` +
44-
`import("${base}${preloadChunk}").then(({l})=>` +
58+
`let b=fetch("${bundleGraphPath}");` +
59+
`import("${preloaderPath}").then(({l})=>` +
4560
`l(${JSON.stringify(base)},b${optsStr})` +
4661
`);`;
4762

@@ -50,10 +65,10 @@ export const preloaderPre = (
5065
* We add modulepreloads even when the script is at the top because they already fire during
5166
* html download
5267
*/
53-
jsx('link', { rel: 'modulepreload', href: `${base}${preloadChunk}` }),
68+
jsx('link', { rel: 'modulepreload', href: preloaderPath }),
5469
jsx('link', {
5570
rel: 'preload',
56-
href: `${base}q-bundle-graph-${resolvedManifest?.manifest.manifestHash}.json`,
71+
href: bundleGraphPath,
5772
as: 'fetch',
5873
crossorigin: 'anonymous',
5974
}),
@@ -64,11 +79,11 @@ export const preloaderPre = (
6479
nonce,
6580
})
6681
);
82+
}
6783

68-
const core = resolvedManifest?.manifest.core;
69-
if (core) {
70-
beforeContent.push(jsx('link', { rel: 'modulepreload', href: `${base}${core}` }));
71-
}
84+
const corePath = simplifyPath(base, resolvedManifest?.manifest.core);
85+
if (corePath) {
86+
beforeContent.push(jsx('link', { rel: 'modulepreload', href: corePath }));
7287
}
7388
};
7489

@@ -102,6 +117,8 @@ export const includePreloader = (
102117

103118
const manifestHash = resolvedManifest?.manifest.manifestHash;
104119
if (allowed) {
120+
const preloaderBundle = resolvedManifest?.manifest.preloader;
121+
const coreBundle = resolvedManifest?.manifest.core;
105122
const expandedBundles = expandBundles(referencedBundles, resolvedManifest);
106123
// Keep the same as in getQueue (but *10)
107124
let probability = 4;
@@ -111,6 +128,10 @@ export const includePreloader = (
111128
if (probability < tenXMinProbability) {
112129
break;
113130
}
131+
// we already preload the preloader and core bundles
132+
if (hrefOrProbability === preloaderBundle || hrefOrProbability === coreBundle) {
133+
continue;
134+
}
114135
links.push(hrefOrProbability);
115136
if (--allowed === 0) {
116137
break;
@@ -121,41 +142,43 @@ export const includePreloader = (
121142
}
122143
}
123144

124-
const preloadChunk = manifestHash && resolvedManifest?.manifest.preloader;
125-
if (preloadChunk) {
126-
const insertLinks = links.length
127-
? /**
128-
* We only use modulepreload links because they behave best. Older browsers can rely on the
129-
* preloader which does feature detection and which will be available soon after inserting these
130-
* links.
131-
*/
132-
`${JSON.stringify(links)}.map((l,e)=>{` +
133-
`e=document.createElement('link');` +
134-
`e.rel='modulepreload';` +
135-
`e.href=${JSON.stringify(base)}+l;` +
136-
`document.head.appendChild(e)` +
137-
`});`
138-
: '';
139-
// We are super careful not to interfere with the page loading.
140-
const script =
141-
insertLinks +
142-
// First we wait for the onload event
145+
const preloaderPath = simplifyPath(base, manifestHash && resolvedManifest?.manifest.preloader);
146+
const insertLinks = links.length
147+
? /**
148+
* We only use modulepreload links because they behave best. Older browsers can rely on the
149+
* preloader which does feature detection and which will be available soon after inserting these
150+
* links.
151+
*/
152+
`${JSON.stringify(links)}.map((l,e)=>{` +
153+
`e=document.createElement('link');` +
154+
`e.rel='modulepreload';` +
155+
`e.href=${JSON.stringify(base)}+l;` +
156+
`document.head.appendChild(e)` +
157+
`});`
158+
: '';
159+
// We are super careful not to interfere with the page loading.
160+
let script = insertLinks;
161+
if (preloaderPath) {
162+
// First we wait for the onload event
163+
script +=
143164
`window.addEventListener('load',f=>{` +
144-
`f=_=>import("${base}${preloadChunk}").then(({p})=>p(${JSON.stringify(referencedBundles)}));` +
165+
`f=_=>import("${preloaderPath}").then(({p})=>p(${JSON.stringify(referencedBundles)}));` +
145166
// then we ask for idle callback
146167
`try{requestIdleCallback(f,{timeout:2000})}` +
147168
// some browsers don't support requestIdleCallback
148169
`catch(e){setTimeout(f,200)}` +
149170
`})`;
150-
/**
151-
* Uses the preloader chunk to add the `<link>` elements at runtime. This allows core to simply
152-
* import the preloader as well and have all the state there, plus it makes it easy to write a
153-
* complex implementation.
154-
*
155-
* Note that we don't preload the preloader or bundlegraph, they are requested after the SSR
156-
* preloads because they are not as important. Also the preloader includes the vitePreload
157-
* function and will in fact already be in that list.
158-
*/
171+
}
172+
/**
173+
* Uses the preloader chunk to add the `<link>` elements at runtime. This allows core to simply
174+
* import the preloader as well and have all the state there, plus it makes it easy to write a
175+
* complex implementation.
176+
*
177+
* Note that we don't preload the preloader or bundlegraph, they are requested after the SSR
178+
* preloads because they are not as important. Also the preloader includes the vitePreload
179+
* function and will in fact already be in that list.
180+
*/
181+
if (script) {
159182
nodes.push(
160183
jsx('script', {
161184
type: 'module',

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,5 @@ export const expandBundles = (names: string[], resolvedManifest?: ResolvedManife
7474
probability *= 0.85;
7575
}
7676

77-
return getQueue().filter(
78-
(name) =>
79-
name !== resolvedManifest?.manifest.preloader && name !== resolvedManifest?.manifest.core
80-
);
77+
return getQueue();
8178
};

0 commit comments

Comments
 (0)