Skip to content

perf(qwikloader): put qwikloader in a separate bundle #7613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/empty-sites-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@builder.io/qwik': patch
---

: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.
4 changes: 0 additions & 4 deletions packages/docs/src/entry.ssr.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ export default function (opts: RenderToStreamOptions) {
}
}
return renderToStream(<Root />, {
qwikLoader: {
// The docs can be long so make sure to intercept events before the end of the document.
position: 'top',
},
...opts,
containerAttributes: {
lang: 'en',
Expand Down
3 changes: 3 additions & 0 deletions packages/docs/src/repl/bundled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import qCoreMinMjs from '../../node_modules/@builder.io/qwik/dist/core.min.mjs?r
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';
// we use the debug version for the repl so it's understandable
import qQwikLoaderJs from '../../node_modules/@builder.io/qwik/dist/qwikloader.debug.js?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';
Expand Down Expand Up @@ -57,6 +59,7 @@ export const bundled: PkgUrls = {
'/dist/server.cjs': qServerCjs,
'/dist/server.d.ts': qServerDts,
'/dist/preloader.mjs': qPreloaderMjs,
'/dist/qwikloader.js': qQwikLoaderJs,
'/bindings/qwik.wasm.cjs': qWasmCjs,
'/bindings/qwik_wasm_bg.wasm': qWasmBinUrl,
},
Expand Down
8 changes: 7 additions & 1 deletion packages/docs/src/repl/worker/repl-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,13 @@ export const replResolver = (options: ReplInputOptions, buildMode: 'client' | 's
return rsp.text();
}
}

// this id is unchanged because it's an entry point
if (id === '@builder.io/qwik/qwikloader.js') {
const rsp = await depResponse('@builder.io/qwik', '/qwikloader.js');
if (rsp) {
return rsp.text();
}
}
// We're the fallback, we know all the files
if (/\.[jt]sx?$/.test(id)) {
throw new Error(`load: unknown module ${id}`);
Expand Down
4 changes: 2 additions & 2 deletions packages/docs/src/routes/api/qwik-optimizer/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,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<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>",
"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>",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts",
"mdFile": "qwik.qwikmanifest.md"
},
Expand Down Expand Up @@ -667,7 +667,7 @@
}
],
"kind": "TypeAlias",
"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)",
"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)",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts",
"mdFile": "qwik.serverqwikmanifest.md"
},
Expand Down
16 changes: 16 additions & 0 deletions packages/docs/src/routes/api/qwik-optimizer/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1640,6 +1640,21 @@ _(Optional)_ The preloader bundle fileName
</td></tr>
<tr><td>

[qwikLoader?](#)

</td><td>

</td><td>

string

</td><td>

_(Optional)_ The Qwik loader bundle fileName

</td></tr>
<tr><td>

[symbols](#)

</td><td>
Expand Down Expand Up @@ -2764,6 +2779,7 @@ export type ServerQwikManifest = Pick<
| "mapping"
| "preloader"
| "core"
| "qwikLoader"
>;
```

Expand Down
2 changes: 1 addition & 1 deletion packages/docs/src/routes/api/qwik-server/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
}
],
"kind": "Interface",
"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>",
"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>",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/server/types.ts",
"mdFile": "qwik.qwikloaderoptions.md"
},
Expand Down
4 changes: 3 additions & 1 deletion packages/docs/src/routes/api/qwik-server/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,9 @@ Description

</td><td>

_(Optional)_
_(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.

Defaults to `'auto'`.

</td></tr>
<tr><td>
Expand Down
4 changes: 4 additions & 0 deletions packages/qwik/src/optimizer/src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,10 @@ export function generateManifestFromBundles(
manifest.preloader = bundleFileName;
} else if (modulePaths.some((m) => /[/\\]qwik[/\\]dist[/\\]core\.[^/]*js$/.test(m))) {
manifest.core = bundleFileName;
} else if (
modulePaths.some((m) => /[/\\]qwik[/\\]dist[/\\]qwikloader(\.debug)?\.[^/]*js$/.test(m))
) {
manifest.qwikLoader = bundleFileName;
}
}

Expand Down
33 changes: 24 additions & 9 deletions packages/qwik/src/optimizer/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,19 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) {
debug(`transformedOutputs.clear()`);
clientTransformedOutputs.clear();
serverTransformedOutputs.clear();

if (opts.target === 'client') {
const ql = await _ctx.resolve('@builder.io/qwik/qwikloader.js', undefined, {
skipSelf: true,
});
if (ql) {
_ctx.emitFile({
id: ql.id,
type: 'chunk',
preserveSignature: 'allow-extension',
});
}
}
};

const getIsServer = (viteOpts?: { ssr?: boolean }) => {
Expand Down Expand Up @@ -880,12 +893,13 @@ export const isDev = ${JSON.stringify(isDev)};
if (manifest?.manifestHash) {
serverManifest = {
manifestHash: manifest.manifestHash,
injections: manifest.injections,
bundleGraph: manifest.bundleGraph,
mapping: manifest.mapping,
preloader: manifest.preloader,
core: manifest.core,
preloader: manifest.preloader,
qwikLoader: manifest.qwikLoader,
bundleGraphAsset: manifest.bundleGraphAsset,
injections: manifest.injections,
mapping: manifest.mapping,
bundleGraph: manifest.bundleGraph,
};
}
return `// @qwik-client-manifest
Expand Down Expand Up @@ -924,11 +938,12 @@ export const manifest = ${JSON.stringify(serverManifest)};\n`;
function manualChunks(id: string, { getModuleInfo }: Rollup.ManualChunkMeta) {
// 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';
if (opts.target === 'client') {
if (id.endsWith(QWIK_PRELOADER_REAL_ID) || id === '\0vite/preload-helper.js') {
return 'qwik-preloader';
} else if (/qwik[\\/]dist[\\/]qwikloader\.js$/.test(id)) {
return 'qwik-loader';
}
}

const module = getModuleInfo(id)!;
Expand Down
3 changes: 2 additions & 1 deletion packages/qwik/src/optimizer/src/qwik.optimizer.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ export interface QwikManifest {
[name: string]: string;
};
preloader?: string;
qwikLoader?: string;
symbols: {
[symbolName: string]: QwikSymbol;
};
Expand Down Expand Up @@ -362,7 +363,7 @@ export { SegmentEntryStrategy as HookEntryStrategy }
export { SegmentEntryStrategy }

// @public
export type ServerQwikManifest = Pick<QwikManifest, 'manifestHash' | 'injections' | 'bundleGraph' | 'bundleGraphAsset' | 'mapping' | 'preloader' | 'core'>;
export type ServerQwikManifest = Pick<QwikManifest, 'manifestHash' | 'injections' | 'bundleGraph' | 'bundleGraphAsset' | 'mapping' | 'preloader' | 'core' | 'qwikLoader'>;

// @public (undocumented)
export interface SingleEntryStrategy {
Expand Down
3 changes: 3 additions & 0 deletions packages/qwik/src/optimizer/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ export interface QwikManifest {
preloader?: string;
/** The Qwik core bundle fileName */
core?: string;
/** The Qwik loader bundle fileName */
qwikLoader?: string;
/** CSS etc to inject in the document head */
injections?: GlobalInjections[];
/** The version of the manifest */
Expand Down Expand Up @@ -263,6 +265,7 @@ export type ServerQwikManifest = Pick<
| 'mapping'
| 'preloader'
| 'core'
| 'qwikLoader'
>;

/**
Expand Down
Loading