Skip to content

Commit 76c42a7

Browse files
committed
perf(preloader): listen to qwikloader for early preloading
1 parent adf20ca commit 76c42a7

File tree

11 files changed

+69
-17
lines changed

11 files changed

+69
-17
lines changed

packages/docs/src/repl/worker/repl-request-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ const injectDevHtml = (clientId: string, html?: string) => {
133133
});
134134
135135
document.addEventListener('qsymbol', (ev) => {
136-
const symbolName = ev.detail;
136+
const symbolName = ev.detail?.symbol;
137137
sendToServerWindow({
138138
kind: 'symbol',
139139
scope: 'client',

packages/docs/src/root.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,7 @@ export default component$(() => {
7676

7777
export function collectSymbols() {
7878
(window as any).symbols = [];
79-
document.addEventListener('qsymbol', (e) => (window as any).symbols.push((e as any).detail));
79+
document.addEventListener('qsymbol', (e) =>
80+
(window as any).symbols.push((e as any).detail.symbol)
81+
);
8082
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2194,7 +2194,7 @@
21942194
}
21952195
],
21962196
"kind": "TypeAlias",
2197-
"content": "Emitted by qwik-loader when a module was lazily loaded\n\n\n```typescript\nexport type QwikSymbolEvent = CustomEvent<{\n symbol: string;\n element: Element;\n reqTime: number;\n}>;\n```",
2197+
"content": "Emitted by qwik-loader when a module was lazily loaded\n\n\n```typescript\nexport type QwikSymbolEvent = CustomEvent<{\n symbol: string;\n element: Element;\n reqTime: number;\n qBase?: string;\n qManifest?: string;\n qVersion?: string;\n href?: string;\n}>;\n```",
21982198
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik-events.ts",
21992199
"mdFile": "qwik.qwiksymbolevent.md"
22002200
},

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4383,6 +4383,10 @@ export type QwikSymbolEvent = CustomEvent<{
43834383
symbol: string;
43844384
element: Element;
43854385
reqTime: number;
4386+
qBase?: string;
4387+
qManifest?: string;
4388+
qVersion?: string;
4389+
href?: string;
43864390
}>;
43874391
```
43884392

packages/docs/src/routes/docs/(qwikcity)/guides/bundle/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ To collect symbol usage from a running application:
139139
```html
140140
<script>
141141
window.symbols = [];
142-
document.addEventListener('qsymbol', (e) => window.symbols.push(e.detail));
142+
document.addEventListener('qsymbol', (e) => window.symbols.push(e.detail.symbol));
143143
</script>
144144
```
145145
2. Perform some set of operations mimicking user behavior.

packages/qwik/src/core/preloader/preloader.unit.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,5 @@ test('preloader script', () => {
2121
* dereference objects etc, but that actually results in worse compression
2222
*/
2323
const compressed = compress(Buffer.from(preLoader), { mode: 1, quality: 11 });
24-
expect(compressed.length).toBe(1722);
25-
expect(preLoader.length).toBe(5107);
24+
expect([compressed.length, preLoader.length]).toEqual([1785, 5298]);
2625
});

packages/qwik/src/core/preloader/queue.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
BundleImportState_Preload,
99
BundleImportState_Queued,
1010
} from './types';
11+
import type { QwikSymbolEvent } from '../render/jsx/types/jsx-qwik-events';
1112

1213
export const bundles: BundleImports = new Map();
1314
export let shouldResetFactor: boolean;
@@ -184,11 +185,11 @@ export const adjustProbabilities = (
184185
* We can multiply this chance together with all other bundle adjustments to get the chance
185186
* that a dep will be loaded given all the chances of the other bundles.
186187
*
187-
* But when we're very likely to load the current bundle, make the dynamic imports more likely
188+
* But when we're very likely to load the current bundle, make the dynamic imports very likely
188189
* too.
189190
*/
190191
const newInverseProbability =
191-
dep.$probability$ !== 1 && adjustFactor < 0.1 ? 0.05 : 1 - dep.$probability$ * probability;
192+
dep.$probability$ !== 1 && adjustFactor < 0.1 ? 0.01 : 1 - dep.$probability$ * probability;
192193

193194
/** We need to undo the previous adjustment */
194195
const factor = newInverseProbability / prevAdjust;
@@ -229,3 +230,15 @@ export const preload = (name: string | (number | string)[], probability?: number
229230
trigger();
230231
}
231232
};
233+
234+
if (isBrowser) {
235+
// Get early hints from qwikloader
236+
document.addEventListener('qsymbol', (ev) => {
237+
const { symbol, href } = (ev as QwikSymbolEvent).detail;
238+
// the qrl class doesn't emit href, we don't need to preload
239+
if (href) {
240+
const hash = symbol.slice(symbol.lastIndexOf('_') + 1);
241+
preload(hash, 1);
242+
}
243+
});
244+
}

packages/qwik/src/core/qwik.core.api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,10 @@ export type QwikSymbolEvent = CustomEvent<{
781781
symbol: string;
782782
element: Element;
783783
reqTime: number;
784+
qBase?: string;
785+
qManifest?: string;
786+
qVersion?: string;
787+
href?: string;
784788
}>;
785789

786790
// @public @deprecated (undocumented)

packages/qwik/src/core/render/jsx/types/jsx-qwik-events.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,28 @@ import type { AllEventKeys } from './jsx-qwik-attributes';
33
/** Emitted by qwik-loader when an element becomes visible. Used by `useVisibleTask$` @public */
44
export type QwikVisibleEvent = CustomEvent<IntersectionObserverEntry>;
55
/** Emitted by qwik-loader when a module was lazily loaded @public */
6-
export type QwikSymbolEvent = CustomEvent<{ symbol: string; element: Element; reqTime: number }>;
6+
export type QwikSymbolEvent = CustomEvent<{
7+
symbol: string;
8+
element: Element;
9+
reqTime: number;
10+
qBase?: string;
11+
qManifest?: string;
12+
qVersion?: string;
13+
href?: string;
14+
}>;
715
/** Emitted by qwik-loader on document when the document first becomes interactive @public */
816
export type QwikInitEvent = CustomEvent<{}>;
917
/** Emitted by qwik-loader on document when the document first becomes idle @public */
1018
export type QwikIdleEvent = CustomEvent<{}>;
1119
/** Emitted by qwik-core on document when the a view transition start @public */
1220
export type QwikViewTransitionEvent = CustomEvent<ViewTransition>;
21+
/** Emitted by qwik-loader on document when there was an error loading a module @public */
22+
export type QwikErrorEvent = CustomEvent<
23+
{
24+
importError?: 'sync' | 'async' | 'no-symbol';
25+
error: unknown;
26+
} & QwikSymbolEvent['detail']
27+
>;
1328

1429
// Utility types for supporting autocompletion in union types
1530

packages/qwik/src/qwikloader.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import type { QwikSymbolEvent, QwikVisibleEvent } from './core/render/jsx/types/jsx-qwik-events';
1+
import type {
2+
QwikErrorEvent,
3+
QwikSymbolEvent,
4+
QwikVisibleEvent,
5+
} from './core/render/jsx/types/jsx-qwik-events';
26
import type { QContainerElement } from './core/container/container';
37
import type { QContext } from './core/state/context';
48

@@ -117,7 +121,15 @@ type qWindow = Window & {
117121
let importError: undefined | 'sync' | 'async' | 'no-symbol';
118122
let error: undefined | Error;
119123
const isSync = qrl.startsWith('#');
120-
const eventData = { qBase, qManifest, qVersion, href, symbol, element, reqTime };
124+
const eventData: QwikSymbolEvent['detail'] = {
125+
qBase,
126+
qManifest,
127+
qVersion,
128+
href,
129+
symbol,
130+
element,
131+
reqTime,
132+
};
121133
if (isSync) {
122134
const hash = container.getAttribute('q:instance')!;
123135
handler = ((doc as any)['qFuncs_' + hash] || [])[Number.parseInt(symbol)];
@@ -126,6 +138,7 @@ type qWindow = Window & {
126138
error = new Error('sym:' + symbol);
127139
}
128140
} else {
141+
emitEvent<QwikSymbolEvent>('qsymbol', eventData);
129142
const uri = url.href.split('#')[0];
130143
try {
131144
const module = import(/* @vite-ignore */ uri);
@@ -141,7 +154,11 @@ type qWindow = Window & {
141154
}
142155
}
143156
if (!handler) {
144-
emitEvent('qerror', { importError, error, ...eventData });
157+
emitEvent<QwikErrorEvent>('qerror', {
158+
importError,
159+
error,
160+
...eventData,
161+
});
145162
console.error(error);
146163
// break out of the loop if handler is not found
147164
break;
@@ -150,14 +167,13 @@ type qWindow = Window & {
150167
if (element.isConnected) {
151168
try {
152169
doc.__q_context__ = [element, ev, url];
153-
isSync || emitEvent<QwikSymbolEvent>('qsymbol', { ...eventData });
154170
const results = handler(ev, element);
155171
// only await if there is a promise returned
156172
if (isPromise(results)) {
157173
await results;
158174
}
159175
} catch (error) {
160-
emitEvent('qerror', { error, ...eventData });
176+
emitEvent<QwikErrorEvent>('qerror', { error, ...eventData });
161177
} finally {
162178
doc.__q_context__ = previousCtx;
163179
}

packages/qwik/src/qwikloader.unit.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ test('qwikloader script', () => {
2121
* dereference objects etc, but that actually results in worse compression
2222
*/
2323
const compressed = compress(Buffer.from(qwikLoader), { mode: 1, quality: 11 });
24-
expect(compressed.length).toBe(1428);
25-
expect(qwikLoader.length).toBe(3126);
24+
expect([compressed.length, qwikLoader.length]).toEqual([1421, 3118]);
2625
expect(qwikLoader).toMatchInlineSnapshot(
27-
`"(()=>{const t=document,e=window,n=new Set,o=new Set([t]);let r;const s=(t,e)=>Array.from(t.querySelectorAll(e)),a=t=>{const e=[];return o.forEach((n=>e.push(...s(n,t)))),e},i=t=>{w(t),s(t,"[q\\\\:shadowroot]").forEach((t=>{const e=t.shadowRoot;e&&i(e)}))},c=t=>t&&"function"==typeof t.then,l=(t,e,n=e.type)=>{a("[on"+t+"\\\\:"+n+"]").forEach((o=>b(o,t,e,n)))},f=e=>{if(void 0===e._qwikjson_){let n=(e===t.documentElement?t.body:e).lastElementChild;for(;n;){if("SCRIPT"===n.tagName&&"qwik/json"===n.getAttribute("type")){e._qwikjson_=JSON.parse(n.textContent.replace(/\\\\x3C(\\/?script)/gi,"<$1"));break}n=n.previousElementSibling}}},p=(t,e)=>new CustomEvent(t,{detail:e}),b=async(e,n,o,r=o.type)=>{const s="on"+n+":"+r;e.hasAttribute("preventdefault:"+r)&&o.preventDefault(),e.hasAttribute("stoppropagation:"+r)&&o.stopPropagation();const a=e._qc_,i=a&&a.li.filter((t=>t[0]===s));if(i&&i.length>0){for(const t of i){const n=t[1].getFn([e,o],(()=>e.isConnected))(o,e),r=o.cancelBubble;c(n)&&await n,r&&o.stopPropagation()}return}const l=e.getAttribute(s);if(l){const n=e.closest("[q\\\\:container]"),r=n.getAttribute("q:base"),s=n.getAttribute("q:version")||"unknown",a=n.getAttribute("q:manifest-hash")||"dev",i=new URL(r,t.baseURI);for(const p of l.split("\\n")){const l=new URL(p,i),b=l.href,h=l.hash.replace(/^#?([^?[|]*).*$/,"$1")||"default",q=performance.now();let _,d,y;const w=p.startsWith("#"),g={qBase:r,qManifest:a,qVersion:s,href:b,symbol:h,element:e,reqTime:q};if(w){const e=n.getAttribute("q:instance");_=(t["qFuncs_"+e]||[])[Number.parseInt(h)],_||(d="sync",y=Error("sym:"+h))}else{const t=l.href.split("#")[0];try{const e=import(t);f(n),_=(await e)[h],_||(d="no-symbol",y=Error(\`\${h} not in \${t}\`))}catch(t){d||(d="async"),y=t}}if(!_){u("qerror",{importError:d,error:y,...g}),console.error(y);break}const m=t.__q_context__;if(e.isConnected)try{t.__q_context__=[e,o,l],w||u("qsymbol",{...g});const n=_(o,e);c(n)&&await n}catch(t){u("qerror",{error:t,...g})}finally{t.__q_context__=m}}}},u=(e,n)=>{t.dispatchEvent(p(e,n))},h=t=>t.replace(/([A-Z])/g,(t=>"-"+t.toLowerCase())),q=async t=>{let e=h(t.type),n=t.target;for(l("-document",t,e);n&&n.getAttribute;){const o=b(n,"",t,e);let r=t.cancelBubble;c(o)&&await o,r=r||t.cancelBubble||n.hasAttribute("stoppropagation:"+t.type),n=t.bubbles&&!0!==r?n.parentElement:null}},_=t=>{l("-window",t,h(t.type))},d=()=>{var s;const c=t.readyState;if(!r&&("interactive"==c||"complete"==c)&&(o.forEach(i),r=1,u("qinit"),(null!=(s=e.requestIdleCallback)?s:e.setTimeout).bind(e)((()=>u("qidle"))),n.has("qvisible"))){const t=a("[on\\\\:qvisible]"),e=new IntersectionObserver((t=>{for(const n of t)n.isIntersecting&&(e.unobserve(n.target),b(n.target,"",p("qvisible",n)))}));t.forEach((t=>e.observe(t)))}},y=(t,e,n,o=!1)=>t.addEventListener(e,n,{capture:o,passive:!1}),w=(...t)=>{for(const r of t)"string"==typeof r?n.has(r)||(o.forEach((t=>y(t,r,q,!0))),y(e,r,_,!0),n.add(r)):o.has(r)||(n.forEach((t=>y(r,t,q,!0))),o.add(r))};if(!("__q_context__"in t)){t.__q_context__=0;const r=e.qwikevents;Array.isArray(r)&&w(...r),e.qwikevents={events:n,roots:o,push:w},y(t,"readystatechange",d),d()}})();"`
26+
`"(()=>{const t=document,e=window,n=new Set,o=new Set([t]);let r;const s=(t,e)=>Array.from(t.querySelectorAll(e)),a=t=>{const e=[];return o.forEach((n=>e.push(...s(n,t)))),e},i=t=>{w(t),s(t,"[q\\\\:shadowroot]").forEach((t=>{const e=t.shadowRoot;e&&i(e)}))},c=t=>t&&"function"==typeof t.then,l=(t,e,n=e.type)=>{a("[on"+t+"\\\\:"+n+"]").forEach((o=>b(o,t,e,n)))},f=e=>{if(void 0===e._qwikjson_){let n=(e===t.documentElement?t.body:e).lastElementChild;for(;n;){if("SCRIPT"===n.tagName&&"qwik/json"===n.getAttribute("type")){e._qwikjson_=JSON.parse(n.textContent.replace(/\\\\x3C(\\/?script)/gi,"<$1"));break}n=n.previousElementSibling}}},p=(t,e)=>new CustomEvent(t,{detail:e}),b=async(e,n,o,r=o.type)=>{const s="on"+n+":"+r;e.hasAttribute("preventdefault:"+r)&&o.preventDefault(),e.hasAttribute("stoppropagation:"+r)&&o.stopPropagation();const a=e._qc_,i=a&&a.li.filter((t=>t[0]===s));if(i&&i.length>0){for(const t of i){const n=t[1].getFn([e,o],(()=>e.isConnected))(o,e),r=o.cancelBubble;c(n)&&await n,r&&o.stopPropagation()}return}const l=e.getAttribute(s);if(l){const n=e.closest("[q\\\\:container]"),r=n.getAttribute("q:base"),s=n.getAttribute("q:version")||"unknown",a=n.getAttribute("q:manifest-hash")||"dev",i=new URL(r,t.baseURI);for(const p of l.split("\\n")){const l=new URL(p,i),b=l.href,h=l.hash.replace(/^#?([^?[|]*).*$/,"$1")||"default",q=performance.now();let _,d,y;const w=p.startsWith("#"),g={qBase:r,qManifest:a,qVersion:s,href:b,symbol:h,element:e,reqTime:q};if(w){const e=n.getAttribute("q:instance");_=(t["qFuncs_"+e]||[])[Number.parseInt(h)],_||(d="sync",y=Error("sym:"+h))}else{u("qsymbol",g);const t=l.href.split("#")[0];try{const e=import(t);f(n),_=(await e)[h],_||(d="no-symbol",y=Error(\`\${h} not in \${t}\`))}catch(t){d||(d="async"),y=t}}if(!_){u("qerror",{importError:d,error:y,...g}),console.error(y);break}const m=t.__q_context__;if(e.isConnected)try{t.__q_context__=[e,o,l];const n=_(o,e);c(n)&&await n}catch(t){u("qerror",{error:t,...g})}finally{t.__q_context__=m}}}},u=(e,n)=>{t.dispatchEvent(p(e,n))},h=t=>t.replace(/([A-Z])/g,(t=>"-"+t.toLowerCase())),q=async t=>{let e=h(t.type),n=t.target;for(l("-document",t,e);n&&n.getAttribute;){const o=b(n,"",t,e);let r=t.cancelBubble;c(o)&&await o,r=r||t.cancelBubble||n.hasAttribute("stoppropagation:"+t.type),n=t.bubbles&&!0!==r?n.parentElement:null}},_=t=>{l("-window",t,h(t.type))},d=()=>{var s;const c=t.readyState;if(!r&&("interactive"==c||"complete"==c)&&(o.forEach(i),r=1,u("qinit"),(null!=(s=e.requestIdleCallback)?s:e.setTimeout).bind(e)((()=>u("qidle"))),n.has("qvisible"))){const t=a("[on\\\\:qvisible]"),e=new IntersectionObserver((t=>{for(const n of t)n.isIntersecting&&(e.unobserve(n.target),b(n.target,"",p("qvisible",n)))}));t.forEach((t=>e.observe(t)))}},y=(t,e,n,o=!1)=>t.addEventListener(e,n,{capture:o,passive:!1}),w=(...t)=>{for(const r of t)"string"==typeof r?n.has(r)||(o.forEach((t=>y(t,r,q,!0))),y(e,r,_,!0),n.add(r)):o.has(r)||(n.forEach((t=>y(r,t,q,!0))),o.add(r))};if(!("__q_context__"in t)){t.__q_context__=0;const r=e.qwikevents;Array.isArray(r)&&w(...r),e.qwikevents={events:n,roots:o,push:w},y(t,"readystatechange",d),d()}})();"`
2827
);
2928
});

0 commit comments

Comments
 (0)