UNPKG

wxt

Version:

⚡ Next-gen Web Extension Framework

145 lines (144 loc) 5.27 kB
import { getEntrypointName } from "../../../utils/entrypoints.mjs"; import { parseHTML } from "linkedom"; import { dirname, relative, resolve } from "node:path"; import { normalizePath } from "../../../utils/paths.mjs"; import { hash } from "ohash"; const inlineScriptContents = {}; export function devHtmlPrerender(config, server) { const htmlReloadId = "@wxt/reload-html"; const resolvedHtmlReloadId = resolve( config.wxtModuleDir, "dist/virtual/reload-html.mjs" ); const virtualInlineScript = "virtual:wxt-inline-script"; const resolvedVirtualInlineScript = "\0" + virtualInlineScript; return [ { apply: "build", name: "wxt:dev-html-prerender", config() { return { resolve: { alias: { [htmlReloadId]: resolvedHtmlReloadId } } }; }, // Convert scripts like src="./main.tsx" -> src="http://localhost:3000/entrypoints/popup/main.tsx" // before the paths are replaced with their bundled path transform(code, id) { if (config.command !== "serve" || server == null || !id.endsWith(".html")) return; const { document } = parseHTML(code); const _pointToDevServer = (querySelector, attr) => pointToDevServer(config, server, id, document, querySelector, attr); _pointToDevServer("script[type=module]", "src"); _pointToDevServer("link[rel=stylesheet]", "href"); const reloader = document.createElement("script"); reloader.src = htmlReloadId; reloader.type = "module"; document.head.appendChild(reloader); const newHtml = document.toString(); config.logger.debug("transform " + id); config.logger.debug("Old HTML:\n" + code); config.logger.debug("New HTML:\n" + newHtml); return newHtml; }, // Pass the HTML through the dev server to add dev-mode specific code async transformIndexHtml(html, ctx) { if (config.command !== "serve" || server == null) return; const originalUrl = `${server.origin}${ctx.path}`; const name = getEntrypointName(config.entrypointsDir, ctx.filename); const url = `${server.origin}/${name}.html`; const serverHtml = await server.transformHtml(url, html, originalUrl); const { document } = parseHTML(serverHtml); const inlineScripts = document.querySelectorAll("script:not([src])"); inlineScripts.forEach((script) => { const textContent = script.textContent ?? ""; const key = hash(textContent); inlineScriptContents[key] = textContent; const virtualScript = document.createElement("script"); virtualScript.type = "module"; virtualScript.src = `${server.origin}/@id/${virtualInlineScript}?${key}`; script.replaceWith(virtualScript); }); const viteClientScript = document.querySelector( "script[src='/@vite/client']" ); if (viteClientScript) { viteClientScript.src = `${server.origin}${viteClientScript.src}`; } const newHtml = document.toString(); config.logger.debug("transformIndexHtml " + ctx.filename); config.logger.debug("Old HTML:\n" + html); config.logger.debug("New HTML:\n" + newHtml); return newHtml; } }, { name: "wxt:virtualize-inline-scripts", apply: "serve", resolveId(id) { if (id.startsWith(virtualInlineScript)) { return "\0" + id; } if (id.startsWith("/chunks/")) { return "\0noop"; } }, load(id) { if (id.startsWith(resolvedVirtualInlineScript)) { const key = id.substring(id.indexOf("?") + 1); return inlineScriptContents[key]; } if (id === "\0noop") { return ""; } } } ]; } export function pointToDevServer(config, server, id, document, querySelector, attr) { document.querySelectorAll(querySelector).forEach((element) => { if (element.hasAttribute("vite-ignore") || element.hasAttribute("wxt-ignore")) { element.removeAttribute("wxt-ignore"); return; } const src = element.getAttribute(attr); if (!src || isUrl(src)) return; let resolvedAbsolutePath; const matchingAlias = Object.entries(config.alias).find( ([key]) => src.startsWith(key) ); if (matchingAlias) { const [alias, replacement] = matchingAlias; resolvedAbsolutePath = resolve( config.root, src.replace(alias, replacement) ); } else { resolvedAbsolutePath = resolve(dirname(id), src); } if (resolvedAbsolutePath) { const relativePath = normalizePath( relative(config.root, resolvedAbsolutePath) ); if (relativePath.startsWith(".")) { let path = normalizePath(resolvedAbsolutePath); if (!path.startsWith("/")) path = "/" + path; element.setAttribute(attr, `${server.origin}/@fs${path}`); } else { const url = new URL(relativePath, server.origin); element.setAttribute(attr, url.href); } } }); } function isUrl(str) { try { new URL(str); return true; } catch { return false; } }