babel-plugin-transform-assets-import-to-string 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # babel-plugin-transform-assets-import-to-string
2
+
2
3
  > Babel plugin that transforms image assets import and requires to urls / cdn
3
4
 
4
5
  [![npm][npm-badge]][npm-link]
@@ -6,11 +7,19 @@
6
7
 
7
8
  ## Table of Contents
8
9
 
9
- - [About](#about)
10
- - [Installation](#installation)
11
- - [Usage](#usage)
12
- - [via babelrc](#via-babelrc)
13
- - [via Node API](#via-node-api)
10
+ * [About](#about)
11
+ * [Features](#features)
12
+ * [Requirements](#requirements)
13
+ * [Installation](#installation)
14
+ * [Usage](#usage)
15
+ * [via babelrc](#via-babelrc)
16
+ * [via Node API](#via-node-api)
17
+ * [Options](#options)
18
+ * [Examples](#examples)
19
+ * [Basic URI Transformation](#basic-uri-transformation)
20
+ * [File Copying with outputDir](#file-copying-with-outputdir)
21
+ * [Path Preservation with preservePaths](#path-preservation-with-preservepaths)
22
+ * [Disabling Hash](#disabling-hash)
14
23
 
15
24
  ## About
16
25
 
@@ -24,8 +33,8 @@ const image1 = require('../path/assets/icon1.svg');
24
33
 
25
34
  // to
26
35
 
27
- const image = 'http://your.cdn.address/assets/icon.svg';
28
- const image1 = 'http://your.cdn.address/assets/icon1.svg';
36
+ const image = 'https://cdn.example.com/assets/icon.a1b2c3d4.svg';
37
+ const image1 = 'https://cdn.example.com/assets/icon1.e5f6g7h8.svg';
29
38
 
30
39
  // Somewhere further down in your code:
31
40
  //
@@ -36,47 +45,201 @@ const image1 = 'http://your.cdn.address/assets/icon1.svg';
36
45
  // ajaxAsyncRequest(image)
37
46
  ```
38
47
 
39
- See the spec for more [examples](https://github.com/yeojz/babel-plugin-transform-assets-import-to-string/blob/master/test/index.spec.js).
48
+ See the spec for more [examples](https://github.com/yeojz/babel-plugin-transform-assets-import-to-string/blob/master/test/index.spec.ts).
49
+
50
+ ## Features
51
+
52
+ - Transforms asset imports to CDN URLs with content hashing
53
+ - Supports both ES6 `import` and CommonJS `require()`
54
+ - Optional file copying to output directory during build
55
+ - Content-based hashing for cache busting (same content = same hash)
56
+ - Configurable file extensions and hash length
57
+ - Path structure preservation option
58
+ - TypeScript support
59
+
60
+ ## Requirements
61
+
62
+ - Node.js 20 or higher
63
+ - Babel 7.20 or higher
40
64
 
41
65
  ## Installation
42
66
 
43
67
  ```
44
- $> npm install babel-plugin-transform-assets-import-to-string --save
68
+ $> npm install babel-plugin-transform-assets-import-to-string --save-dev
69
+ ```
70
+
71
+ This plugin requires `@babel/core` as a peer dependency. If you don't already have it installed:
72
+
73
+ ```
74
+ $> npm install @babel/core --save-dev
45
75
  ```
46
76
 
47
77
  ## Usage
48
78
 
49
79
  ### via .babelrc
80
+
50
81
  ```json
51
82
  {
52
- "plugins": [["transform-assets-import-to-string", {
53
- "baseDir": "/assets",
54
- "baseUri": "http://your.cdn.address"
55
- }]]
83
+ "plugins": [
84
+ [
85
+ "transform-assets-import-to-string",
86
+ {
87
+ "baseUri": "https://cdn.example.com/assets"
88
+ }
89
+ ]
90
+ ]
56
91
  }
57
92
  ```
58
93
 
59
94
  ### via Node API
60
95
 
61
96
  ```js
62
- require("babel-core").transform("code", {
63
- plugins: [["transform-assets-import-to-string", {
64
- "baseDir": "/assets",
65
- "baseUri": "http://your.cdn.address"
66
- }]]
97
+ require('@babel/core').transform('code', {
98
+ plugins: [
99
+ [
100
+ 'transform-assets-import-to-string',
101
+ {
102
+ baseUri: 'https://cdn.example.com/assets'
103
+ }
104
+ ]
105
+ ]
67
106
  });
68
107
  ```
69
108
 
70
- By default, it will transform the following extensions: `.gif, .jpeg, .jpg, .png, .svg` if `extensions` option is not defined. To configure a custom list, just add the `extensions` array as an option.
109
+ ## Options
110
+
111
+ | Option | Type | Default | Description |
112
+ |--------|------|---------|-------------|
113
+ | `baseUri` | `string` | `""` | URL prefix for transformed paths (e.g., `"https://cdn.example.com/assets"`) |
114
+ | `outputDir` | `string` | `undefined` | Directory to copy assets to during build. If not set, no files are copied. |
115
+ | `extensions` | `string[]` | `[".gif", ".jpeg", ".jpg", ".png", ".svg"]` | File extensions to transform. Leading `.` (dot) is required. |
116
+ | `hashLength` | `number` | `8` | Length of content hash in filename. Set to `0` to disable hashing. |
117
+ | `preservePaths` | `string` | `undefined` | Base path to strip while preserving directory structure. If not set, filenames are flattened. |
118
+
119
+ ## Examples
120
+
121
+ ### Basic URI Transformation
122
+
123
+ Transform imports to CDN URLs with content hashing:
124
+
125
+ ```json
126
+ {
127
+ "plugins": [
128
+ [
129
+ "transform-assets-import-to-string",
130
+ {
131
+ "baseUri": "https://cdn.example.com/assets"
132
+ }
133
+ ]
134
+ ]
135
+ }
136
+ ```
137
+
138
+ ```js
139
+ // Input
140
+ import logo from './images/logo.svg';
141
+
142
+ // Output
143
+ const logo = 'https://cdn.example.com/assets/logo.a1b2c3d4.svg';
144
+ ```
145
+
146
+ ### File Copying with outputDir
147
+
148
+ Copy assets to a build directory during transformation:
149
+
150
+ ```json
151
+ {
152
+ "plugins": [
153
+ [
154
+ "transform-assets-import-to-string",
155
+ {
156
+ "baseUri": "https://cdn.example.com/assets",
157
+ "outputDir": "./dist/assets"
158
+ }
159
+ ]
160
+ ]
161
+ }
162
+ ```
163
+
164
+ ```js
165
+ // Input
166
+ import logo from './images/logo.svg';
71
167
 
72
- __Note:__ leading `.` (dot) is required.
168
+ // Output
169
+ const logo = 'https://cdn.example.com/assets/logo.a1b2c3d4.svg';
170
+ // File copied to: ./dist/assets/logo.a1b2c3d4.svg
171
+ ```
172
+
173
+ ### Path Preservation with preservePaths
174
+
175
+ Keep directory structure by specifying a base path to strip:
73
176
 
74
177
  ```json
75
178
  {
76
179
  "plugins": [
77
- ["transform-assets-import-to-string", {
78
- "extensions": [".jpg", ".png"]
79
- }]
180
+ [
181
+ "transform-assets-import-to-string",
182
+ {
183
+ "baseUri": "https://cdn.example.com",
184
+ "outputDir": "./dist/static",
185
+ "preservePaths": "src"
186
+ }
187
+ ]
188
+ ]
189
+ }
190
+ ```
191
+
192
+ ```js
193
+ // Input (file at src/components/icons/logo.svg)
194
+ import logo from './icons/logo.svg';
195
+
196
+ // Output
197
+ const logo = 'https://cdn.example.com/components/icons/logo.a1b2c3d4.svg';
198
+ // File copied to: ./dist/static/components/icons/logo.a1b2c3d4.svg
199
+ ```
200
+
201
+ Without `preservePaths`, all files are flattened to the root of `outputDir`.
202
+
203
+ ### Disabling Hash
204
+
205
+ Use `hashLength: 0` to disable content hashing:
206
+
207
+ ```json
208
+ {
209
+ "plugins": [
210
+ [
211
+ "transform-assets-import-to-string",
212
+ {
213
+ "baseUri": "https://cdn.example.com/assets",
214
+ "hashLength": 0
215
+ }
216
+ ]
217
+ ]
218
+ }
219
+ ```
220
+
221
+ ```js
222
+ // Input
223
+ import logo from './images/logo.svg';
224
+
225
+ // Output
226
+ const logo = 'https://cdn.example.com/assets/logo.svg';
227
+ ```
228
+
229
+ ### Custom Extensions
230
+
231
+ Transform only specific file types:
232
+
233
+ ```json
234
+ {
235
+ "plugins": [
236
+ [
237
+ "transform-assets-import-to-string",
238
+ {
239
+ "baseUri": "https://cdn.example.com/assets",
240
+ "extensions": [".svg", ".png"]
241
+ }
242
+ ]
80
243
  ]
81
244
  }
82
245
  ```
@@ -87,6 +250,5 @@ __Note:__ leading `.` (dot) is required.
87
250
 
88
251
  [circle-badge]: https://img.shields.io/circleci/project/github/yeojz/babel-plugin-transform-assets-import-to-string/master.svg?style=flat-square
89
252
  [circle-link]: https://circleci.com/gh/yeojz/babel-plugin-transform-assets-import-to-string
90
-
91
253
  [npm-badge]: https://img.shields.io/npm/v/babel-plugin-transform-assets-import-to-string.svg?style=flat-square
92
254
  [npm-link]: https://www.npmjs.com/package/babel-plugin-transform-assets-import-to-string
package/dist/index.cjs ADDED
@@ -0,0 +1,249 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ default: () => plugin,
34
+ resetBuildCache: () => resetBuildCache
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/transform.ts
39
+ var import_node_path3 = __toESM(require("path"), 1);
40
+
41
+ // src/steps/fileHash.ts
42
+ var import_node_crypto = __toESM(require("crypto"), 1);
43
+ var import_node_fs = __toESM(require("fs"), 1);
44
+ function computeFileHash(absPath, hashLength) {
45
+ if (hashLength === 0) {
46
+ return "";
47
+ }
48
+ const content = import_node_fs.default.readFileSync(absPath);
49
+ const hash = import_node_crypto.default.createHash("sha1").update(content).digest("hex").slice(0, hashLength);
50
+ return hash;
51
+ }
52
+
53
+ // src/steps/buildOutputPath.ts
54
+ var import_node_path = __toESM(require("path"), 1);
55
+ function buildOutputPath(options) {
56
+ const { absPath, hash, preservePaths, projectRoot } = options;
57
+ const ext = import_node_path.default.extname(absPath);
58
+ const basename = import_node_path.default.basename(absPath, ext);
59
+ const hashedName = hash ? `${basename}.${hash}${ext}` : `${basename}${ext}`;
60
+ if (!preservePaths) {
61
+ return hashedName;
62
+ }
63
+ const normalizedBase = preservePaths.replace(/^\/|\/$/g, "");
64
+ const relativePath = import_node_path.default.relative(projectRoot, absPath);
65
+ const segments = relativePath.split(import_node_path.default.sep);
66
+ const baseIndex = segments.indexOf(normalizedBase);
67
+ let dirPath;
68
+ if (baseIndex !== -1) {
69
+ dirPath = segments.slice(baseIndex + 1, -1).join("/");
70
+ } else {
71
+ dirPath = import_node_path.default.dirname(relativePath).split(import_node_path.default.sep).join("/");
72
+ }
73
+ if (dirPath && dirPath !== ".") {
74
+ return `${dirPath}/${hashedName}`;
75
+ }
76
+ return hashedName;
77
+ }
78
+
79
+ // src/steps/copyFile.ts
80
+ var import_node_fs2 = __toESM(require("fs"), 1);
81
+ var import_node_path2 = __toESM(require("path"), 1);
82
+ function copyFile(options) {
83
+ const { absPath, outputPath, outputDir, cache } = options;
84
+ if (cache.pathMap.has(absPath)) {
85
+ return;
86
+ }
87
+ const existingSource = cache.outputMap.get(outputPath);
88
+ if (existingSource && existingSource !== absPath) {
89
+ throw new Error(
90
+ `Filename collision detected (hashLength is 0)
91
+ - ${existingSource}
92
+ - ${absPath}
93
+ Both would output to: ${import_node_path2.default.join(outputDir, outputPath)}
94
+ Consider enabling hashLength or renaming one of the files.`
95
+ );
96
+ }
97
+ const destPath = import_node_path2.default.join(outputDir, outputPath);
98
+ const destDir = import_node_path2.default.dirname(destPath);
99
+ if (!import_node_fs2.default.existsSync(destDir)) {
100
+ import_node_fs2.default.mkdirSync(destDir, { recursive: true });
101
+ }
102
+ import_node_fs2.default.copyFileSync(absPath, destPath);
103
+ cache.pathMap.set(absPath, outputPath);
104
+ cache.outputMap.set(outputPath, absPath);
105
+ }
106
+
107
+ // src/steps/replaceNode.ts
108
+ function getVariableName(node) {
109
+ if (node.specifiers?.[0]?.type === "ImportDefaultSpecifier") {
110
+ return node.specifiers[0].local.name;
111
+ }
112
+ return void 0;
113
+ }
114
+ function replaceNode(scope, uri, types) {
115
+ const content = types.stringLiteral(uri);
116
+ if (scope.callee === "require") {
117
+ scope.path.replaceWith(content);
118
+ return;
119
+ }
120
+ const importPath = scope.path;
121
+ const variableName = getVariableName(importPath.node);
122
+ if (variableName) {
123
+ scope.path.replaceWith(
124
+ types.variableDeclaration("const", [
125
+ types.variableDeclarator(types.identifier(variableName), content)
126
+ ])
127
+ );
128
+ }
129
+ }
130
+
131
+ // src/transform.ts
132
+ function transform(scope, options, types, cache, projectRoot) {
133
+ const ext = import_node_path3.default.extname(scope.value);
134
+ if (!options.extensions || !options.extensions.includes(ext)) {
135
+ return;
136
+ }
137
+ const dir = import_node_path3.default.dirname(import_node_path3.default.resolve(scope.filename));
138
+ const absPath = import_node_path3.default.resolve(dir, scope.value);
139
+ if (absPath.includes("node_modules")) {
140
+ return;
141
+ }
142
+ const hashLength = options.hashLength ?? 8;
143
+ const hash = computeFileHash(absPath, hashLength);
144
+ const outputPath = buildOutputPath({
145
+ absPath,
146
+ hash,
147
+ preservePaths: options.preservePaths,
148
+ projectRoot
149
+ });
150
+ if (options.outputDir) {
151
+ copyFile({
152
+ absPath,
153
+ outputPath,
154
+ outputDir: options.outputDir,
155
+ cache
156
+ });
157
+ }
158
+ const baseUri = options.baseUri || "";
159
+ const separator = baseUri && !baseUri.endsWith("/") ? "/" : "";
160
+ const uri = `${baseUri}${separator}${outputPath}`;
161
+ replaceNode(scope, uri, types);
162
+ }
163
+
164
+ // src/index.ts
165
+ var DEFAULT_EXTENSIONS = [".gif", ".jpeg", ".jpg", ".png", ".svg"];
166
+ function isRequireStatement(path4) {
167
+ const callee = path4.get("callee");
168
+ return !Array.isArray(callee) && callee.isIdentifier() && callee.node.name === "require";
169
+ }
170
+ function isValidArgument(path4) {
171
+ const args = path4.get("arguments");
172
+ const arg = args[0];
173
+ return arg !== void 0 && arg.isStringLiteral();
174
+ }
175
+ var buildCache = null;
176
+ function plugin({
177
+ types: t
178
+ }) {
179
+ return {
180
+ name: "transform-assets-import-to-string",
181
+ pre() {
182
+ if (!buildCache) {
183
+ buildCache = {
184
+ pathMap: /* @__PURE__ */ new Map(),
185
+ outputMap: /* @__PURE__ */ new Map()
186
+ };
187
+ }
188
+ },
189
+ post() {
190
+ },
191
+ visitor: {
192
+ ImportDeclaration(nodePath, state) {
193
+ const opts = {
194
+ baseUri: "",
195
+ extensions: DEFAULT_EXTENSIONS,
196
+ hashLength: 8,
197
+ ...state.opts
198
+ };
199
+ const projectRoot = state.cwd || process.cwd();
200
+ transform(
201
+ {
202
+ path: nodePath,
203
+ filename: state.filename,
204
+ value: nodePath.node.source.value,
205
+ callee: "import"
206
+ },
207
+ opts,
208
+ t,
209
+ buildCache,
210
+ projectRoot
211
+ );
212
+ },
213
+ CallExpression(nodePath, state) {
214
+ if (isRequireStatement(nodePath) && isValidArgument(nodePath)) {
215
+ const args = nodePath.get("arguments");
216
+ const arg = args[0];
217
+ if (!arg.isStringLiteral()) return;
218
+ const opts = {
219
+ baseUri: "",
220
+ extensions: DEFAULT_EXTENSIONS,
221
+ hashLength: 8,
222
+ ...state.opts
223
+ };
224
+ const projectRoot = state.cwd || process.cwd();
225
+ transform(
226
+ {
227
+ path: nodePath,
228
+ filename: state.filename,
229
+ value: arg.node.value,
230
+ callee: "require"
231
+ },
232
+ opts,
233
+ t,
234
+ buildCache,
235
+ projectRoot
236
+ );
237
+ }
238
+ }
239
+ }
240
+ };
241
+ }
242
+ function resetBuildCache() {
243
+ buildCache = null;
244
+ }
245
+ // Annotate the CommonJS export names for ESM import in node:
246
+ 0 && (module.exports = {
247
+ resetBuildCache
248
+ });
249
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"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"]}
@@ -0,0 +1,27 @@
1
+ import * as _babel_types from '@babel/types';
2
+ import { PluginObj } from '@babel/core';
3
+
4
+ interface PluginOptions {
5
+ /** Base URL prefix (e.g., "https://cdn.example.com/assets") */
6
+ baseUri?: string;
7
+ /** Directory to copy assets to (enables file copying when set) */
8
+ outputDir?: string;
9
+ /** File extensions to transform (default: ['.gif', '.jpeg', '.jpg', '.png', '.svg']) */
10
+ extensions?: string[];
11
+ /** Content hash length in filename (default: 8, set 0 to disable) */
12
+ hashLength?: number;
13
+ /** Path base to strip when preserving paths (flattens if not set) */
14
+ preservePaths?: string;
15
+ }
16
+
17
+ interface PluginState {
18
+ opts: PluginOptions;
19
+ filename: string;
20
+ cwd: string;
21
+ }
22
+ declare function plugin({ types: t, }: {
23
+ types: typeof _babel_types;
24
+ }): PluginObj<PluginState>;
25
+ declare function resetBuildCache(): void;
26
+
27
+ export { type PluginOptions, plugin as default, resetBuildCache };
@@ -0,0 +1,27 @@
1
+ import * as _babel_types from '@babel/types';
2
+ import { PluginObj } from '@babel/core';
3
+
4
+ interface PluginOptions {
5
+ /** Base URL prefix (e.g., "https://cdn.example.com/assets") */
6
+ baseUri?: string;
7
+ /** Directory to copy assets to (enables file copying when set) */
8
+ outputDir?: string;
9
+ /** File extensions to transform (default: ['.gif', '.jpeg', '.jpg', '.png', '.svg']) */
10
+ extensions?: string[];
11
+ /** Content hash length in filename (default: 8, set 0 to disable) */
12
+ hashLength?: number;
13
+ /** Path base to strip when preserving paths (flattens if not set) */
14
+ preservePaths?: string;
15
+ }
16
+
17
+ interface PluginState {
18
+ opts: PluginOptions;
19
+ filename: string;
20
+ cwd: string;
21
+ }
22
+ declare function plugin({ types: t, }: {
23
+ types: typeof _babel_types;
24
+ }): PluginObj<PluginState>;
25
+ declare function resetBuildCache(): void;
26
+
27
+ export { type PluginOptions, plugin as default, resetBuildCache };