@jsenv/core
Version:
Tool to develop, test and build js projects
141 lines (132 loc) • 3.95 kB
JavaScript
// https://bundlers.tooling.report/hashing/avoid-cascade/
import {
createHtmlNode,
findHtmlNode,
getHtmlNodeAttribute,
getHtmlNodeText,
injectHtmlNodeAsEarlyAsPossible,
parseHtml,
removeHtmlNode,
setHtmlNodeText,
stringifyHtmlAst,
} from "@jsenv/ast";
import { isWebWorkerUrlInfo } from "@jsenv/core/src/kitchen/web_workers.js";
import { prependContent } from "../kitchen/prepend_content.js";
export const injectGlobalMappings = async (urlInfo, mappings) => {
if (urlInfo.type === "html") {
const minification = Boolean(
urlInfo.context.getPluginMeta("willMinifyJsClassic"),
);
const content = generateClientCodeForMappings(mappings, {
globalName: "window",
minification,
});
await prependContent(urlInfo, { type: "js_classic", content });
return;
}
if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
const minification = Boolean(
urlInfo.context.getPluginMeta("willMinifyJsClassic"),
);
const content = generateClientCodeForMappings(mappings, {
globalName: isWebWorkerUrlInfo(urlInfo) ? "self" : "window",
minification,
});
await prependContent(urlInfo, { type: "js_classic", content });
return;
}
};
const generateClientCodeForMappings = (
versionMappings,
{ globalName, minification },
) => {
if (minification) {
return `;(function(){var m = ${JSON.stringify(
versionMappings,
)}; ${globalName}.__v__ = function (s) { return m[s] || s }; })();`;
}
return `;(function() {
var __versionMappings__ = {
${stringifyParams(versionMappings, " ")}
};
${globalName}.__v__ = function (specifier) {
return __versionMappings__[specifier] || specifier
};
})();`;
};
export const injectImportmapMappings = (urlInfo, getMappings) => {
const htmlAst = parseHtml({
html: urlInfo.content,
url: urlInfo.url,
storeOriginalPositions: false,
});
// jsenv_plugin_importmap.js is removing importmap during build
// it means at this point we know HTML has no importmap in it
// we can safely inject one
const importmapMinification = Boolean(
urlInfo.context.getPluginMeta("willMinifyJson"),
);
const importmapNode = findHtmlNode(htmlAst, (node) => {
return (
node.tagName === "script" &&
getHtmlNodeAttribute(node, "type") === "importmap"
);
});
const generateMappingText = (mappings) => {
if (importmapMinification) {
return JSON.stringify({ imports: mappings });
}
return JSON.stringify({ imports: mappings }, null, " ");
};
const mutate = (mutation) => {
mutation();
urlInfo.mutateContent({
content: stringifyHtmlAst(htmlAst),
});
};
if (importmapNode) {
// we want to remove some mappings, override others, add eventually add new
const currentMappings = JSON.parse(getHtmlNodeText(importmapNode));
const mappings = getMappings(currentMappings.imports);
if (!mappings || Object.keys(mappings).length === 0) {
mutate(() => {
removeHtmlNode(importmapNode);
});
return;
}
mutate(() => {
setHtmlNodeText(importmapNode, generateMappingText(mappings), {
indentation: "auto",
});
});
return;
}
const mappings = getMappings(null);
if (!mappings || Object.keys(mappings).length === 0) {
return;
}
mutate(() => {
injectHtmlNodeAsEarlyAsPossible(
htmlAst,
createHtmlNode({
tagName: "script",
type: "importmap",
children: generateMappingText(getMappings(null)),
}),
"jsenv:versioning",
);
});
return;
};
const stringifyParams = (params, prefix = "") => {
const source = JSON.stringify(params, null, prefix);
if (prefix.length) {
// remove leading "{\n"
// remove leading prefix
// remove trailing "\n}"
return source.slice(2 + prefix.length, -2);
}
// remove leading "{"
// remove trailing "}"
return source.slice(1, -1);
};