Skip to content

Commit a6dddd6

Browse files
committed
perf(qwikloader): qwikloader in separate bundle
this doesn't impact LCP and reduces download size, and allows earlier execution
1 parent cf3a8aa commit a6dddd6

File tree

14 files changed

+88
-48
lines changed

14 files changed

+88
-48
lines changed

.changeset/empty-sites-tie.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@builder.io/qwik': patch
3+
---
4+
5+
:zap: the qwikloader is no longer embedded in the SSR results. Instead, the same techniques are used as for the preloader to ensure that the qwikloader is active as soon as possible, loaded from a separate bundle. This reduces SSR page size by several kB end ensures that subsequent qwikloader loads are nearly instant.

packages/docs/src/entry.ssr.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,6 @@ export default function (opts: RenderToStreamOptions) {
3434
}
3535
}
3636
return renderToStream(<Root />, {
37-
qwikLoader: {
38-
// The docs can be long so make sure to intercept events before the end of the document.
39-
position: 'top',
40-
},
4137
...opts,
4238
containerAttributes: {
4339
lang: 'en',

packages/docs/src/repl/bundled.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import qCoreMinMjs from '../../node_modules/@builder.io/qwik/dist/core.min.mjs?r
1515
import qCoreMjs from '../../node_modules/@builder.io/qwik/dist/core.mjs?raw-source';
1616
import qOptimizerCjs from '../../node_modules/@builder.io/qwik/dist/optimizer.cjs?raw-source';
1717
import qPreloaderMjs from '../../node_modules/@builder.io/qwik/dist/preloader.mjs?raw-source';
18+
// we use the debug version for the repl so it's understandable
19+
import qQwikLoaderJs from '../../node_modules/@builder.io/qwik/dist/qwikloader.debug.js?raw-source';
1820
import qServerCjs from '../../node_modules/@builder.io/qwik/dist/server.cjs?raw-source';
1921
import qServerDts from '../../node_modules/@builder.io/qwik/dist/server.d.ts?raw-source';
2022
import qWasmCjs from '../../node_modules/@builder.io/qwik/bindings/qwik.wasm.cjs?raw-source';
@@ -57,6 +59,7 @@ export const bundled: PkgUrls = {
5759
'/dist/server.cjs': qServerCjs,
5860
'/dist/server.d.ts': qServerDts,
5961
'/dist/preloader.mjs': qPreloaderMjs,
62+
'/dist/qwikloader.js': qQwikLoaderJs,
6063
'/bindings/qwik.wasm.cjs': qWasmCjs,
6164
'/bindings/qwik_wasm_bg.wasm': qWasmBinUrl,
6265
},

packages/docs/src/repl/worker/repl-plugins.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,13 @@ export const replResolver = (options: ReplInputOptions, buildMode: 'client' | 's
7878
return rsp.text();
7979
}
8080
}
81-
81+
// this id is unchanged because it's an entry point
82+
if (id === '@builder.io/qwik/qwikloader.js') {
83+
const rsp = await depResponse('@builder.io/qwik', '/qwikloader.js');
84+
if (rsp) {
85+
return rsp.text();
86+
}
87+
}
8288
// We're the fallback, we know all the files
8389
if (/\.[jt]sx?$/.test(id)) {
8490
throw new Error(`load: unknown module ${id}`);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@
465465
}
466466
],
467467
"kind": "Interface",
468-
"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[assets?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n{ \\[fileName: string\\]: [QwikAsset](#qwikasset)<!-- -->; }\n\n\n</td><td>\n\n_(Optional)_ All assets. The key is the fileName relative to the rootDir\n\n\n</td></tr>\n<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[bundleGraphAsset?](#)\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. The key is the bundle fileName relative to \"build/\"\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. The key is the symbol name, the value is the bundle fileName\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>",
468+
"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[assets?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n{ \\[fileName: string\\]: [QwikAsset](#qwikasset)<!-- -->; }\n\n\n</td><td>\n\n_(Optional)_ All assets. The key is the fileName relative to the rootDir\n\n\n</td></tr>\n<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[bundleGraphAsset?](#)\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. The key is the bundle fileName relative to \"build/\"\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. The key is the symbol name, the value is the bundle fileName\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[qwikLoader?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\n_(Optional)_ The Qwik loader 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>",
469469
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts",
470470
"mdFile": "qwik.qwikmanifest.md"
471471
},
@@ -667,7 +667,7 @@
667667
}
668668
],
669669
"kind": "TypeAlias",
670-
"content": "The manifest values that are needed for SSR.\n\n\n```typescript\nexport type ServerQwikManifest = Pick<QwikManifest, 'manifestHash' | 'injections' | 'bundleGraph' | 'bundleGraphAsset' | 'mapping' | 'preloader' | 'core'>;\n```\n**References:** [QwikManifest](#qwikmanifest)",
670+
"content": "The manifest values that are needed for SSR.\n\n\n```typescript\nexport type ServerQwikManifest = Pick<QwikManifest, 'manifestHash' | 'injections' | 'bundleGraph' | 'bundleGraphAsset' | 'mapping' | 'preloader' | 'core' | 'qwikLoader'>;\n```\n**References:** [QwikManifest](#qwikmanifest)",
671671
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts",
672672
"mdFile": "qwik.serverqwikmanifest.md"
673673
},

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1640,6 +1640,21 @@ _(Optional)_ The preloader bundle fileName
16401640
</td></tr>
16411641
<tr><td>
16421642

1643+
[qwikLoader?](#)
1644+
1645+
</td><td>
1646+
1647+
</td><td>
1648+
1649+
string
1650+
1651+
</td><td>
1652+
1653+
_(Optional)_ The Qwik loader bundle fileName
1654+
1655+
</td></tr>
1656+
<tr><td>
1657+
16431658
[symbols](#)
16441659

16451660
</td><td>
@@ -2764,6 +2779,7 @@ export type ServerQwikManifest = Pick<
27642779
| "mapping"
27652780
| "preloader"
27662781
| "core"
2782+
| "qwikLoader"
27672783
>;
27682784
```
27692785

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@
138138
}
139139
],
140140
"kind": "Interface",
141-
"content": "```typescript\nexport interface QwikLoaderOptions \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[include?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n'always' \\| 'never' \\| 'auto'\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\n[position?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n'top' \\| 'bottom'\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>",
141+
"content": "```typescript\nexport interface QwikLoaderOptions \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[include?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n'always' \\| 'never' \\| 'auto'\n\n\n</td><td>\n\n_(Optional)_ Whether to include the qwikloader script in the document. Normally you don't need to worry about this, but in case of multi-container apps using different Qwik versions, you might want to only enable it on one of the containers.\n\nDefaults to `'auto'`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\n[position?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n'top' \\| 'bottom'\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>",
142142
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/server/types.ts",
143143
"mdFile": "qwik.qwikloaderoptions.md"
144144
},

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,9 @@ Description
575575

576576
</td><td>
577577

578-
_(Optional)_
578+
_(Optional)_ Whether to include the qwikloader script in the document. Normally you don't need to worry about this, but in case of multi-container apps using different Qwik versions, you might want to only enable it on one of the containers.
579+
580+
Defaults to `'auto'`.
579581

580582
</td></tr>
581583
<tr><td>

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,10 @@ export function generateManifestFromBundles(
486486
manifest.preloader = bundleFileName;
487487
} else if (modulePaths.some((m) => /[/\\]qwik[/\\]dist[/\\]core\.[^/]*js$/.test(m))) {
488488
manifest.core = bundleFileName;
489+
} else if (
490+
modulePaths.some((m) => /[/\\]qwik[/\\]dist[/\\]qwikloader(\.debug)?\.[^/]*js$/.test(m))
491+
) {
492+
manifest.qwikLoader = bundleFileName;
489493
}
490494
}
491495

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

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,19 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) {
407407
debug(`transformedOutputs.clear()`);
408408
clientTransformedOutputs.clear();
409409
serverTransformedOutputs.clear();
410+
411+
if (opts.target === 'client') {
412+
const ql = await _ctx.resolve('@builder.io/qwik/qwikloader.js', undefined, {
413+
skipSelf: true,
414+
});
415+
if (ql) {
416+
_ctx.emitFile({
417+
id: ql.id,
418+
type: 'chunk',
419+
preserveSignature: 'allow-extension',
420+
});
421+
}
422+
}
410423
};
411424

412425
const getIsServer = (viteOpts?: { ssr?: boolean }) => {
@@ -880,12 +893,13 @@ export const isDev = ${JSON.stringify(isDev)};
880893
if (manifest?.manifestHash) {
881894
serverManifest = {
882895
manifestHash: manifest.manifestHash,
883-
injections: manifest.injections,
884-
bundleGraph: manifest.bundleGraph,
885-
mapping: manifest.mapping,
886-
preloader: manifest.preloader,
887896
core: manifest.core,
897+
preloader: manifest.preloader,
898+
qwikLoader: manifest.qwikLoader,
888899
bundleGraphAsset: manifest.bundleGraphAsset,
900+
injections: manifest.injections,
901+
mapping: manifest.mapping,
902+
bundleGraph: manifest.bundleGraph,
889903
};
890904
}
891905
return `// @qwik-client-manifest
@@ -924,11 +938,12 @@ export const manifest = ${JSON.stringify(serverManifest)};\n`;
924938
function manualChunks(id: string, { getModuleInfo }: Rollup.ManualChunkMeta) {
925939
// The preloader has to stay in a separate chunk if it's a client build
926940
// the vite preload helper must be included or to prevent breaking circular dependencies
927-
if (
928-
opts.target === 'client' &&
929-
(id.endsWith(QWIK_PRELOADER_REAL_ID) || id === '\0vite/preload-helper.js')
930-
) {
931-
return 'qwik-preloader';
941+
if (opts.target === 'client') {
942+
if (id.endsWith(QWIK_PRELOADER_REAL_ID) || id === '\0vite/preload-helper.js') {
943+
return 'qwik-preloader';
944+
} else if (/qwik[\\/]dist[\\/]qwikloader\.js$/.test(id)) {
945+
return 'qwik-loader';
946+
}
932947
}
933948

934949
const module = getModuleInfo(id)!;

0 commit comments

Comments
 (0)