UNPKG

@jsenv/core

Version:

Tool to develop, test and build js projects

165 lines (160 loc) 5.39 kB
import { analyzeScriptNode, getHtmlNodeAttribute, getHtmlNodePosition, getHtmlNodeText, getUrlForContentInsideHtml, parseHtml, setHtmlNodeAttributes, setHtmlNodeText, stringifyHtmlAst, visitHtmlNodes, } from "@jsenv/ast"; export const jsenvPluginInliningIntoHtml = () => { return { name: "jsenv:inlining_into_html", appliesDuring: "*", transformUrlContent: { html: async (urlInfo) => { const htmlAst = parseHtml({ html: urlInfo.content, url: urlInfo.url, }); const mutations = []; const actions = []; const onLinkRelStyleSheet = (linkNode, { href }) => { let linkReference = null; for (const referenceToOther of urlInfo.referenceToOthersSet) { if ( referenceToOther.generatedSpecifier === href && referenceToOther.type === "link_href" && referenceToOther.subtype === "stylesheet" ) { linkReference = referenceToOther; break; } } if (!linkReference.searchParams.has("inline")) { return; } const { line, column, isOriginal } = getHtmlNodePosition(linkNode, { preferOriginal: true, }); const linkInlineUrl = getUrlForContentInsideHtml( linkNode, urlInfo, linkReference, ); const linkReferenceInlined = linkReference.inline({ line, column, isOriginal, specifier: linkInlineUrl, type: "style", expectedType: linkReference.expectedType, }); const linkUrlInfoInlined = linkReferenceInlined.urlInfo; actions.push(async () => { await linkUrlInfoInlined.cook(); mutations.push(() => { setHtmlNodeAttributes(linkNode, { "inlined-from-href": linkReference.url, "href": undefined, "rel": undefined, "type": undefined, "as": undefined, "crossorigin": undefined, "integrity": undefined, "jsenv-inlined-by": "jsenv:inlining_into_html", }); linkNode.nodeName = "style"; linkNode.tagName = "style"; setHtmlNodeText(linkNode, linkUrlInfoInlined.content, { indentation: "auto", }); }); }); }; const onScriptWithSrc = (scriptNode, { src }) => { let scriptReference; for (const dependencyReference of urlInfo.referenceToOthersSet) { if ( dependencyReference.generatedSpecifier === src && dependencyReference.type === "script" ) { scriptReference = dependencyReference; break; } } if (!scriptReference.searchParams.has("inline")) { return; } const { line, column, isOriginal } = getHtmlNodePosition(scriptNode, { preferOriginal: true, }); const scriptInlineUrl = getUrlForContentInsideHtml( scriptNode, urlInfo, scriptReference, ); const scriptReferenceInlined = scriptReference.inline({ line, column, isOriginal, specifier: scriptInlineUrl, type: scriptReference.type, subtype: scriptReference.subtype, expectedType: scriptReference.expectedType, }); const scriptUrlInfoInlined = scriptReferenceInlined.urlInfo; actions.push(async () => { await scriptUrlInfoInlined.cook(); mutations.push(() => { setHtmlNodeAttributes(scriptNode, { "inlined-from-src": src, "src": undefined, "crossorigin": undefined, "integrity": undefined, "jsenv-inlined-by": "jsenv:inlining_into_html", }); setHtmlNodeText(scriptNode, scriptUrlInfoInlined.content, { indentation: "auto", }); }); }); }; visitHtmlNodes(htmlAst, { link: (linkNode) => { const rel = getHtmlNodeAttribute(linkNode, "rel"); if (rel !== "stylesheet") { return; } const href = getHtmlNodeAttribute(linkNode, "href"); if (!href) { return; } onLinkRelStyleSheet(linkNode, { href }); }, script: (scriptNode) => { const { type } = analyzeScriptNode(scriptNode); const scriptNodeText = getHtmlNodeText(scriptNode); if (scriptNodeText) { return; } const src = getHtmlNodeAttribute(scriptNode, "src"); if (!src) { return; } onScriptWithSrc(scriptNode, { type, src }); }, }); if (actions.length > 0) { await Promise.all(actions.map((action) => action())); } mutations.forEach((mutation) => mutation()); const htmlModified = stringifyHtmlAst(htmlAst); return htmlModified; }, }, }; };