wxt
Version:
⚡ Next-gen Web Extension Framework
122 lines (121 loc) • 4.71 kB
JavaScript
import { normalizePath } from "../../../utils/paths.mjs";
import { getEntrypointName } from "../../../utils/entrypoints.mjs";
import "../../../utils/index.mjs";
import { dirname, relative, resolve } from "node:path";
import { parseHTML } from "linkedom";
import { hash } from "ohash";
//#region src/core/builders/vite/plugins/devHtmlPrerender.ts
const inlineScriptContents = {};
/**
* Pre-renders the HTML entrypoints when building the extension to connect to
* the dev server.
*/
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 } } };
},
transform: {
filter: { id: /\.html$/ },
handler(code, id) {
if (config.command !== "serve" || server == null) 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;
}
},
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 { document } = parseHTML(await server.transformHtml(url, html, originalUrl));
document.querySelectorAll("script:not([src])").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: {
filter: { id: [new RegExp(`^${virtualInlineScript}`), /* @__PURE__ */ new RegExp("^/chunks/")] },
handler(id) {
if (id.startsWith("/chunks/")) return "\0noop";
return `\0${id}`;
}
},
load: {
filter: { id: [new RegExp(`^${resolvedVirtualInlineScript}`), /* @__PURE__ */ new RegExp("^\0noop")] },
handler(id) {
if (id === "\0noop") return "";
return inlineScriptContents[id.substring(id.indexOf("?") + 1)];
}
}
}];
}
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;
}
}
//#endregion
export { devHtmlPrerender, pointToDevServer };