UNPKG

@jsenv/plugin-transpilation

Version:
174 lines (169 loc) 5.39 kB
/* * 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; };