Skip to content

Commit 68774d1

Browse files
committed
fix(core): properly add qwik/preloader to the head
also handle non-html containers
1 parent da6e98f commit 68774d1

File tree

10 files changed

+76
-37
lines changed

10 files changed

+76
-37
lines changed

.changeset/empty-sites-tie.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

packages/docs/src/repl/bundled.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import qCoreInternalDts from '../../node_modules/@qwik.dev/core/dist/core-intern
1717
import qCoreMinMjs from '../../node_modules/@qwik.dev/core/dist/core.min.mjs?raw-source';
1818
import qPreloaderMjs from '../../node_modules/@qwik.dev/core/dist/preloader.mjs?raw-source';
1919
import qHandlersMjs from '../../node_modules/@qwik.dev/core/handlers.mjs?raw-source';
20-
// we use the debug version for the repl so it's understandable
21-
import qQwikLoaderJs from '../../node_modules/@builder.io/qwik/dist/qwikloader.debug.js?raw-source';
20+
import qQwikLoaderJs from '../../node_modules/@qwik.dev/core/dist/qwikloader.js?raw-source';
21+
import qQwikLoaderDebugJs from '../../node_modules/@qwik.dev/core/dist/qwikloader.debug.js?raw-source';
2222
import qCoreMjs from '../../node_modules/@qwik.dev/core/dist/core.mjs?raw-source';
2323
import qOptimizerCjs from '../../node_modules/@qwik.dev/core/dist/optimizer.cjs?raw-source';
2424
import qServerCjs from '../../node_modules/@qwik.dev/core/dist/server.cjs?raw-source';
@@ -69,6 +69,7 @@ export const bundled: PkgUrls = {
6969
'/dist/server.d.ts': qServerDts,
7070
'/dist/preloader.mjs': qPreloaderMjs,
7171
'/dist/qwikloader.js': qQwikLoaderJs,
72+
'/dist/qwikloader.debug.js': qQwikLoaderDebugJs,
7273
'/handlers.mjs': qHandlersMjs,
7374
'/bindings/qwik.wasm.cjs': qWasmCjs,
7475
'/bindings/qwik_wasm_bg.wasm': qWasmBinUrl,

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,11 @@ export const replResolver = (options: ReplInputOptions, buildMode: 'client' | 's
9898
}
9999
}
100100
// this id is unchanged because it's an entry point
101-
if (id === '@builder.io/qwik/qwikloader.js') {
102-
const rsp = await depResponse('@builder.io/qwik', '/qwikloader.js');
101+
if (id === '@qwik.dev/core/qwikloader.js') {
102+
const rsp = await depResponse(
103+
'@qwik.dev/core',
104+
`/qwikloader${options.debug ? '.debug' : ''}.js`
105+
);
103106
if (rsp) {
104107
return rsp.text();
105108
}

packages/qwik/src/core/ssr/ssr-render-jsx.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,16 @@ function processJSXNode(
185185

186186
enqueue(ssr.closeElement);
187187

188-
enqueue(ssr.emitQwikLoaderAtTopIfNeeded);
189188
if (type === 'head') {
190-
enqueue(ssr.emitPreloaderPre);
189+
ssr.emitQwikLoaderAtTopIfNeeded();
190+
ssr.emitPreloaderPre();
191191
enqueue(ssr.additionalHeadNodes);
192192
} else if (type === 'body') {
193193
enqueue(ssr.additionalBodyNodes);
194+
} else if (!ssr.isHtml && !(ssr as any)._didAddQwikLoader) {
195+
ssr.emitQwikLoaderAtTopIfNeeded();
196+
ssr.emitPreloaderPre();
197+
(ssr as any)._didAddQwikLoader = true;
194198
}
195199

196200
const children = jsx.children as JSXOutput;

packages/qwik/src/core/ssr/ssr-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export type SymbolToChunkResolver = (symbol: string) => string;
5656

5757
export interface SSRContainer extends Container {
5858
readonly tag: string;
59+
readonly isHtml: boolean;
5960
readonly writer: StreamWriter;
6061
readonly serializationCtx: SerializationContext;
6162
readonly symbolToChunkResolver: SymbolToChunkResolver;

packages/qwik/src/core/tests/render-api.spec.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ const defaultManifest: QwikManifest = {
7070
version: '1',
7171
preloader: 'preloader.js',
7272
};
73+
const manifestWithHelpers = {
74+
...defaultManifest,
75+
core: 'core.js',
76+
preloader: 'preloader.js',
77+
qwikLoader: 'qwik-loader.js',
78+
bundleGraphAsset: 'assets/bundle-graph.json',
79+
};
7380

7481
const ManyEventsComponent = component$(() => {
7582
useOn(
@@ -489,6 +496,31 @@ describe('render api', () => {
489496
const document = createDocument({ html: result.html });
490497
expect(document.body.firstChild?.nodeName.toLowerCase()).toEqual(testTag);
491498
});
499+
it('should render qwik loader and preloader for custom tag name', async () => {
500+
const testTag = 'test-tag';
501+
const result = await renderToStringAndSetPlatform(<Counter />, {
502+
containerTagName: testTag,
503+
manifest: manifestWithHelpers,
504+
});
505+
const document = createDocument({ html: result.html });
506+
const containerElement = document.body.firstChild;
507+
expect(containerElement?.nodeName.toLowerCase()).toEqual(testTag);
508+
expect(containerElement?.lastChild?.textContent ?? '').toContain('window.qwikevents');
509+
const scripts = document.querySelectorAll('script');
510+
expect(scripts[0]?.getAttribute('src')).toEqual('/build/qwik-loader.js');
511+
expect(scripts[1]?.innerHTML).toContain('/build/preloader.js');
512+
expect(scripts[4]?.innerHTML).toContain('/build/preloader.js');
513+
const links = document.querySelectorAll('link');
514+
expect(links[0]?.getAttribute('href')).toEqual('/build/qwik-loader.js');
515+
expect(links[0]?.getAttribute('rel')).toEqual('modulepreload');
516+
expect(links[1]?.getAttribute('href')).toEqual('/build/preloader.js');
517+
expect(links[1]?.getAttribute('rel')).toEqual('modulepreload');
518+
expect(links[2]?.getAttribute('href')).toEqual('/assets/bundle-graph.json');
519+
expect(links[2]?.getAttribute('rel')).toEqual('preload');
520+
expect(links[2]?.getAttribute('as')).toEqual('fetch');
521+
expect(links[3]?.getAttribute('href')).toEqual('/build/core.js');
522+
expect(links[3]?.getAttribute('rel')).toEqual('modulepreload');
523+
});
492524
it('should render custom container attributes', async () => {
493525
const testAttrName = 'test-attr';
494526
const testAttrValue = 'test-value';

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,8 +494,11 @@ export function generateManifestFromBundles(
494494
modulePaths.some((m) => /[/\\](core|qwik)[/\\]dist[/\\]core(.min|.prod)?\.[cm]js$/.test(m))
495495
) {
496496
manifest.core = bundleFileName;
497-
} else if (
498-
modulePaths.some((m) => /[/\\]qwik[/\\]dist[/\\]qwikloader(\.debug)?\.[^/]*js$/.test(m))
497+
}
498+
if (
499+
modulePaths.some((m) =>
500+
/[/\\](core|qwik)[/\\](dist[/\\])?qwikloader(\.debug)?\.[^/]*js$/.test(m)
501+
)
499502
) {
500503
manifest.qwikLoader = bundleFileName;
501504
}

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,6 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) {
9494
const serverTransformedOutputs = new Map<string, [TransformModule, string]>();
9595
const parentIds = new Map<string, string>();
9696

97-
const npmChunks = new Map<string, number>();
98-
9997
let internalOptimizer: Optimizer | null = null;
10098
let linter: QwikLinter | undefined = undefined;
10199
let diagnosticsCallback: (
@@ -414,10 +412,9 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) {
414412
debug(`transformedOutputs.clear()`);
415413
clientTransformedOutputs.clear();
416414
serverTransformedOutputs.clear();
417-
npmChunks.clear();
418415

419416
if (opts.target === 'client') {
420-
const ql = await _ctx.resolve('@builder.io/qwik/qwikloader.js', undefined, {
417+
const ql = await _ctx.resolve('@qwik.dev/core/qwikloader.js', undefined, {
421418
skipSelf: true,
422419
});
423420
if (ql) {
@@ -1097,7 +1094,7 @@ export const makeNormalizePath = (sys: OptimizerSystem) => (id: string) => {
10971094
if (isWin(sys.os)) {
10981095
// MIT https://github.com/sindresorhus/slash/blob/main/license
10991096
// Convert Windows backslash paths to slash paths: foo\\bar ➔ foo/bar
1100-
const isExtendedLengthPath = /^\\\\\?\\/.test(id);
1097+
const isExtendedLengthPath = id.startsWith('\\\\?\\');
11011098
if (!isExtendedLengthPath) {
11021099
const hasNonAscii = /[^\u0000-\u0080]+/.test(id); // eslint-disable-line no-control-regex
11031100
if (!hasNonAscii) {

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

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,29 @@ const simplifyPath = (base: string, path: string | null | undefined) => {
1919
return simplified.join('/');
2020
};
2121

22+
const getBase = (container: SSRContainer) => {
23+
let base = container.$buildBase$!;
24+
if (import.meta.env.DEV && !import.meta.env.TEST) {
25+
// Vite dev server active
26+
// in dev, all bundles are absolute paths from the base url, not /build
27+
base = import.meta.env.BASE_URL;
28+
}
29+
return base;
30+
};
31+
2232
export const preloaderPre = (
2333
container: SSRContainer,
2434
options: RenderToStreamOptions['preloader'],
2535
nonce?: string
2636
) => {
2737
const { resolvedManifest } = container;
28-
const base = container.$buildBase$!;
29-
const preloaderPath = simplifyPath(base, resolvedManifest?.manifest?.preloader);
38+
const base = getBase(container);
39+
const preloaderBundle = simplifyPath(base, resolvedManifest?.manifest?.preloader);
3040
let bundleGraphPath = resolvedManifest?.manifest.bundleGraphAsset;
3141
if (bundleGraphPath) {
3242
bundleGraphPath = (import.meta.env.BASE_URL || '/') + bundleGraphPath;
3343
}
34-
if (preloaderPath && bundleGraphPath && options !== false) {
44+
if (preloaderBundle && bundleGraphPath && options !== false) {
3545
const preloaderOpts: Parameters<typeof initPreloader>[1] =
3646
typeof options === 'object'
3747
? {
@@ -61,7 +71,7 @@ export const preloaderPre = (
6171
* We add modulepreloads even when the script is at the top because they already fire during
6272
* html download
6373
*/
64-
container.openElement('link', null, ['rel', 'modulepreload', 'href', preloaderPath]);
74+
container.openElement('link', null, ['rel', 'modulepreload', 'href', preloaderBundle]);
6575
container.closeElement();
6676
container.openElement('link', null, [
6777
'rel',
@@ -77,7 +87,7 @@ export const preloaderPre = (
7787

7888
const script =
7989
`let b=fetch("${bundleGraphPath}");` +
80-
`import("${preloaderPath}").then(({l})=>` +
90+
`import("${preloaderBundle}").then(({l})=>` +
8191
`l(${JSON.stringify(base)},b${optsStr})` +
8292
`);`;
8393
const scriptAttrs = ['type', 'module', 'async', true];
@@ -110,20 +120,11 @@ export const includePreloader = (
110120
);
111121
let allowed = ssrPreloads;
112122

113-
let base = container.$buildBase$!;
114-
if (import.meta.env.DEV) {
115-
// Vite dev server active
116-
// in dev, all bundles are absolute paths from the base url, not /build
117-
base = import.meta.env.BASE_URL;
118-
if (base.endsWith('/')) {
119-
base = base.slice(0, -1);
120-
}
121-
}
123+
const base = getBase(container);
122124

123125
const links = [];
124126

125127
const { resolvedManifest } = container;
126-
const manifestHash = resolvedManifest?.manifest.manifestHash;
127128
if (allowed) {
128129
const preloaderBundle = resolvedManifest?.manifest.preloader;
129130
const coreBundle = resolvedManifest?.manifest.core;
@@ -150,7 +151,7 @@ export const includePreloader = (
150151
}
151152
}
152153

153-
const preloaderPath = simplifyPath(base, manifestHash && resolvedManifest?.manifest.preloader);
154+
const preloaderBundle = simplifyPath(base, resolvedManifest?.manifest.preloader);
154155
const insertLinks = links.length
155156
? /**
156157
* We only use modulepreload links because they behave best. Older browsers can rely on the
@@ -166,11 +167,11 @@ export const includePreloader = (
166167
: '';
167168
// We are super careful not to interfere with the page loading.
168169
let script = insertLinks;
169-
if (preloaderPath) {
170+
if (preloaderBundle) {
170171
// First we wait for the onload event
171172
script +=
172173
`window.addEventListener('load',f=>{` +
173-
`f=_=>import("${preloaderPath}").then(({p})=>p(${JSON.stringify(referencedBundles)}));` +
174+
`f=_=>import("${preloaderBundle}").then(({p})=>p(${JSON.stringify(referencedBundles)}));` +
174175
// then we ask for idle callback
175176
`try{requestIdleCallback(f,{timeout:2000})}` +
176177
// some browsers don't support requestIdleCallback

packages/qwik/src/server/ssr-container.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ export type CleanupQueue = any[][];
166166

167167
class SSRContainer extends _SharedContainer implements ISSRContainer {
168168
public tag: string;
169+
public isHtml: boolean;
169170
public writer: StreamWriter;
170171
public timing: RenderToStreamResult['timing'];
171172
public resolvedManifest: ResolvedManifest;
@@ -234,6 +235,7 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
234235
);
235236
this.renderTimer = createTimer();
236237
this.tag = opts.tagName;
238+
this.isHtml = opts.tagName === 'html';
237239
this.writer = opts.writer;
238240
this.timing = opts.timing;
239241
this.$buildBase$ = opts.buildBase;
@@ -802,7 +804,7 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
802804
if (qwikLoaderBundle) {
803805
// always emit the preload+import. It will probably be used at some point on the site
804806
qwikLoaderBundle = this.$buildBase$ + qwikLoaderBundle;
805-
this.openElement('link', ['rel', 'preload', 'href', qwikLoaderBundle]);
807+
this.openElement('link', ['rel', 'modulepreload', 'href', qwikLoaderBundle]);
806808
this.closeElement();
807809
this.openElement('script', ['type', 'module', 'async', true, 'src', qwikLoaderBundle]);
808810
this.closeElement();

0 commit comments

Comments
 (0)