@jsenv/plugin-transpilation
Version:
159 lines (149 loc) • 5.16 kB
JavaScript
/*
* - propagate "?js_module_fallback" query string param on urls
* - perform conversion from js module to js classic when url uses "?js_module_fallback"
*/
import {
convertJsModuleToJsClassic,
systemJsClientFileUrlDefault,
} from "@jsenv/js-module-fallback";
import { injectQueryParams, urlToFilename } from "@jsenv/urls";
const systemJsClientFileUrl = injectQueryParams(systemJsClientFileUrlDefault, {
as_js_classic: undefined,
});
export const jsenvPluginJsModuleConversion = ({ remapImportSpecifier }) => {
const isReferencingJsModule = (reference) => {
if (
reference.type === "js_import" ||
reference.subtype === "system_register_arg" ||
reference.subtype === "system_import_arg"
) {
return true;
}
if (reference.type === "js_url" && reference.expectedType === "js_module") {
return true;
}
return false;
};
const shouldPropagateJsModuleConversion = (reference) => {
if (isReferencingJsModule(reference)) {
const insideJsClassic =
reference.ownerUrlInfo.searchParams.has("js_module_fallback");
return insideJsClassic;
}
return false;
};
const markAsJsClassicProxy = (reference) => {
reference.expectedType = "js_classic";
if (!reference.filenameHint) {
reference.filenameHint = generateJsClassicFilename(reference.url);
}
};
const turnIntoJsClassicProxy = (reference) => {
markAsJsClassicProxy(reference);
return injectQueryParams(reference.url, {
js_module_fallback: "",
});
};
return {
name: "jsenv:js_module_conversion",
appliesDuring: "*",
redirectReference: (reference) => {
if (reference.searchParams.has("js_module_fallback")) {
markAsJsClassicProxy(reference);
return null;
}
// when search param is injected, it will be removed later
// by "getWithoutSearchParam". We don't want to redirect again
// (would create infinite recursion)
if (
reference.prev &&
reference.prev.searchParams.has(`js_module_fallback`)
) {
return null;
}
// We want to propagate transformation of js module to js classic to:
// - import specifier (static/dynamic import + re-export)
// - url specifier when inside System.register/_context.import()
// (because it's the transpiled equivalent of static and dynamic imports)
// And not other references otherwise we could try to transform inline resources
// or specifiers inside new URL()...
if (shouldPropagateJsModuleConversion(reference)) {
return turnIntoJsClassicProxy(reference);
}
return null;
},
fetchUrlContent: async (urlInfo) => {
const jsModuleUrlInfo = urlInfo.getWithoutSearchParam(
"js_module_fallback",
{
// override the expectedType to "js_module"
// because when there is ?js_module_fallback it means the underlying resource
// is a js_module
expectedType: "js_module",
},
);
if (!jsModuleUrlInfo) {
return null;
}
await jsModuleUrlInfo.cook();
let outputFormat;
if (urlInfo.isEntryPoint && !jsModuleUrlInfo.data.usesImport) {
// if it's an entry point without dependency (it does not use import)
// then we can use UMD
outputFormat = "umd";
} else {
// otherwise we have to use system in case it's imported
// by an other file (for entry points)
// or to be able to import when it uses import
outputFormat = "system";
urlInfo.type = "js_classic";
urlInfo.dependencies.foundSideEffectFile({
sideEffectFileUrl: systemJsClientFileUrl,
expectedType: "js_classic",
line: 0,
column: 0,
});
}
const { content, sourcemap } = await convertJsModuleToJsClassic({
rootDirectoryUrl: urlInfo.context.rootDirectoryUrl,
input: jsModuleUrlInfo.content,
inputIsEntryPoint: urlInfo.isEntryPoint,
inputSourcemap: jsModuleUrlInfo.sourcemap,
inputUrl: jsModuleUrlInfo.url,
outputUrl: urlInfo.url,
outputFormat,
remapImportSpecifier,
});
return {
content,
contentType: "text/javascript",
type: "js_classic",
originalUrl: jsModuleUrlInfo.originalUrl,
originalContent: jsModuleUrlInfo.originalContent,
sourcemap,
data: jsModuleUrlInfo.data,
};
},
};
};
const generateJsClassicFilename = (url) => {
const filename = urlToFilename(url);
let [basename, extension] = splitFileExtension(filename);
const { searchParams } = new URL(url);
if (
searchParams.has("as_json_module") ||
searchParams.has("as_css_module") ||
searchParams.has("as_text_module")
) {
basename += extension;
extension = ".js";
}
return `${basename}.nomodule${extension}`;
};
const splitFileExtension = (filename) => {
const dotLastIndex = filename.lastIndexOf(".");
if (dotLastIndex === -1) {
return [filename, ""];
}
return [filename.slice(0, dotLastIndex), filename.slice(dotLastIndex)];
};