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![replace](/\\x3C(\/?script)/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, },