diff --git a/scripts/build-safari-15.js b/scripts/build-safari-15.js index 7fe6c9c1..1466bf15 100644 --- a/scripts/build-safari-15.js +++ b/scripts/build-safari-15.js @@ -16,6 +16,7 @@ import { build } from "vite"; import { svelte } from "@sveltejs/vite-plugin-svelte"; +import pluginMarkedDivest from "./vite-plugin-marked-divest.js"; import * as Utils from "./utils.js"; /** @type {import("vite").InlineConfig} */ @@ -36,7 +37,7 @@ const sharedConfig = { */ build({ ...Utils.baseConfig, - plugins: [svelte()], + plugins: [svelte(), pluginMarkedDivest()], build: { ...sharedConfig.build, outDir: `${Utils.APP_SHARED_RESOURCES}/dist/`, diff --git a/scripts/build-safari-16.4.js b/scripts/build-safari-16.4.js index 390b880a..866b844d 100644 --- a/scripts/build-safari-16.4.js +++ b/scripts/build-safari-16.4.js @@ -15,6 +15,7 @@ import { build } from "vite"; import { svelte } from "@sveltejs/vite-plugin-svelte"; +import pluginMarkedDivest from "./vite-plugin-marked-divest.js"; import * as Utils from "./utils.js"; /** @type {import("vite").InlineConfig} */ @@ -35,7 +36,7 @@ const sharedConfig = { */ build({ ...Utils.baseConfig, - plugins: [svelte()], + plugins: [svelte(), pluginMarkedDivest()], build: { ...sharedConfig.build, outDir: `${Utils.APP_SHARED_RESOURCES}/dist/`, diff --git a/scripts/vite-plugin-marked-divest.js b/scripts/vite-plugin-marked-divest.js new file mode 100644 index 00000000..27093567 --- /dev/null +++ b/scripts/vite-plugin-marked-divest.js @@ -0,0 +1,89 @@ +import { fileURLToPath } from "node:url"; +import { readFile } from "node:fs/promises"; +import { marked } from "marked"; + +/** + * Exclude files that do not need to be processed + * @param {string} id + * @returns {boolean} + */ +function isExclude(id) { + const res = [/\/src\/app\/_locales\/[A-Za-z0-9_]+\/messages\.js/]; + for (const re of res) { + if (re.test(id)) return false; + } + return true; +} + +/** + * Preprocess `marked` calls and `.md?raw` transformations at build time + * to avoid including `marked` modules and runtime transformations + * @returns {import('vite').Plugin} + */ +function preprocessMarked() { + return { + name: "preprocess-marked", + resolveId: { + order: "pre", + handler(source, importer) { + if (isExclude(importer)) return; + if (source.endsWith(".md?raw")) { + const importerUrl = new URL(importer, import.meta.url); + const sourceUrl = new URL(source, importerUrl); + return fileURLToPath(sourceUrl) + "?marked"; + } + }, + }, + load: { + order: "pre", + async handler(id) { + if (!id.endsWith(".md?marked")) return; + const url = new URL(id, import.meta.url); + const text = await readFile(fileURLToPath(url), "utf8"); + const html = await marked.parse(text); + return `export default ${JSON.stringify(html)}`; + }, + }, + transform(code, id) { + if (isExclude(id)) return; + if (!code.includes("marked.parse")) return; + const node = this.parse(code); + if (node.sourceType !== "module") return; + for (const module of node.body) { + if (module.type !== "ImportDeclaration") continue; + if (!module.source.value.toString().endsWith(".md?raw")) continue; + const specifier = module.specifiers.find( + (specifier) => specifier.type === "ImportDefaultSpecifier", + ); + const varName = specifier.local.name; + code = code.replace(`await marked.parse(${varName})`, varName); + // console.debug(`optimizing 'await marked.parse(${varName})'`, id); // DEBUG + } + return { code }; + }, + }; +} + +/** + * Turn off `Marked` module side effects for better tree-shaking + * @returns {import('vite').Plugin} + */ +function disableMarkedSideEffects() { + return { + name: "disable-marked-side-effects", + transform(code, id) { + if (id.includes("/node_modules/marked")) { + return { code, moduleSideEffects: false }; + } + }, + }; +} + +/** + * @returns {import('vite').Plugin[]} + */ +function markedDivest() { + return [disableMarkedSideEffects(), preprocessMarked()]; +} + +export default markedDivest; diff --git a/src/app/i18n.js b/src/app/i18n.js index 8ed34019..e5a42ee5 100644 --- a/src/app/i18n.js +++ b/src/app/i18n.js @@ -14,7 +14,6 @@ function getLangFrom(messages, messageName, substitutions = undefined) { console.warn(`i18n - "${messageName}" not found`, messages); return ""; } - /** @type {string} message */ let text = messages[messageName].message; // handle substitutions if (typeof substitutions === "string") {