wxt
Version:
⚡ Next-gen Web Extension Framework
145 lines (144 loc) • 5.27 kB
JavaScript
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;
}
}