@aem-vite/import-rewriter
Version:
Dynamic imports transformer and CSS path rewriter for Vite when using Adobe Experience Manager
207 lines (204 loc) • 8.14 kB
JavaScript
// src/bundles.ts
import { init, parse as parseImports } from "es-module-lexer";
import MagicString from "magic-string";
// src/helpers.ts
import _debug from "debug";
import { createHash } from "crypto";
import { existsSync, readFileSync } from "fs";
import { join } from "path";
var entryPaths = /* @__PURE__ */ new Set();
var debug = _debug("aem-vite-import-rewriter");
var relativePathPattern = /([.]{1,2}\/)+/;
function getEntryPaths() {
return entryPaths;
}
function setEntryPath(path) {
entryPaths.add(path);
}
function generateChecksum(source) {
return createHash("md5").update(source, "utf8").digest("hex");
}
function getCacheKey(entryPath, keyFormat) {
let keyFormatString = "";
switch (keyFormat) {
case "cloud":
keyFormatString = "lc-%s-lc.%m";
break;
case "acs-classic":
keyFormatString = "%s.%m";
break;
case "acs-modern":
keyFormatString = "%m.ACSHASH%s";
break;
default:
if (typeof keyFormat === "object" && keyFormat.type === "custom" && keyFormat.format) {
keyFormatString = keyFormat.format;
} else {
throw new Error(`Invalid key format provided: ${keyFormat}`);
}
}
const combinedContents = [...entryPaths].map((entry) => {
const path = join(entryPath, entry);
return existsSync(path) ? readFileSync(path).toString() : "";
});
return keyFormatString.replace("%s", generateChecksum(combinedContents.join("")));
}
function getAemClientLibPath(options, forImport = false, withChecksum = false, rollupOptions) {
let path = options.publicPath;
if (forImport) {
return `${path}/${options.resourcesPath}/`;
}
if (withChecksum && options.caching && options.caching.enabled && rollupOptions !== void 0) {
const entryPath = rollupOptions.dir;
path = `${path}.${getCacheKey(entryPath, options.caching.keyFormat)}`;
path = path.replace(".%m", options.minify === true ? ".min" : "");
}
return `${path}.js`;
}
function getReplacementPath(parentPath, path, options, entryAliases) {
const isEntryPath = entryPaths.has(parentPath);
if (isEntryPath) {
return path.replace(new RegExp(`^${relativePathPattern.source}`), getAemClientLibPath(options, true));
}
return isInputAnEntryAlias(path, entryAliases) ? path.replace(
new RegExp(`${relativePathPattern.source}${path.replace(relativePathPattern, "")}`),
getAemClientLibPath(options)
) : path;
}
function isInputAnEntryAlias(input, entryAliases) {
const entryAliasesExpr = new RegExp(`^[./]+(${Object.keys(entryAliases).join("|")})\\.js$`);
return !!RegExp(entryAliasesExpr).exec(input)?.[0];
}
// src/bundles.ts
function bundlesImportRewriter(options) {
const entryAliases = {};
return {
apply: "build",
enforce: "post",
name: "aem-vite:import-rewriter",
configResolved(config) {
const inputs = config.build.rollupOptions.input;
if (!inputs || typeof inputs !== "object" || Array.isArray(inputs)) {
throw new Error(
"Missing valid input aliases which are required to map to an AEM ClientLib path, see https://www.aemvite.dev/guide/front-end/vite/#source-structure for more information."
);
}
for (const [key, value] of Object.entries(inputs)) {
if (/(ts|js)x?$/.test(value)) {
entryAliases[key] = value;
}
}
if (Object.keys(entryAliases).length > 1) {
throw new Error(
"Invalid number of JavaScript inputs provided. Only a single input is currently supported which is a limitation of AEM ClientLibs. It is recommended to create a second ClientLib and Vite config if you need to meet this need."
);
}
},
async renderChunk(source, chunk, rollupOptions) {
if (rollupOptions.format !== "es") {
return null;
}
if (!options?.publicPath.length) {
this.error(
`'publicPath' doesn't appear to be defined, see https://aemvite.dev/guide/faqs/#vite-errors for more information.`
);
}
if (chunk.isEntry && chunk.facadeModuleId && /(ts|js)x?$/.test(chunk.facadeModuleId)) {
debug("setting new entry path: %s\n", chunk.fileName);
setEntryPath(chunk.fileName);
}
await init;
let imports = [];
try {
imports = parseImports(source)[0];
} catch (e) {
this.error({ ...e });
}
if (!imports.length) {
return null;
}
let s;
const str = () => s || (s = new MagicString(source));
for (const element of imports) {
const { e: end, d: dynamicIndex, n: importPath, s: start } = element;
if (dynamicIndex === -1 && importPath && relativePathPattern.test(importPath)) {
const replacementPath = getReplacementPath(chunk.fileName, importPath, options, entryAliases);
debug("(render chunk)");
debug("chunk file name: %s", chunk.fileName);
debug("import path: %s", importPath);
debug("updated import path: %s\n", replacementPath);
str().overwrite(start, end, replacementPath);
}
}
if (s) {
return {
code: s.toString(),
map: rollupOptions.sourcemap !== false ? s.generateMap({ hires: true }) : null
};
}
return null;
},
async generateBundle(rollupOptions, output, isWrite) {
const aemClientLibPath = getAemClientLibPath(options);
debug("(generate bundle)");
debug("aem clientlib path: %s", aemClientLibPath);
debug("is write: %s", isWrite);
for (const [fileName, chunk] of Object.entries(output)) {
if (chunk.type !== "chunk" || !chunk.imports) {
continue;
}
debug("(generate bundle)");
debug("bundle name: %s", fileName);
const source = chunk.code;
await init;
let imports = [];
try {
imports = parseImports(source)[0];
} catch (e) {
this.error({ ...e });
}
if (!imports.length) {
continue;
}
let s;
const str = () => s || (s = new MagicString(source));
for (const element of imports) {
const { e: end, d: dynamicIndex, n: importPath, s: start } = element;
if (dynamicIndex === -1 && importPath && relativePathPattern.test(importPath)) {
const replacementPath = getReplacementPath(chunk.fileName, importPath, options, entryAliases);
debug("chunk file name: %s", chunk.fileName);
debug("import type: native");
debug("import path: %s\n", importPath);
str().overwrite(start, end, replacementPath);
}
if (dynamicIndex > -1 && importPath) {
debug("chunk file name: %s", chunk.fileName);
debug("import type: dynamic");
debug("import path: %s", importPath);
const dynamicEnd = source.indexOf(")", end) + 1;
const original = source.slice(dynamicIndex + 8, dynamicEnd - 2);
debug("updated import path: %s\n", getReplacementPath(chunk.fileName, importPath, options, entryAliases));
if (!original.startsWith("/")) {
str().overwrite(start + 1, end - 1, getReplacementPath(chunk.fileName, importPath, options, entryAliases));
}
}
}
let aemImportPath = aemClientLibPath;
let newSource = s?.toString() ?? source;
if (options.caching && options.caching.enabled) {
aemImportPath = getAemClientLibPath(options, false, true, rollupOptions);
}
newSource = newSource.replace(new RegExp(aemClientLibPath, "g"), aemImportPath);
const relativeClientLibPath = aemImportPath.substring(aemImportPath.lastIndexOf("/") + 1);
getEntryPaths().forEach((path) => {
newSource = newSource.replace(new RegExp(path, "g"), relativeClientLibPath);
});
chunk.code = newSource;
chunk.map = rollupOptions.sourcemap !== false ? s.generateMap({ hires: true }) : null;
}
}
};
}
export {
bundlesImportRewriter
};