UNPKG

@jsenv/core

Version:

Tool to develop, test and build js projects

141 lines (132 loc) 3.95 kB
// 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); };