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 +186 -24
- package/dist/index.cjs +249 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +212 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -28
- package/.babelrc +0 -11
- package/.editorconfig +0 -15
- package/.eslintrc.json +0 -20
- package/.npmignore +0 -5
- package/.nycrc +0 -8
- package/circle.yml +0 -27
- package/lib/index.js +0 -58
- package/lib/transform.js +0 -63
- package/yarn.lock +0 -3084
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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 = '
|
|
28
|
-
const image1 = '
|
|
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.
|
|
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": [
|
|
53
|
-
|
|
54
|
-
|
|
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(
|
|
63
|
-
plugins: [
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
78
|
-
"
|
|
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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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 };
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|