From 7f5272361f14b433669542d8aeed89445243d2bb Mon Sep 17 00:00:00 2001 From: easrng Date: Thu, 30 May 2024 09:03:22 -0400 Subject: [PATCH] optimize svg loading, run tsc in lint to check types, don't include extra copies of the fonts in the assets folder --- package.json | 2 +- rollup.config.js | 2 +- src/html/loading-screen.ts | 7 ++-- src/load-svg.ts | 78 +++++++++++++++++++------------------- tsconfig.json | 3 +- 5 files changed, 48 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index bd5db12..167a621 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "main": "index.js", "scripts": { "test": "jest test/*", - "lint": "eslint \"./src\" \"./test\" --ext .js,.ts", + "lint": "tsc && eslint \"./src\" \"./test\" --ext .js,.ts", "build": "rollup -c rollup.config.js", "watch": "rollup --watch -c rollup.config.js", "dev": "rollup --watch -c rollup.config.js & sirv ./ --port 8000 -D" diff --git a/rollup.config.js b/rollup.config.js index a95c49c..a6438dc 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -14,7 +14,7 @@ export default { typescript({noEmitOnError: !process.env.ROLLUP_WATCH}), copy({ targets: [ - { src: 'src/assets/**/*', dest: 'dist/assets' } + { src: 'src/assets/*', dest: 'dist/assets' } ] }) ] diff --git a/src/html/loading-screen.ts b/src/html/loading-screen.ts index 50f7038..a257650 100644 --- a/src/html/loading-screen.ts +++ b/src/html/loading-screen.ts @@ -43,13 +43,14 @@ const loadingScreenTemplate = h('template', position: absolute; top: 0; left: 0; - right: 100%; + right: 0; bottom: 0; background-color: rgba(22, 117, 206, 1); + transform: translateX(-100%); } #loading-bar-progress.active { - /* transition: right 0.05s linear; */ + /* transition: transform 0.05s linear; */ } #error-message { @@ -140,7 +141,7 @@ export class LoadingScreenElement extends HTMLElement { const progress = this.totalAssets === 0 ? 0 : this.loadedAssets / this.totalAssets; - loadingBarProgress.style.right = `${(1 - progress) * 100}%`; + loadingBarProgress.style.transform = `translateX(-${(1 - progress) * 100}%)`; } } diff --git a/src/load-svg.ts b/src/load-svg.ts index 70b969f..8e76b40 100644 --- a/src/load-svg.ts +++ b/src/load-svg.ts @@ -1,5 +1,4 @@ import Rectangle from './rectangle.js'; -import toBase64 from './util/to-base64.js'; const fonts = { 'Sans Serif': './assets/fonts/NotoSans-Medium.woff2', @@ -13,39 +12,39 @@ const fonts = { type FontName = keyof typeof fonts; -const loadedFonts: Record | null> = { - 'Sans Serif': null, - 'Serif': null, - 'Handwriting': null, - 'Marker': null, - 'Curly': null, - 'Pixel': null, - 'Scratch': null, -}; +const isFont = Object.prototype.hasOwnProperty.bind(fonts) as (font: unknown) => font is keyof typeof fonts; + +const fontPromises: Partial>> = {}; + +const fontURLs: Partial> = {}; // Load fonts for SVG costumes on-demand. -const loadFonts = async(fontNames: Iterable): Promise> => { +const loadFonts = async(fontNames: Iterable) => { + const promises: Promise[] = []; for (const name of fontNames) { - if (!Object.prototype.hasOwnProperty.call(loadedFonts, name)) { + if (!isFont(name)) { continue; } - if (loadedFonts[name as FontName] === null) { - loadedFonts[name as FontName] = fetch(import.meta.resolve(fonts[name as FontName])) - .then(response => response.blob()) - .then(blob => blob.arrayBuffer()) - .then(buffer => { - const base64 = toBase64(new Uint8Array(buffer)); - return `data:font/woff2;base64,${base64}`; - }); - } - } - - const fontURLs = {} as Record; - const fontPromises = await Promise.all(Object.values(loadedFonts)); - for (let i = 0; i < fontPromises.length; i++) { - fontURLs[Object.keys(loadedFonts)[i] as FontName] = fontPromises[i]!; + const cachedPromise = fontPromises[name]; + promises.push( + !cachedPromise ? + fontPromises[name] = fetch(import.meta.resolve(fonts[name])) + .then(response => response.blob()) + .then(blob => new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + fontURLs[name] = reader.result as string; + resolve(); + }; + reader.onerror = () => { + reject(reader.error); + }; + reader.readAsDataURL(blob); + })) : + cachedPromise, + ); } - return fontURLs; + await Promise.all(promises); }; const loadSVG = async(src: Blob): Promise<{url: string; viewBox: Rectangle}> => { @@ -89,22 +88,25 @@ const loadSVG = async(src: Blob): Promise<{url: string; viewBox: Rectangle}> => } if (foundFonts.size > 0) { - const fontURLs = await loadFonts(foundFonts.values()); + await loadFonts(foundFonts.values()); + + const css = []; // Inject fonts as data URLs into the SVG for (const fontName of foundFonts) { - if (!Object.prototype.hasOwnProperty.call(fonts, fontName)) { - continue; + const fontURL = isFont(fontName) && fontURLs[fontName]; + if (fontURL) { + css.push("@font-face{font-family:'", fontName, "';src:url('", fontURL, "')}"); } - const defs = svgDOM.createElementNS('http://www.w3.org/2000/svg', 'defs'); - const style = svgDOM.createElementNS('http://www.w3.org/2000/svg', 'style'); - style.setAttribute('type', 'text/css'); - defs.appendChild(style); - const fontURL = fontURLs[fontName as FontName]; - style.append(`@font-face { font-family: '${fontName}'; src: url(${JSON.stringify(fontURL)}); }`); - svgTag.insertBefore(defs, svgTag.firstChild); } + const defs = svgDOM.createElementNS('http://www.w3.org/2000/svg', 'defs'); + const style = svgDOM.createElementNS('http://www.w3.org/2000/svg', 'style'); + style.setAttribute('type', 'text/css'); + defs.appendChild(style); + style.append(...css); + svgTag.insertBefore(defs, svgTag.firstChild); + src = new Blob([new XMLSerializer().serializeToString(svgDOM)], {type: 'image/svg+xml'}); } } diff --git a/tsconfig.json b/tsconfig.json index 2c5896e..8ee9e89 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,10 +7,11 @@ "sourceMap": true, "strictNullChecks": true, "strict": true, + "skipLibCheck": true, "module": "Node16", "target": "ES2022", "noErrorTruncation": true, - "outDir": "dist", + "noEmit": true, "lib": ["ESNext", "dom"] }, "include": ["src/**/*", "test/**/*", "./*.cjs"]