UNPKG

@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
// 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 };