rolldown-plugin-sourcemaps 0.1.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/LICENSE +21 -0
- package/README.md +100 -0
- package/dist/index.d.mts +18 -0
- package/dist/index.mjs +213 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Kudakwashe Mupeni
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# rolldown-plugin-sourcemaps
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/rolldown-plugin-sourcemaps)
|
|
4
|
+
[](https://github.com/rtvision/rolldown-plugin-sourcemaps/blob/main/LICENSE)
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
[Rolldown](https://rolldown.rs) plugin for loading files with existing source maps from `sourceMappingURL` comments.
|
|
8
|
+
|
|
9
|
+
Inspired by [webpack/source-map-loader](https://github.com/webpack/source-map-loader).
|
|
10
|
+
|
|
11
|
+
## Why?
|
|
12
|
+
|
|
13
|
+
- You consume external modules (from `node_modules`) with bundled source maps
|
|
14
|
+
- You want accurate stack traces pointing to original source code
|
|
15
|
+
- You need to chain source maps from pre-compiled dependencies
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install rolldown-plugin-sourcemaps --save-dev
|
|
21
|
+
# or
|
|
22
|
+
pnpm add rolldown-plugin-sourcemaps -D
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### With Vite (Rolldown)
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
import sourcemaps from 'rolldown-plugin-sourcemaps';
|
|
31
|
+
import { defineConfig } from 'vite';
|
|
32
|
+
|
|
33
|
+
export default defineConfig({
|
|
34
|
+
build: {
|
|
35
|
+
sourcemap: true,
|
|
36
|
+
rollupOptions: {
|
|
37
|
+
plugins: [sourcemaps()]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### With Rolldown directly
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
import sourcemaps from 'rolldown-plugin-sourcemaps';
|
|
47
|
+
|
|
48
|
+
export default {
|
|
49
|
+
input: 'src/index.js',
|
|
50
|
+
plugins: [sourcemaps()],
|
|
51
|
+
output: {
|
|
52
|
+
sourcemap: true,
|
|
53
|
+
file: 'dist/bundle.js',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Options
|
|
59
|
+
|
|
60
|
+
### `include`
|
|
61
|
+
|
|
62
|
+
Type: `string | RegExp | (string | RegExp)[]`
|
|
63
|
+
|
|
64
|
+
Default: `/[/\\]node_modules[/\\]/` (only processes files in `node_modules`)
|
|
65
|
+
|
|
66
|
+
Files to include for source map processing. By default, only files in `node_modules` are processed since that's where pre-compiled source maps typically exist.
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
// Process all files
|
|
70
|
+
sourcemaps({ include: /./ })
|
|
71
|
+
|
|
72
|
+
// Only process specific packages
|
|
73
|
+
sourcemaps({ include: /[/\\]node_modules[/\\]@myorg[/\\]/ })
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `exclude`
|
|
77
|
+
|
|
78
|
+
Type: `string | RegExp | (string | RegExp)[]`
|
|
79
|
+
|
|
80
|
+
Default: `undefined`
|
|
81
|
+
|
|
82
|
+
Files to exclude from source map processing.
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
// Exclude specific packages
|
|
86
|
+
sourcemaps({ exclude: /[/\\]node_modules[/\\]some-large-package[/\\]/ })
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Performance
|
|
90
|
+
|
|
91
|
+
This plugin is optimized for performance:
|
|
92
|
+
|
|
93
|
+
- **Targeted processing**: Only processes `node_modules` by default (where pre-compiled source maps exist)
|
|
94
|
+
- **Fast detection**: Uses simple string matching before regex parsing
|
|
95
|
+
- **Parallel I/O**: Resolves multiple source files in parallel
|
|
96
|
+
- **Early termination**: Skips unnecessary work when `sourcesContent` already exists
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Plugin } from "rolldown";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
interface SourcemapsPluginOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Files to include. Defaults to node_modules since that's where
|
|
7
|
+
* pre-compiled sourcemaps typically exist.
|
|
8
|
+
*/
|
|
9
|
+
include?: string | RegExp | (string | RegExp)[];
|
|
10
|
+
/**
|
|
11
|
+
* Files to exclude from processing.
|
|
12
|
+
*/
|
|
13
|
+
exclude?: string | RegExp | (string | RegExp)[];
|
|
14
|
+
}
|
|
15
|
+
declare function sourcemaps(options?: SourcemapsPluginOptions): Plugin;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { SourcemapsPluginOptions, sourcemaps as default };
|
|
18
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import path$1 from "node:path";
|
|
2
|
+
|
|
3
|
+
//#region src/decode-uri-component.ts
|
|
4
|
+
const TOKEN_PATTERN = "%[a-f0-9]{2}";
|
|
5
|
+
const singleMatcher = new RegExp(`(${TOKEN_PATTERN})|([^%]+?)`, "gi");
|
|
6
|
+
const multiMatcher = new RegExp(`(${TOKEN_PATTERN})+`, "gi");
|
|
7
|
+
function decodeComponents(components, split) {
|
|
8
|
+
try {
|
|
9
|
+
return [decodeURIComponent(components.join(""))];
|
|
10
|
+
} catch {}
|
|
11
|
+
if (components.length === 1) return components;
|
|
12
|
+
const splitAt = split ?? 1;
|
|
13
|
+
const left = components.slice(0, splitAt);
|
|
14
|
+
const right = components.slice(splitAt);
|
|
15
|
+
return [...decodeComponents(left), ...decodeComponents(right)];
|
|
16
|
+
}
|
|
17
|
+
function decode(input) {
|
|
18
|
+
try {
|
|
19
|
+
return decodeURIComponent(input);
|
|
20
|
+
} catch {
|
|
21
|
+
singleMatcher.lastIndex = 0;
|
|
22
|
+
let tokens = singleMatcher.exec(input) || [];
|
|
23
|
+
let localInput = input;
|
|
24
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
25
|
+
localInput = decodeComponents(tokens, i).join("");
|
|
26
|
+
singleMatcher.lastIndex = 0;
|
|
27
|
+
tokens = singleMatcher.exec(localInput) || [];
|
|
28
|
+
}
|
|
29
|
+
return localInput;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function customDecodeURIComponent(input) {
|
|
33
|
+
const replaceMap = {
|
|
34
|
+
"%FE%FF": "��",
|
|
35
|
+
"%FF%FE": "��"
|
|
36
|
+
};
|
|
37
|
+
multiMatcher.lastIndex = 0;
|
|
38
|
+
let match = multiMatcher.exec(input);
|
|
39
|
+
while (match) {
|
|
40
|
+
const matchStr = match[0];
|
|
41
|
+
try {
|
|
42
|
+
replaceMap[matchStr] = decodeURIComponent(matchStr);
|
|
43
|
+
} catch {
|
|
44
|
+
const result = decode(matchStr);
|
|
45
|
+
if (result !== matchStr) replaceMap[matchStr] = result;
|
|
46
|
+
}
|
|
47
|
+
match = multiMatcher.exec(input);
|
|
48
|
+
}
|
|
49
|
+
replaceMap["%C2"] = "�";
|
|
50
|
+
let result = input;
|
|
51
|
+
for (const key of Object.keys(replaceMap)) result = result.replaceAll(key, replaceMap[key]);
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
function decodeUriComponent(encodedURI) {
|
|
55
|
+
if (typeof encodedURI !== "string") throw new TypeError(`Expected \`encodedURI\` to be of type \`string\`, got \`${typeof encodedURI}\``);
|
|
56
|
+
try {
|
|
57
|
+
return decodeURIComponent(encodedURI);
|
|
58
|
+
} catch {
|
|
59
|
+
return customDecodeURIComponent(encodedURI);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/source-map-resolve.ts
|
|
65
|
+
function resolveUrl(base, ...paths) {
|
|
66
|
+
let resolved = base;
|
|
67
|
+
for (const p of paths) resolved = path$1.resolve(path$1.dirname(resolved), p);
|
|
68
|
+
return resolved;
|
|
69
|
+
}
|
|
70
|
+
const PLUS_SIGN_REGEX = /\+/g;
|
|
71
|
+
const XSSI_PREFIX_REGEX = /^\)\]\}'/;
|
|
72
|
+
const DATA_URI_REGEX = /^data:([^,;]*)(;[^,;]*)*(?:,(.*))?$/;
|
|
73
|
+
const JSON_MIME_REGEX = /^(?:application|text)\/json$/;
|
|
74
|
+
const TRAILING_SLASH_REGEX = /\/?$/;
|
|
75
|
+
const sourceMappingURLRegex = /(?:\/\*(?:\s*\r?\n(?:\/\/)?)?(?:[#@] sourceMappingURL=([^\s'"]*))\s*\*\/|\/\/(?:[#@] sourceMappingURL=([^\s'"]*)))\s*/g;
|
|
76
|
+
function customDecodeUriComponent(encodedURI) {
|
|
77
|
+
return decodeUriComponent(encodedURI.replace(PLUS_SIGN_REGEX, "%2B"));
|
|
78
|
+
}
|
|
79
|
+
function parseMapToJSON(str) {
|
|
80
|
+
return JSON.parse(str.replace(XSSI_PREFIX_REGEX, ""));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Fast check if code contains a sourceMappingURL - used as early exit optimization
|
|
84
|
+
*/
|
|
85
|
+
function hasSourceMappingURL(code) {
|
|
86
|
+
return code.includes("sourceMappingURL=");
|
|
87
|
+
}
|
|
88
|
+
function getSourceMappingUrl(code) {
|
|
89
|
+
if (!hasSourceMappingURL(code)) return null;
|
|
90
|
+
sourceMappingURLRegex.lastIndex = 0;
|
|
91
|
+
let lastMatch = null;
|
|
92
|
+
let match;
|
|
93
|
+
while ((match = sourceMappingURLRegex.exec(code)) !== null) lastMatch = match;
|
|
94
|
+
return lastMatch ? lastMatch[1] || lastMatch[2] || "" : null;
|
|
95
|
+
}
|
|
96
|
+
async function resolveSourceMap(code, codeUrl, read) {
|
|
97
|
+
const sourceMappingURL = getSourceMappingUrl(code);
|
|
98
|
+
if (!sourceMappingURL) return null;
|
|
99
|
+
const dataUri = DATA_URI_REGEX.exec(sourceMappingURL);
|
|
100
|
+
if (dataUri) {
|
|
101
|
+
const mimeType = dataUri[1] || "text/plain";
|
|
102
|
+
if (!JSON_MIME_REGEX.test(mimeType)) throw new Error(`Unuseful data uri mime type: ${mimeType}`);
|
|
103
|
+
return {
|
|
104
|
+
sourceMappingURL,
|
|
105
|
+
url: null,
|
|
106
|
+
sourcesRelativeTo: codeUrl,
|
|
107
|
+
map: parseMapToJSON((dataUri[2] === ";base64" ? atob : decodeURIComponent)(dataUri[3] || ""))
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const url = resolveUrl(codeUrl, sourceMappingURL);
|
|
111
|
+
return {
|
|
112
|
+
sourceMappingURL,
|
|
113
|
+
url,
|
|
114
|
+
sourcesRelativeTo: url,
|
|
115
|
+
map: parseMapToJSON(await read(customDecodeUriComponent(url)))
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async function resolveSources(map, mapUrl, read) {
|
|
119
|
+
const sources = map.sources ?? [];
|
|
120
|
+
const existingContent = map.sourcesContent;
|
|
121
|
+
const sourceRoot = map.sourceRoot;
|
|
122
|
+
if (existingContent && existingContent.length >= sources.length && existingContent.every((c) => typeof c === "string")) return {
|
|
123
|
+
sourcesResolved: sources.map((source) => {
|
|
124
|
+
if (source === null) return "";
|
|
125
|
+
const resolvePaths = [source];
|
|
126
|
+
if (sourceRoot !== void 0 && sourceRoot !== "" && sourceRoot !== null) resolvePaths.unshift(sourceRoot.replace(TRAILING_SLASH_REGEX, "/"));
|
|
127
|
+
return resolveUrl(mapUrl, ...resolvePaths);
|
|
128
|
+
}),
|
|
129
|
+
sourcesContent: existingContent
|
|
130
|
+
};
|
|
131
|
+
const sourcesResolved = Array.from({ length: sources.length });
|
|
132
|
+
for (let i = 0; i < sources.length; i++) {
|
|
133
|
+
const source = sources[i];
|
|
134
|
+
if (source === null) {
|
|
135
|
+
sourcesResolved[i] = "";
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const resolvePaths = [source];
|
|
139
|
+
if (sourceRoot !== void 0 && sourceRoot !== "" && sourceRoot !== null) resolvePaths.unshift(sourceRoot.replace(TRAILING_SLASH_REGEX, "/"));
|
|
140
|
+
sourcesResolved[i] = resolveUrl(mapUrl, ...resolvePaths);
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
sourcesResolved,
|
|
144
|
+
sourcesContent: await Promise.all(sources.map(async (source, i) => {
|
|
145
|
+
if (source === null) return "";
|
|
146
|
+
const sourceContent = existingContent?.[i];
|
|
147
|
+
if (typeof sourceContent === "string") return sourceContent;
|
|
148
|
+
try {
|
|
149
|
+
return await read(customDecodeUriComponent(sourcesResolved[i]));
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return error;
|
|
152
|
+
}
|
|
153
|
+
}))
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/index.ts
|
|
159
|
+
const QUERY_SUFFIX_REGEX = /\?.*$/;
|
|
160
|
+
const DEFAULT_INCLUDE = /[/\\]node_modules[/\\]/;
|
|
161
|
+
function sourcemaps(options = {}) {
|
|
162
|
+
return {
|
|
163
|
+
name: "sourcemaps",
|
|
164
|
+
load: {
|
|
165
|
+
filter: { id: {
|
|
166
|
+
include: options.include ?? DEFAULT_INCLUDE,
|
|
167
|
+
exclude: options.exclude
|
|
168
|
+
} },
|
|
169
|
+
async handler(id) {
|
|
170
|
+
const readFile = async (path) => {
|
|
171
|
+
return this.fs.readFile(path, { encoding: "utf8" });
|
|
172
|
+
};
|
|
173
|
+
let code;
|
|
174
|
+
let actualPath = id;
|
|
175
|
+
try {
|
|
176
|
+
code = await readFile(id);
|
|
177
|
+
} catch {
|
|
178
|
+
actualPath = id.replace(QUERY_SUFFIX_REGEX, "");
|
|
179
|
+
try {
|
|
180
|
+
code = await readFile(actualPath);
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
this.addWatchFile(actualPath);
|
|
186
|
+
if (!hasSourceMappingURL(code)) return code;
|
|
187
|
+
let map;
|
|
188
|
+
try {
|
|
189
|
+
const result = await resolveSourceMap(code, actualPath, readFile);
|
|
190
|
+
if (result === null) return code;
|
|
191
|
+
map = result.map;
|
|
192
|
+
} catch {
|
|
193
|
+
this.warn("Failed resolving source map");
|
|
194
|
+
return code;
|
|
195
|
+
}
|
|
196
|
+
if (map.sourcesContent === void 0) try {
|
|
197
|
+
const { sourcesContent } = await resolveSources(map, actualPath, readFile);
|
|
198
|
+
if (sourcesContent.every((item) => typeof item === "string")) map.sourcesContent = sourcesContent;
|
|
199
|
+
} catch {
|
|
200
|
+
this.warn("Failed resolving sources for source map");
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
code,
|
|
204
|
+
map
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
//#endregion
|
|
212
|
+
export { sourcemaps as default };
|
|
213
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["path"],"sources":["../src/decode-uri-component.ts","../src/source-map-resolve.ts","../src/index.ts"],"sourcesContent":["// Pre-compiled regex patterns - avoid creating in hot paths\nconst TOKEN_PATTERN = '%[a-f0-9]{2}';\nconst singleMatcher = new RegExp(`(${TOKEN_PATTERN})|([^%]+?)`, 'gi');\nconst multiMatcher = new RegExp(`(${TOKEN_PATTERN})+`, 'gi');\n\nfunction decodeComponents(\n components: RegExpMatchArray | [],\n split?: number,\n): string[] {\n try {\n // Try to decode the entire string first\n return [decodeURIComponent(components.join(''))];\n } catch {\n // Do nothing\n }\n\n if (components.length === 1) {\n return components;\n }\n\n const splitAt = split ?? 1;\n\n // Split the array in 2 parts\n const left = components.slice(0, splitAt) as RegExpMatchArray;\n const right = components.slice(splitAt) as RegExpMatchArray;\n\n return [...decodeComponents(left), ...decodeComponents(right)];\n}\n\nfunction decode(input: string): string {\n try {\n return decodeURIComponent(input);\n } catch {\n // Reset lastIndex for stateful regex\n singleMatcher.lastIndex = 0;\n let tokens = singleMatcher.exec(input) || [];\n let localInput = input;\n\n for (let i = 1; i < tokens.length; i++) {\n localInput = decodeComponents(tokens, i).join('');\n singleMatcher.lastIndex = 0;\n tokens = singleMatcher.exec(localInput) || [];\n }\n\n return localInput;\n }\n}\n\nfunction customDecodeURIComponent(input: string): string {\n // Keep track of all the replacements and prefill the map with the `BOM`\n const replaceMap: Record<string, string> = {\n '%FE%FF': '\\uFFFD\\uFFFD',\n '%FF%FE': '\\uFFFD\\uFFFD',\n };\n\n // Reset lastIndex for stateful regex\n multiMatcher.lastIndex = 0;\n let match = multiMatcher.exec(input);\n\n while (match) {\n const matchStr = match[0];\n try {\n // Decode as big chunks as possible\n replaceMap[matchStr] = decodeURIComponent(matchStr);\n } catch {\n const result = decode(matchStr);\n if (result !== matchStr) {\n replaceMap[matchStr] = result;\n }\n }\n match = multiMatcher.exec(input);\n }\n\n // Add `%C2` at the end of the map to make sure it does not replace the combinator before everything else\n replaceMap['%C2'] = '\\uFFFD';\n\n // Use replaceAll with string instead of creating new RegExp in loop\n let result = input;\n for (const key of Object.keys(replaceMap)) {\n result = result.replaceAll(key, replaceMap[key]);\n }\n\n return result;\n}\n\nexport default function decodeUriComponent(encodedURI: string): string {\n if (typeof encodedURI !== 'string') {\n throw new TypeError(\n `Expected \\`encodedURI\\` to be of type \\`string\\`, got \\`${typeof encodedURI}\\``,\n );\n }\n\n try {\n // Try the built in decoder first\n return decodeURIComponent(encodedURI);\n } catch {\n // Fallback to a more advanced decoder\n return customDecodeURIComponent(encodedURI);\n }\n}\n","import path from 'node:path';\nimport type { ExistingRawSourceMap } from 'rolldown';\nimport decodeUriComponent from './decode-uri-component.js';\n\ninterface ResolvedSources {\n sourcesResolved: string[];\n sourcesContent: (string | Error)[];\n}\n\ninterface ResolvedSourceMap {\n map: ExistingRawSourceMap;\n url: string | null;\n sourcesRelativeTo: string;\n sourceMappingURL: string;\n}\n\nfunction resolveUrl(base: string, ...paths: string[]): string {\n let resolved = base;\n for (const p of paths) {\n resolved = path.resolve(path.dirname(resolved), p);\n }\n return resolved;\n}\n\n// Pre-compiled regex patterns for performance\nconst PLUS_SIGN_REGEX = /\\+/g;\nconst XSSI_PREFIX_REGEX = /^\\)\\]\\}'/;\nconst DATA_URI_REGEX = /^data:([^,;]*)(;[^,;]*)*(?:,(.*))?$/;\nconst JSON_MIME_REGEX = /^(?:application|text)\\/json$/;\nconst TRAILING_SLASH_REGEX = /\\/?$/;\nconst sourceMappingURLRegex =\n /(?:\\/\\*(?:\\s*\\r?\\n(?:\\/\\/)?)?(?:[#@] sourceMappingURL=([^\\s'\"]*))\\s*\\*\\/|\\/\\/(?:[#@] sourceMappingURL=([^\\s'\"]*)))\\s*/g;\n\nfunction customDecodeUriComponent(encodedURI: string): string {\n return decodeUriComponent(encodedURI.replace(PLUS_SIGN_REGEX, '%2B'));\n}\n\nfunction parseMapToJSON(str: string): ExistingRawSourceMap {\n return JSON.parse(str.replace(XSSI_PREFIX_REGEX, '')) as ExistingRawSourceMap;\n}\n\n/**\n * Fast check if code contains a sourceMappingURL - used as early exit optimization\n */\nexport function hasSourceMappingURL(code: string): boolean {\n return code.includes('sourceMappingURL=');\n}\n\nfunction getSourceMappingUrl(code: string): string | null {\n // Fast path: check if there's any sourceMappingURL at all\n if (!hasSourceMappingURL(code)) {\n return null;\n }\n\n // Reset lastIndex for stateful global regex\n sourceMappingURLRegex.lastIndex = 0;\n\n let lastMatch: RegExpExecArray | null = null;\n let match: RegExpExecArray | null;\n\n // Use exec loop instead of matchAll + Array.from + pop (more efficient)\n while ((match = sourceMappingURLRegex.exec(code)) !== null) {\n lastMatch = match;\n }\n\n return lastMatch ? lastMatch[1] || lastMatch[2] || '' : null;\n}\n\nexport async function resolveSourceMap(\n code: string,\n codeUrl: string,\n read: (path: string) => Promise<string>,\n): Promise<ResolvedSourceMap | null> {\n const sourceMappingURL = getSourceMappingUrl(code);\n if (!sourceMappingURL) return null;\n\n const dataUri = DATA_URI_REGEX.exec(sourceMappingURL);\n if (dataUri) {\n const mimeType = dataUri[1] || 'text/plain';\n if (!JSON_MIME_REGEX.test(mimeType)) {\n throw new Error(`Unuseful data uri mime type: ${mimeType}`);\n }\n const map = parseMapToJSON(\n (dataUri[2] === ';base64' ? atob : decodeURIComponent)(dataUri[3] || ''),\n );\n return { sourceMappingURL, url: null, sourcesRelativeTo: codeUrl, map };\n }\n\n const url = resolveUrl(codeUrl, sourceMappingURL);\n const map = parseMapToJSON(await read(customDecodeUriComponent(url)));\n return { sourceMappingURL, url, sourcesRelativeTo: url, map };\n}\n\nexport async function resolveSources(\n map: ExistingRawSourceMap,\n mapUrl: string,\n read: (path: string) => Promise<string>,\n): Promise<ResolvedSources> {\n const sources = map.sources ?? [];\n const existingContent = map.sourcesContent;\n const sourceRoot = map.sourceRoot;\n\n // Early termination: if all sourcesContent already exist, skip file reads entirely\n if (\n existingContent &&\n existingContent.length >= sources.length &&\n existingContent.every((c) => typeof c === 'string')\n ) {\n // Build sourcesResolved paths without reading any files\n const sourcesResolved = sources.map((source) => {\n if (source === null) return '';\n const resolvePaths = [source];\n if (sourceRoot !== undefined && sourceRoot !== '' && sourceRoot !== null) {\n resolvePaths.unshift(sourceRoot.replace(TRAILING_SLASH_REGEX, '/'));\n }\n return resolveUrl(mapUrl, ...resolvePaths);\n });\n return { sourcesResolved, sourcesContent: existingContent as string[] };\n }\n\n // Pre-compute all resolved paths\n const sourcesResolved: string[] = Array.from({ length: sources.length });\n for (let i = 0; i < sources.length; i++) {\n const source = sources[i];\n if (source === null) {\n sourcesResolved[i] = '';\n continue;\n }\n const resolvePaths = [source];\n if (sourceRoot !== undefined && sourceRoot !== '' && sourceRoot !== null) {\n resolvePaths.unshift(sourceRoot.replace(TRAILING_SLASH_REGEX, '/'));\n }\n sourcesResolved[i] = resolveUrl(mapUrl, ...resolvePaths);\n }\n\n // Parallel file reads for sources that need content\n const sourcesContent: (string | Error)[] = await Promise.all(\n sources.map(async (source, i) => {\n if (source === null) return '' as string;\n\n // Use existing content if available\n const sourceContent = existingContent?.[i];\n if (typeof sourceContent === 'string') {\n return sourceContent;\n }\n\n // Read the file\n try {\n return await read(customDecodeUriComponent(sourcesResolved[i]));\n } catch (error) {\n return error as Error;\n }\n }),\n );\n\n return { sourcesResolved, sourcesContent };\n}\n","import type { ExistingRawSourceMap, Plugin, PluginContext } from \"rolldown\";\nimport {\n\thasSourceMappingURL,\n\tresolveSourceMap,\n\tresolveSources,\n} from \"./source-map-resolve.js\";\n\nexport interface SourcemapsPluginOptions {\n\t/**\n\t * Files to include. Defaults to node_modules since that's where\n\t * pre-compiled sourcemaps typically exist.\n\t */\n\tinclude?: string | RegExp | (string | RegExp)[];\n\t/**\n\t * Files to exclude from processing.\n\t */\n\texclude?: string | RegExp | (string | RegExp)[];\n}\n\n// Pre-compiled regex for stripping query suffix\nconst QUERY_SUFFIX_REGEX = /\\?.*$/;\n\n// Default: only process files in node_modules (where pre-compiled sourcemaps exist)\nconst DEFAULT_INCLUDE = /[/\\\\]node_modules[/\\\\]/;\n\nexport default function sourcemaps(\n\toptions: SourcemapsPluginOptions = {},\n): Plugin {\n\treturn {\n\t\tname: \"sourcemaps\",\n\n\t\tload: {\n\t\t\tfilter: {\n\t\t\t\tid: {\n\t\t\t\t\tinclude: options.include ?? DEFAULT_INCLUDE,\n\t\t\t\t\texclude: options.exclude,\n\t\t\t\t},\n\t\t\t},\n\t\t\tasync handler(this: PluginContext, id: string) {\n\t\t\t\t// Use Rolldown's native fs from plugin context\n\t\t\t\tconst readFile = async (path: string): Promise<string> => {\n\t\t\t\t\treturn this.fs.readFile(path, { encoding: \"utf8\" });\n\t\t\t\t};\n\n\t\t\t\tlet code: string;\n\t\t\t\tlet actualPath = id;\n\t\t\t\ttry {\n\t\t\t\t\tcode = await readFile(id);\n\t\t\t\t} catch {\n\t\t\t\t\t// Try without query suffix that some plugins use\n\t\t\t\t\tactualPath = id.replace(QUERY_SUFFIX_REGEX, \"\");\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcode = await readFile(actualPath);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// File cannot be read - let other plugins handle it\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthis.addWatchFile(actualPath);\n\n\t\t\t\t// Fast path: skip if no sourceMappingURL present (most files)\n\t\t\t\tif (!hasSourceMappingURL(code)) {\n\t\t\t\t\treturn code;\n\t\t\t\t}\n\n\t\t\t\tlet map: ExistingRawSourceMap;\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await resolveSourceMap(code, actualPath, readFile);\n\t\t\t\t\tif (result === null) return code;\n\t\t\t\t\tmap = result.map;\n\t\t\t\t} catch {\n\t\t\t\t\tthis.warn(\"Failed resolving source map\");\n\t\t\t\t\treturn code;\n\t\t\t\t}\n\n\t\t\t\t// Only resolve sources if sourcesContent is missing\n\t\t\t\tif (map.sourcesContent === undefined) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst { sourcesContent } = await resolveSources(\n\t\t\t\t\t\t\tmap,\n\t\t\t\t\t\t\tactualPath,\n\t\t\t\t\t\t\treadFile,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (sourcesContent.every((item) => typeof item === \"string\")) {\n\t\t\t\t\t\t\tmap.sourcesContent = sourcesContent as string[];\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tthis.warn(\"Failed resolving sources for source map\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn { code, map };\n\t\t\t},\n\t\t},\n\t};\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rolldown-plugin-sourcemaps",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Rolldown plugin for grabbing source maps from sourceMappingURLs",
|
|
5
|
+
"author": "RTVision",
|
|
6
|
+
"contributors": [
|
|
7
|
+
"Kudakwashe Mupeni <said.coyness-0m@icloud.com>",
|
|
8
|
+
"Max Davidson <max.davidson.mail@gmail.com>"
|
|
9
|
+
],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.mjs",
|
|
13
|
+
"types": "./dist/index.d.mts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.mts",
|
|
17
|
+
"import": "./dist/index.mjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/rtvision/rolldown-plugin-sourcemaps.git"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/rtvision/rolldown-plugin-sourcemaps/issues"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/rtvision/rolldown-plugin-sourcemaps#readme",
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsdown",
|
|
34
|
+
"lint": "oxlint --type-aware --fix",
|
|
35
|
+
"lint:ci": "oxlint --type-aware",
|
|
36
|
+
"format": "oxfmt",
|
|
37
|
+
"format:check": "oxfmt --check",
|
|
38
|
+
"test": "vitest",
|
|
39
|
+
"prebuild": "oxlint --type-aware --type-check",
|
|
40
|
+
"prepublishOnly": "pnpm build",
|
|
41
|
+
"release": "changeset publish"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"rolldown",
|
|
45
|
+
"rolldown-plugin",
|
|
46
|
+
"sourcemap",
|
|
47
|
+
"source-map",
|
|
48
|
+
"sourceMappingURL"
|
|
49
|
+
],
|
|
50
|
+
"files": [
|
|
51
|
+
"dist"
|
|
52
|
+
],
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"rolldown": ">=1.0.0-rc.1"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@changesets/cli": "^2.29.7",
|
|
58
|
+
"@types/node": "^22.15.21",
|
|
59
|
+
"oxfmt": "^0.26.0",
|
|
60
|
+
"oxlint": "^1.41.0",
|
|
61
|
+
"oxlint-tsgolint": "^0.11.1",
|
|
62
|
+
"rolldown": "^1.0.0-rc.1",
|
|
63
|
+
"tsdown": "^0.20.1",
|
|
64
|
+
"typescript": "^5.8.3",
|
|
65
|
+
"vite": "^7.2.4",
|
|
66
|
+
"vitest": "^4.0.13"
|
|
67
|
+
}
|
|
68
|
+
}
|