@jsenv/plugin-transpilation
Version:
174 lines (169 loc) • 5.39 kB
JavaScript
/*
* when <script type="module"> cannot be used:
* - ?js_module_fallback is injected into the src of <script type="module">
* - js inside <script type="module"> is transformed into classic js
* - <link rel="modulepreload"> are converted to <link rel="preload">
*/
import {
analyzeScriptNode,
getHtmlNodeAttribute,
getHtmlNodeText,
parseHtml,
setHtmlNodeAttributes,
stringifyHtmlAst,
visitHtmlNodes,
} from "@jsenv/ast";
import { injectQueryParams } from "@jsenv/urls";
export const jsenvPluginJsModuleFallbackInsideHtml = ({
needJsModuleFallback,
}) => {
const turnIntoJsClassicProxy = (reference) => {
return injectQueryParams(reference.url, { js_module_fallback: "" });
};
return {
name: "jsenv:js_module_fallback_inside_html",
appliesDuring: "*",
init: needJsModuleFallback,
redirectReference: {
link_href: (reference) => {
if (
reference.prev &&
reference.prev.searchParams.has(`js_module_fallback`)
) {
return null;
}
if (reference.subtype === "modulepreload") {
return turnIntoJsClassicProxy(reference);
}
if (
reference.subtype === "preload" &&
reference.expectedType === "js_module"
) {
return turnIntoJsClassicProxy(reference);
}
return null;
},
script: (reference) => {
if (
reference.prev &&
reference.prev.searchParams.has(`js_module_fallback`)
) {
return null;
}
if (reference.expectedType === "js_module") {
return turnIntoJsClassicProxy(reference);
}
return null;
},
js_url: (reference) => {
if (
reference.prev &&
reference.prev.searchParams.has(`js_module_fallback`)
) {
return null;
}
if (reference.expectedType === "js_module") {
return turnIntoJsClassicProxy(reference);
}
return null;
},
},
finalizeUrlContent: {
html: async (urlInfo) => {
const htmlAst = parseHtml({ html: urlInfo.content, url: urlInfo.url });
const mutations = [];
visitHtmlNodes(htmlAst, {
link: (node) => {
const rel = getHtmlNodeAttribute(node, "rel");
if (rel !== "modulepreload" && rel !== "preload") {
return;
}
const href = getHtmlNodeAttribute(node, "href");
if (!href) {
return;
}
let linkHintReference = null;
for (const referenceToOther of urlInfo.referenceToOthersSet) {
if (
referenceToOther.generatedSpecifier === href &&
referenceToOther.type === "link_href" &&
referenceToOther.subtype === rel
) {
linkHintReference = referenceToOther;
break;
}
}
if (rel === "modulepreload") {
if (linkHintReference.expectedType === "js_classic") {
mutations.push(() => {
setHtmlNodeAttributes(node, {
rel: "preload",
as: "script",
crossorigin: undefined,
});
});
}
}
if (
rel === "preload" &&
wasConvertedFromJsModule(linkHintReference)
) {
mutations.push(() => {
setHtmlNodeAttributes(node, { crossorigin: undefined });
});
}
},
script: (node) => {
const { type } = analyzeScriptNode(node);
if (type !== "js_module") {
return;
}
const src = getHtmlNodeAttribute(node, "src");
const text = getHtmlNodeText(node);
let scriptReference = null;
for (const referenceToOther of urlInfo.referenceToOthersSet) {
if (referenceToOther.type !== "script") {
continue;
}
if (src && referenceToOther.generatedSpecifier === src) {
scriptReference = referenceToOther;
break;
}
if (text) {
if (referenceToOther.content === text) {
scriptReference = referenceToOther;
break;
}
if (referenceToOther.urlInfo.content === text) {
scriptReference = referenceToOther;
break;
}
}
}
if (!wasConvertedFromJsModule(scriptReference)) {
return;
}
mutations.push(() => {
setHtmlNodeAttributes(node, { type: undefined });
});
},
});
await Promise.all(mutations.map((mutation) => mutation()));
return stringifyHtmlAst(htmlAst, {
cleanupPositionAttributes: urlInfo.context.dev,
});
},
},
};
};
const wasConvertedFromJsModule = (reference) => {
if (reference.expectedType === "js_classic") {
// check if a prev version was using js module
if (reference.original) {
if (reference.original.expectedType === "js_module") {
return true;
}
}
}
return false;
};