@jsenv/core
Version:
Tool to develop, test and build js projects
165 lines (160 loc) • 5.39 kB
JavaScript
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;
},
},
};
};