UNPKG

babel-plugin-transform-assets-import-to-string

Version:
1 lines 13.6 kB
{"version":3,"sources":["../src/index.ts","../src/transform.ts","../src/steps/fileHash.ts","../src/steps/buildOutputPath.ts","../src/steps/copyFile.ts","../src/steps/replaceNode.ts"],"sourcesContent":["import type { PluginObj, NodePath } from '@babel/core';\nimport type { ImportDeclaration, CallExpression } from '@babel/types';\nimport { transform } from './transform.js';\nimport type { PluginOptions, CopyCache } from './types.js';\n\nconst DEFAULT_EXTENSIONS = ['.gif', '.jpeg', '.jpg', '.png', '.svg'];\n\nfunction isRequireStatement(path: NodePath<CallExpression>): boolean {\n const callee = path.get('callee');\n return (\n !Array.isArray(callee) &&\n callee.isIdentifier() &&\n callee.node.name === 'require'\n );\n}\n\nfunction isValidArgument(path: NodePath<CallExpression>): boolean {\n const args = path.get('arguments');\n const arg = args[0];\n return arg !== undefined && arg.isStringLiteral();\n}\n\ninterface PluginState {\n opts: PluginOptions;\n filename: string;\n cwd: string;\n}\n\n// Module-level cache shared across all files in a build\nlet buildCache: CopyCache | null = null;\n\nexport default function plugin({\n types: t,\n}: {\n types: typeof import('@babel/types');\n}): PluginObj<PluginState> {\n return {\n name: 'transform-assets-import-to-string',\n pre() {\n // Initialize cache at start of build if not exists\n if (!buildCache) {\n buildCache = {\n pathMap: new Map(),\n outputMap: new Map(),\n };\n }\n },\n post() {\n // Clear cache after build completes\n // Note: This runs per-file, so we don't clear here\n // Cache persists for the entire build\n },\n visitor: {\n ImportDeclaration(\n nodePath: NodePath<ImportDeclaration>,\n state: PluginState\n ) {\n const opts: PluginOptions = {\n baseUri: '',\n extensions: DEFAULT_EXTENSIONS,\n hashLength: 8,\n ...state.opts,\n };\n\n const projectRoot = state.cwd || process.cwd();\n\n transform(\n {\n path: nodePath,\n filename: state.filename,\n value: nodePath.node.source.value,\n callee: 'import',\n },\n opts,\n t,\n buildCache!,\n projectRoot\n );\n },\n CallExpression(nodePath: NodePath<CallExpression>, state: PluginState) {\n if (isRequireStatement(nodePath) && isValidArgument(nodePath)) {\n const args = nodePath.get('arguments');\n const arg = args[0];\n\n if (!arg.isStringLiteral()) return;\n\n const opts: PluginOptions = {\n baseUri: '',\n extensions: DEFAULT_EXTENSIONS,\n hashLength: 8,\n ...state.opts,\n };\n\n const projectRoot = state.cwd || process.cwd();\n\n transform(\n {\n path: nodePath,\n filename: state.filename,\n value: arg.node.value,\n callee: 'require',\n },\n opts,\n t,\n buildCache!,\n projectRoot\n );\n }\n },\n },\n };\n}\n\n// Export for testing - allows resetting cache between test runs\nexport function resetBuildCache(): void {\n buildCache = null;\n}\n\nexport type { PluginOptions } from './types.js';\n","import path from 'node:path';\nimport type { types as t } from '@babel/core';\nimport { computeFileHash } from './steps/fileHash.js';\nimport { buildOutputPath } from './steps/buildOutputPath.js';\nimport { copyFile } from './steps/copyFile.js';\nimport { replaceNode } from './steps/replaceNode.js';\nimport type { PluginOptions, TransformScope, CopyCache } from './types.js';\n\nexport function transform(\n scope: TransformScope,\n options: PluginOptions,\n types: typeof t,\n cache: CopyCache,\n projectRoot: string\n): void {\n const ext = path.extname(scope.value);\n\n if (!options.extensions || !options.extensions.includes(ext)) {\n return;\n }\n\n const dir = path.dirname(path.resolve(scope.filename));\n const absPath = path.resolve(dir, scope.value);\n\n // Skip node_modules\n if (absPath.includes('node_modules')) {\n return;\n }\n\n // Compute content hash\n const hashLength = options.hashLength ?? 8;\n const hash = computeFileHash(absPath, hashLength);\n\n // Build output path\n const outputPath = buildOutputPath({\n absPath,\n hash,\n preservePaths: options.preservePaths,\n projectRoot,\n });\n\n // Copy file if outputDir is set\n if (options.outputDir) {\n copyFile({\n absPath,\n outputPath,\n outputDir: options.outputDir,\n cache,\n });\n }\n\n // Build final URI\n const baseUri = options.baseUri || '';\n const separator = baseUri && !baseUri.endsWith('/') ? '/' : '';\n const uri = `${baseUri}${separator}${outputPath}`;\n\n replaceNode(scope, uri, types);\n}\n","import crypto from 'node:crypto';\nimport fs from 'node:fs';\n\n/**\n * Compute SHA1 content hash of a file\n * @param absPath - Absolute path to the file\n * @param hashLength - Number of hex characters to return (0 = no hash)\n * @returns Hash string or empty string if hashLength is 0\n */\nexport function computeFileHash(absPath: string, hashLength: number): string {\n if (hashLength === 0) {\n return '';\n }\n\n const content = fs.readFileSync(absPath);\n\n const hash = crypto\n .createHash('sha1')\n .update(content)\n .digest('hex')\n .slice(0, hashLength);\n\n return hash;\n}\n","import path from 'node:path';\n\nexport interface BuildOutputPathOptions {\n absPath: string;\n hash: string;\n preservePaths: string | undefined;\n projectRoot: string;\n}\n\nexport function buildOutputPath(options: BuildOutputPathOptions): string {\n const { absPath, hash, preservePaths, projectRoot } = options;\n\n const ext = path.extname(absPath);\n const basename = path.basename(absPath, ext);\n const hashedName = hash ? `${basename}.${hash}${ext}` : `${basename}${ext}`;\n\n if (!preservePaths) {\n return hashedName;\n }\n\n // Normalize preservePaths: strip leading/trailing slashes\n const normalizedBase = preservePaths.replace(/^\\/|\\/$/g, '');\n\n // Get relative path from project root\n const relativePath = path.relative(projectRoot, absPath);\n\n // Find the preservePaths segment in the path\n const segments = relativePath.split(path.sep);\n const baseIndex = segments.indexOf(normalizedBase);\n\n let dirPath: string;\n if (baseIndex !== -1) {\n // Strip everything up to and including the base\n dirPath = segments.slice(baseIndex + 1, -1).join('/');\n } else {\n // Base not found, use full relative directory path\n dirPath = path.dirname(relativePath).split(path.sep).join('/');\n }\n\n if (dirPath && dirPath !== '.') {\n return `${dirPath}/${hashedName}`;\n }\n\n return hashedName;\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { CopyCache } from '../types.js';\n\nexport interface CopyFileOptions {\n absPath: string;\n outputPath: string;\n outputDir: string;\n cache: CopyCache;\n}\n\nexport function copyFile(options: CopyFileOptions): void {\n const { absPath, outputPath, outputDir, cache } = options;\n\n // Check if already copied from this source\n if (cache.pathMap.has(absPath)) {\n return;\n }\n\n // Check for collision (different source, same output)\n const existingSource = cache.outputMap.get(outputPath);\n if (existingSource && existingSource !== absPath) {\n throw new Error(\n `Filename collision detected (hashLength is 0)\\n` +\n ` - ${existingSource}\\n` +\n ` - ${absPath}\\n` +\n ` Both would output to: ${path.join(outputDir, outputPath)}\\n` +\n ` Consider enabling hashLength or renaming one of the files.`\n );\n }\n\n // Build full destination path\n const destPath = path.join(outputDir, outputPath);\n const destDir = path.dirname(destPath);\n\n // Create directories if needed\n if (!fs.existsSync(destDir)) {\n fs.mkdirSync(destDir, { recursive: true });\n }\n\n // Copy the file\n fs.copyFileSync(absPath, destPath);\n\n // Update cache\n cache.pathMap.set(absPath, outputPath);\n cache.outputMap.set(outputPath, absPath);\n}\n","import type { types as t } from '@babel/core';\nimport type { TransformScope } from '../types.js';\n\nfunction getVariableName(node: t.ImportDeclaration): string | undefined {\n if (node.specifiers?.[0]?.type === 'ImportDefaultSpecifier') {\n return node.specifiers[0].local.name;\n }\n return undefined;\n}\n\nexport function replaceNode(\n scope: TransformScope,\n uri: string,\n types: typeof t\n): void {\n const content = types.stringLiteral(uri);\n\n if (scope.callee === 'require') {\n scope.path.replaceWith(content);\n return;\n }\n\n const importPath = scope.path as import('@babel/core').NodePath<t.ImportDeclaration>;\n const variableName = getVariableName(importPath.node);\n\n if (variableName) {\n scope.path.replaceWith(\n types.variableDeclaration('const', [\n types.variableDeclarator(types.identifier(variableName), content),\n ])\n );\n }\n}\n"],"mappings":"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","names":["import_node_path","fs","crypto","path","import_node_fs","import_node_path","path","fs","path","path"]}