From b05b7aeedc163f817a21dfe3fb9214733a854e9c Mon Sep 17 00:00:00 2001
From: Wout Mertens
Date: Thu, 22 Feb 2024 13:32:06 +0100
Subject: [PATCH] refactor(qwik-loader): optimize + remove cruft
- qEvents is no longer used
- qwikloader.optimize.js is the same as regular, remove
- some tricks to allow smaller code after minify
- remove @vite-ignore comment
---
.../docs/src/routes/api/qwik-server/api.json | 2 +-
.../docs/src/routes/api/qwik-server/index.md | 1 -
packages/qwik/package.json | 2 -
packages/qwik/src/core/container/pause.ts | 4 +-
.../qwik/src/core/container/store.unit.tsx | 3 +-
packages/qwik/src/core/render/dom/visitor.ts | 4 +-
packages/qwik/src/qwikloader.ts | 56 ++++++------
packages/qwik/src/server/api.md | 2 -
packages/qwik/src/server/render.ts | 9 +-
packages/qwik/src/server/scripts.ts | 8 --
packages/qwik/src/server/types.ts | 1 -
scripts/submodule-qwikloader.ts | 91 ++++---------------
starters/apps/e2e/src/entry.ssr.tsx | 1 -
13 files changed, 55 insertions(+), 129 deletions(-)
diff --git a/packages/docs/src/routes/api/qwik-server/api.json b/packages/docs/src/routes/api/qwik-server/api.json
index 97c865a19c3..0085d30f9e7 100644
--- a/packages/docs/src/routes/api/qwik-server/api.json
+++ b/packages/docs/src/routes/api/qwik-server/api.json
@@ -124,7 +124,7 @@
}
],
"kind": "Interface",
- "content": "```typescript\nexport interface QwikLoaderOptions \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [events?](#) | | string\\[\\] | _(Optional)_ |\n| [include?](#) | | 'always' \\| 'never' \\| 'auto' | _(Optional)_ |\n| [position?](#) | | 'top' \\| 'bottom' | _(Optional)_ |",
+ "content": "```typescript\nexport interface QwikLoaderOptions \n```\n\n\n| Property | Modifiers | Type | Description |\n| --- | --- | --- | --- |\n| [include?](#) | | 'always' \\| 'never' \\| 'auto' | _(Optional)_ |\n| [position?](#) | | 'top' \\| 'bottom' | _(Optional)_ |",
"editUrl": "https://github.com/BuilderIO/qwik/tree/main/packages/qwik/src/server/types.ts",
"mdFile": "qwik.qwikloaderoptions.md"
},
diff --git a/packages/docs/src/routes/api/qwik-server/index.md b/packages/docs/src/routes/api/qwik-server/index.md
index 179b14d5572..0a30850c266 100644
--- a/packages/docs/src/routes/api/qwik-server/index.md
+++ b/packages/docs/src/routes/api/qwik-server/index.md
@@ -138,7 +138,6 @@ export interface QwikLoaderOptions
| Property | Modifiers | Type | Description |
| -------------- | --------- | ----------------------------- | ------------ |
-| [events?](#) | | string[] | _(Optional)_ |
| [include?](#) | | 'always' \| 'never' \| 'auto' | _(Optional)_ |
| [position?](#) | | 'top' \| 'bottom' | _(Optional)_ |
diff --git a/packages/qwik/package.json b/packages/qwik/package.json
index 0c37755f3a8..1fc5354364c 100644
--- a/packages/qwik/package.json
+++ b/packages/qwik/package.json
@@ -161,8 +161,6 @@
"qwik-prefetch.js",
"qwikloader.js",
"qwikloader.debug.js",
- "qwikloader.optimize.js",
- "qwikloader.optimize.debug.js",
"qwik-cli.cjs",
"server.cjs",
"server.mjs",
diff --git a/packages/qwik/src/core/container/pause.ts b/packages/qwik/src/core/container/pause.ts
index 5bbba9293e5..c55d796d02c 100644
--- a/packages/qwik/src/core/container/pause.ts
+++ b/packages/qwik/src/core/container/pause.ts
@@ -196,9 +196,7 @@ export const pauseContainer = async (
// Emit event registration
const extraListeners = Array.from(containerState.$events$, (s) => JSON.stringify(s));
const eventsScript = doc.createElement('script');
- eventsScript.textContent = `window.qwikevents||=[];window.qwikevents.push(${extraListeners.join(
- ', '
- )})`;
+ eventsScript.textContent = `(window.qwikevents||=[]).push(${extraListeners.join(', ')})`;
parentJSON.appendChild(eventsScript);
return data;
diff --git a/packages/qwik/src/core/container/store.unit.tsx b/packages/qwik/src/core/container/store.unit.tsx
index 6d6eb468730..7cfe4a90b88 100644
--- a/packages/qwik/src/core/container/store.unit.tsx
+++ b/packages/qwik/src/core/container/store.unit.tsx
@@ -49,8 +49,7 @@ test.skip('should serialize content', async () => {
`
);
diff --git a/packages/qwik/src/core/render/dom/visitor.ts b/packages/qwik/src/core/render/dom/visitor.ts
index 0912e24fdb8..17321fcc81c 100644
--- a/packages/qwik/src/core/render/dom/visitor.ts
+++ b/packages/qwik/src/core/render/dom/visitor.ts
@@ -1235,8 +1235,8 @@ export const registerQwikEvent = (prop: string) => {
if (!qTest) {
const eventName = getEventName(prop);
try {
- const qwikevents = ((globalThis as any).qwikevents ||= []);
- qwikevents.push(eventName);
+ // This is managed by qwik-loader
+ ((globalThis as any).qwikevents ||= []).push(eventName);
} catch (err) {
logWarn(err);
}
diff --git a/packages/qwik/src/qwikloader.ts b/packages/qwik/src/qwikloader.ts
index 5eb65d5fa54..e16c4830e5f 100644
--- a/packages/qwik/src/qwikloader.ts
+++ b/packages/qwik/src/qwikloader.ts
@@ -16,28 +16,32 @@ export const qwikLoader = (doc: Document, hasInitialized?: number) => {
const win = window as any;
const events = new Set();
+ // Some shortenings for minification
+ const replace = 'replace';
+ const forEach = 'forEach';
+ const target = 'target';
+ const getAttribute = 'getAttribute';
+ const isConnected = 'isConnected';
+ const qvisible = 'qvisible';
+ const Q_JSON = '_qwikjson_';
const querySelectorAll = (query: string) => {
return doc.querySelectorAll(query);
};
const broadcast = (infix: string, ev: Event, type = ev.type) => {
- querySelectorAll('[on' + infix + '\\:' + type + ']').forEach((target) =>
- dispatch(target, infix, ev, type)
+ querySelectorAll('[on' + infix + '\\:' + type + ']')[forEach]((el) =>
+ dispatch(el, infix, ev, type)
);
};
- const getAttribute = (el: Element, name: string) => {
- return el.getAttribute(name);
- };
-
const resolveContainer = (containerEl: Element) => {
- if ((containerEl as QContainerElement)['_qwikjson_'] === undefined) {
+ if ((containerEl as QContainerElement)[Q_JSON] === undefined) {
const parentJSON = containerEl === doc.documentElement ? doc.body : containerEl;
let script = parentJSON.lastElementChild;
while (script) {
- if (script.tagName === 'SCRIPT' && getAttribute(script, 'type') === 'qwik/json') {
- (containerEl as QContainerElement)['_qwikjson_'] = JSON.parse(
- script.textContent!.replace(/\\x3C(\/?script)/gi, '<$1')
+ if (script.tagName === 'SCRIPT' && script[getAttribute]('type') === 'qwik/json') {
+ (containerEl as QContainerElement)[Q_JSON] = JSON.parse(
+ script.textContent/gi, '<$1')
);
break;
}
@@ -57,21 +61,21 @@ export const qwikLoader = (doc: Document, hasInitialized?: number) => {
ev.preventDefault();
}
const ctx = (element as any)['_qc_'] as QContext | undefined;
- const relevantListeners = ctx?.li.filter((li) => li[0] === attrName);
+ const relevantListeners = ctx && ctx.li.filter((li) => li[0] === attrName);
if (relevantListeners && relevantListeners.length > 0) {
for (const listener of relevantListeners) {
// listener[1] holds the QRL
- await listener[1].getFn([element, ev], () => element.isConnected)(ev, element);
+ await listener[1].getFn([element, ev], () => element[isConnected])(ev, element);
}
return;
}
- const attrValue = getAttribute(element, attrName);
+ const attrValue = element[getAttribute](attrName);
if (attrValue) {
const container = element.closest('[q\\:container]')!;
- const base = new URL(getAttribute(container, 'q:base')!, doc.baseURI);
+ const base = new URL(container[getAttribute]('q:base')!, doc.baseURI);
for (const qrl of attrValue.split('\n')) {
const url = new URL(qrl, base);
- const symbolName = url.hash.replace(/^#?([^?[|]*).*$/, '$1') || 'default';
+ const symbolName = url.hash[replace](/^#?([^?[|]*).*$/, '$1') || 'default';
const reqTime = performance.now();
let handler: any;
const isSync = qrl.startsWith('#');
@@ -83,7 +87,7 @@ export const qwikLoader = (doc: Document, hasInitialized?: number) => {
handler = (await module)[symbolName];
}
const previousCtx = (doc as any)[Q_CONTEXT];
- if (element.isConnected) {
+ if (element[isConnected]) {
try {
(doc as any)[Q_CONTEXT] = [element, ev, url];
isSync ||
@@ -105,7 +109,7 @@ export const qwikLoader = (doc: Document, hasInitialized?: number) => {
doc.dispatchEvent(createEvent(eventName, detail));
};
- const camelToKebab = (str: string) => str.replace(/([A-Z])/g, (a) => '-' + a.toLowerCase());
+ const camelToKebab = (str: string) => str[replace](/([A-Z])/g, (a) => '-' + a.toLowerCase());
/**
* Event handler responsible for processing browser events.
@@ -118,10 +122,10 @@ export const qwikLoader = (doc: Document, hasInitialized?: number) => {
const processDocumentEvent = async (ev: Event) => {
// eslint-disable-next-line prefer-const
let type = camelToKebab(ev.type);
- let element = ev.target as Element | null;
+ let element = ev[target] as Element | null;
broadcast('-document', ev, type);
- while (element && element.getAttribute) {
+ while (element && element[getAttribute]) {
await dispatch(element, '', ev, type);
element = ev.bubbles && ev.cancelBubble !== true ? element.parentElement : null;
}
@@ -141,17 +145,17 @@ export const qwikLoader = (doc: Document, hasInitialized?: number) => {
const riC = win.requestIdleCallback ?? win.setTimeout;
riC.bind(win)(() => emitEvent('qidle'));
- if (events.has('qvisible')) {
- const results = querySelectorAll('[on\\:qvisible]');
+ if (events.has(qvisible)) {
+ const results = querySelectorAll('[on\\:' + qvisible + ']');
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
- observer.unobserve(entry.target);
- dispatch(entry.target, '', createEvent('qvisible', entry));
+ observer.unobserve(entry[target]);
+ dispatch(entry[target], '', createEvent(qvisible, entry));
}
}
});
- results.forEach((el) => observer.observe(el));
+ results[forEach]((el) => observer.observe(el));
}
}
};
@@ -176,8 +180,8 @@ export const qwikLoader = (doc: Document, hasInitialized?: number) => {
};
if (!(Q_CONTEXT in doc)) {
- // Mark qwik-loader presence
- (doc as any)[Q_CONTEXT] = undefined;
+ // Mark qwik-loader presence but falsy
+ (doc as any)[Q_CONTEXT] = 0;
const qwikevents = win.qwikevents;
// If `qwikEvents` is an array, process it.
if (Array.isArray(qwikevents)) {
diff --git a/packages/qwik/src/server/api.md b/packages/qwik/src/server/api.md
index b40cf5c138c..b242cb194a2 100644
--- a/packages/qwik/src/server/api.md
+++ b/packages/qwik/src/server/api.md
@@ -68,8 +68,6 @@ export interface PrefetchStrategy {
// @public (undocumented)
export interface QwikLoaderOptions {
- // (undocumented)
- events?: string[];
// (undocumented)
include?: 'always' | 'never' | 'auto';
// (undocumented)
diff --git a/packages/qwik/src/server/render.ts b/packages/qwik/src/server/render.ts
index 4f9770b6f76..661c70c0e0e 100644
--- a/packages/qwik/src/server/render.ts
+++ b/packages/qwik/src/server/render.ts
@@ -195,7 +195,6 @@ export async function renderToStream(
const includeLoader = includeMode === 'always' || (includeMode === 'auto' && needLoader);
if (includeLoader) {
const qwikLoaderScript = getQwikLoaderScript({
- events: opts.qwikLoader?.events,
debug: opts.debug,
});
children.push(
@@ -207,12 +206,12 @@ export async function renderToStream(
);
}
+ // We emit the events separately so other qwikloaders can see them
const extraListeners = Array.from(containerState.$events$, (s) => JSON.stringify(s));
if (extraListeners.length > 0) {
- let content = `window.qwikevents.push(${extraListeners.join(', ')})`;
- if (!includeLoader) {
- content = `window.qwikevents||=[];${content}`;
- }
+ const content =
+ (includeLoader ? `window.qwikevents` : `(window.qwikevents||=[])`) +
+ `.push(${extraListeners.join(', ')})`;
children.push(
jsx('script', {
dangerouslySetInnerHTML: content,
diff --git a/packages/qwik/src/server/scripts.ts b/packages/qwik/src/server/scripts.ts
index 2d7d25138f3..f50b16480e2 100644
--- a/packages/qwik/src/server/scripts.ts
+++ b/packages/qwik/src/server/scripts.ts
@@ -1,7 +1,5 @@
const QWIK_LOADER_DEFAULT_MINIFIED: string = (globalThis as any).QWIK_LOADER_DEFAULT_MINIFIED;
const QWIK_LOADER_DEFAULT_DEBUG: string = (globalThis as any).QWIK_LOADER_DEFAULT_DEBUG;
-const QWIK_LOADER_OPTIMIZE_MINIFIED: string = (globalThis as any).QWIK_LOADER_OPTIMIZE_MINIFIED;
-const QWIK_LOADER_OPTIMIZE_DEBUG: string = (globalThis as any).QWIK_LOADER_OPTIMIZE_DEBUG;
/**
* Provides the `qwikloader.js` file as a string. Useful for tooling to inline the qwikloader script
@@ -10,12 +8,6 @@ const QWIK_LOADER_OPTIMIZE_DEBUG: string = (globalThis as any).QWIK_LOADER_OPTIM
* @public
*/
export function getQwikLoaderScript(opts: { events?: string[]; debug?: boolean } = {}) {
- if (Array.isArray(opts.events) && opts.events.length > 0) {
- // inject exact known events used
- const loader = opts.debug ? QWIK_LOADER_OPTIMIZE_DEBUG : QWIK_LOADER_OPTIMIZE_MINIFIED;
- return loader.replace('window.qEvents', JSON.stringify(opts.events));
- }
-
// default script selector behavior
return opts.debug ? QWIK_LOADER_DEFAULT_DEBUG : QWIK_LOADER_DEFAULT_MINIFIED;
}
diff --git a/packages/qwik/src/server/types.ts b/packages/qwik/src/server/types.ts
index 9bf6afa81fb..cd8acfd5561 100644
--- a/packages/qwik/src/server/types.ts
+++ b/packages/qwik/src/server/types.ts
@@ -102,7 +102,6 @@ export interface RenderResult {
/** @public */
export interface QwikLoaderOptions {
- events?: string[];
include?: 'always' | 'never' | 'auto';
position?: 'top' | 'bottom';
}
diff --git a/scripts/submodule-qwikloader.ts b/scripts/submodule-qwikloader.ts
index b9913372299..902f0dece5c 100644
--- a/scripts/submodule-qwikloader.ts
+++ b/scripts/submodule-qwikloader.ts
@@ -98,82 +98,31 @@ export async function submoduleQwikLoader(config: BuildConfig) {
],
};
- const optimizeMinified: OutputOptions = {
- // QWIK_LOADER_OPTIMIZE_MINIFIED
- dir: config.distQwikPkgDir,
- format: 'es',
- entryFileNames: `qwikloader.optimize.js`,
- exports: 'none',
- intro: `(()=>{`,
- outro: `})()`,
- plugins: [
- terser({
- compress: {
- global_defs: {
- 'window.BuildEvents': true,
- },
- keep_fargs: false,
- unsafe: true,
- passes: 2,
- },
- format: {
- comments: /@vite-ignore/g,
- },
- }),
- ],
- };
-
- const optimizeDebug: OutputOptions = {
- // QWIK_LOADER_OPTIMIZE_DEBUG
- dir: config.distQwikPkgDir,
- format: 'es',
- entryFileNames: `qwikloader.optimize.debug.js`,
- exports: 'none',
- intro: `(()=>{`,
- outro: `})()`,
- plugins: [
- terser({
- compress: {
- global_defs: {
- 'window.BuildEvents': true,
- },
- inline: false,
- join_vars: false,
- loops: false,
- sequences: false,
- },
- format: {
- comments: /@vite-ignore/g,
- beautify: true,
- braces: true,
- },
- mangle: false,
- }),
- ],
- };
-
const build = await rollup(input);
- await Promise.all([
- build.write(defaultMinified),
- build.write(defaultDebug),
- build.write(optimizeMinified),
- build.write(optimizeDebug),
- ]);
+ await Promise.all([build.write(defaultMinified), build.write(defaultDebug)]);
await generateLoaderSubmodule(config);
- const optimizeFileSize = await fileSize(join(config.distQwikPkgDir, 'qwikloader.optimize.js'));
- console.log(`🸠qwikloader:`, optimizeFileSize);
+ const loaderSize = await fileSize(join(config.distQwikPkgDir, 'qwikloader.js'));
+ console.log(`🸠qwikloader:`, loaderSize);
}
+const getLoaderJsonString = async (config: BuildConfig, name: string) => {
+ const filePath = join(config.distQwikPkgDir, name);
+ const content = await readFile(filePath, 'utf-8');
+ // Remove vite comments and leading/trailing whitespace
+ let cleaned = content.trim().replace(/\n?\/\*\s*@vite[^*]+\*\/\n?/g, '');
+ if (cleaned.endsWith(';')) {
+ cleaned = cleaned.slice(0, -1);
+ }
+ return JSON.stringify(cleaned);
+};
/** Load each of the qwik scripts to be inlined with esbuild "define" as const variables. */
export async function inlineQwikScriptsEsBuild(config: BuildConfig) {
const variableToFileMap = [
['QWIK_LOADER_DEFAULT_MINIFIED', 'qwikloader.js'],
['QWIK_LOADER_DEFAULT_DEBUG', 'qwikloader.debug.js'],
- ['QWIK_LOADER_OPTIMIZE_MINIFIED', 'qwikloader.optimize.js'],
- ['QWIK_LOADER_OPTIMIZE_DEBUG', 'qwikloader.optimize.debug.js'],
];
const define: { [varName: string]: string } = {};
@@ -181,9 +130,7 @@ export async function inlineQwikScriptsEsBuild(config: BuildConfig) {
await Promise.all(
variableToFileMap.map(async (varToFile) => {
const varName = `globalThis.${varToFile[0]}`;
- const filePath = join(config.distQwikPkgDir, varToFile[1]);
- const content = await readFile(filePath, 'utf-8');
- define[varName] = JSON.stringify(content.trim());
+ define[varName] = await getLoaderJsonString(config, varToFile[1]);
})
);
@@ -193,15 +140,9 @@ export async function inlineQwikScriptsEsBuild(config: BuildConfig) {
async function generateLoaderSubmodule(config: BuildConfig) {
const loaderDistDir = join(config.distQwikPkgDir, 'loader');
- const loaderCode = await readFile(join(config.distQwikPkgDir, 'qwikloader.js'), 'utf-8');
- const loaderDebugCode = await readFile(
- join(config.distQwikPkgDir, 'qwikloader.debug.js'),
- 'utf-8'
- );
-
const code = [
- `const QWIK_LOADER = ${JSON.stringify(loaderCode.trim())};`,
- `const QWIK_LOADER_DEBUG = ${JSON.stringify(loaderDebugCode.trim())};`,
+ `const QWIK_LOADER = ${await getLoaderJsonString(config, 'qwikloader.js')};`,
+ `const QWIK_LOADER_DEBUG = ${await getLoaderJsonString(config, 'qwikloader.debug.js')};`,
];
const esmCode = [...code, `export { QWIK_LOADER, QWIK_LOADER_DEBUG };`];
diff --git a/starters/apps/e2e/src/entry.ssr.tsx b/starters/apps/e2e/src/entry.ssr.tsx
index 813ce2916ed..1e6448df0d3 100644
--- a/starters/apps/e2e/src/entry.ssr.tsx
+++ b/starters/apps/e2e/src/entry.ssr.tsx
@@ -99,7 +99,6 @@ export default function (opts: RenderToStreamOptions) {
qwikLoader: {
include:
url.searchParams.get("loader") === "false" ? "never" : "auto",
- events: ["click"],
},
...opts,
},