babel-plugin-transform-assets-import-to-string
Version:
Babel plugin that transforms image assets import and requires to urls / cdn
212 lines (206 loc) • 5.88 kB
JavaScript
// src/transform.ts
import path3 from "path";
// src/steps/fileHash.ts
import crypto from "crypto";
import fs from "fs";
function computeFileHash(absPath, hashLength) {
if (hashLength === 0) {
return "";
}
const content = fs.readFileSync(absPath);
const hash = crypto.createHash("sha1").update(content).digest("hex").slice(0, hashLength);
return hash;
}
// src/steps/buildOutputPath.ts
import path from "path";
function buildOutputPath(options) {
const { absPath, hash, preservePaths, projectRoot } = options;
const ext = path.extname(absPath);
const basename = path.basename(absPath, ext);
const hashedName = hash ? `${basename}.${hash}${ext}` : `${basename}${ext}`;
if (!preservePaths) {
return hashedName;
}
const normalizedBase = preservePaths.replace(/^\/|\/$/g, "");
const relativePath = path.relative(projectRoot, absPath);
const segments = relativePath.split(path.sep);
const baseIndex = segments.indexOf(normalizedBase);
let dirPath;
if (baseIndex !== -1) {
dirPath = segments.slice(baseIndex + 1, -1).join("/");
} else {
dirPath = path.dirname(relativePath).split(path.sep).join("/");
}
if (dirPath && dirPath !== ".") {
return `${dirPath}/${hashedName}`;
}
return hashedName;
}
// src/steps/copyFile.ts
import fs2 from "fs";
import path2 from "path";
function copyFile(options) {
const { absPath, outputPath, outputDir, cache } = options;
if (cache.pathMap.has(absPath)) {
return;
}
const existingSource = cache.outputMap.get(outputPath);
if (existingSource && existingSource !== absPath) {
throw new Error(
`Filename collision detected (hashLength is 0)
- ${existingSource}
- ${absPath}
Both would output to: ${path2.join(outputDir, outputPath)}
Consider enabling hashLength or renaming one of the files.`
);
}
const destPath = path2.join(outputDir, outputPath);
const destDir = path2.dirname(destPath);
if (!fs2.existsSync(destDir)) {
fs2.mkdirSync(destDir, { recursive: true });
}
fs2.copyFileSync(absPath, destPath);
cache.pathMap.set(absPath, outputPath);
cache.outputMap.set(outputPath, absPath);
}
// src/steps/replaceNode.ts
function getVariableName(node) {
if (node.specifiers?.[0]?.type === "ImportDefaultSpecifier") {
return node.specifiers[0].local.name;
}
return void 0;
}
function replaceNode(scope, uri, types) {
const content = types.stringLiteral(uri);
if (scope.callee === "require") {
scope.path.replaceWith(content);
return;
}
const importPath = scope.path;
const variableName = getVariableName(importPath.node);
if (variableName) {
scope.path.replaceWith(
types.variableDeclaration("const", [
types.variableDeclarator(types.identifier(variableName), content)
])
);
}
}
// src/transform.ts
function transform(scope, options, types, cache, projectRoot) {
const ext = path3.extname(scope.value);
if (!options.extensions || !options.extensions.includes(ext)) {
return;
}
const dir = path3.dirname(path3.resolve(scope.filename));
const absPath = path3.resolve(dir, scope.value);
if (absPath.includes("node_modules")) {
return;
}
const hashLength = options.hashLength ?? 8;
const hash = computeFileHash(absPath, hashLength);
const outputPath = buildOutputPath({
absPath,
hash,
preservePaths: options.preservePaths,
projectRoot
});
if (options.outputDir) {
copyFile({
absPath,
outputPath,
outputDir: options.outputDir,
cache
});
}
const baseUri = options.baseUri || "";
const separator = baseUri && !baseUri.endsWith("/") ? "/" : "";
const uri = `${baseUri}${separator}${outputPath}`;
replaceNode(scope, uri, types);
}
// src/index.ts
var DEFAULT_EXTENSIONS = [".gif", ".jpeg", ".jpg", ".png", ".svg"];
function isRequireStatement(path4) {
const callee = path4.get("callee");
return !Array.isArray(callee) && callee.isIdentifier() && callee.node.name === "require";
}
function isValidArgument(path4) {
const args = path4.get("arguments");
const arg = args[0];
return arg !== void 0 && arg.isStringLiteral();
}
var buildCache = null;
function plugin({
types: t
}) {
return {
name: "transform-assets-import-to-string",
pre() {
if (!buildCache) {
buildCache = {
pathMap: /* @__PURE__ */ new Map(),
outputMap: /* @__PURE__ */ new Map()
};
}
},
post() {
},
visitor: {
ImportDeclaration(nodePath, state) {
const opts = {
baseUri: "",
extensions: DEFAULT_EXTENSIONS,
hashLength: 8,
...state.opts
};
const projectRoot = state.cwd || process.cwd();
transform(
{
path: nodePath,
filename: state.filename,
value: nodePath.node.source.value,
callee: "import"
},
opts,
t,
buildCache,
projectRoot
);
},
CallExpression(nodePath, state) {
if (isRequireStatement(nodePath) && isValidArgument(nodePath)) {
const args = nodePath.get("arguments");
const arg = args[0];
if (!arg.isStringLiteral()) return;
const opts = {
baseUri: "",
extensions: DEFAULT_EXTENSIONS,
hashLength: 8,
...state.opts
};
const projectRoot = state.cwd || process.cwd();
transform(
{
path: nodePath,
filename: state.filename,
value: arg.node.value,
callee: "require"
},
opts,
t,
buildCache,
projectRoot
);
}
}
}
};
}
function resetBuildCache() {
buildCache = null;
}
export {
plugin as default,
resetBuildCache
};
//# sourceMappingURL=index.js.map