Skip to content
This repository was archived by the owner on Jun 28, 2025. It is now read-only.

Commit 43f275f

Browse files
committed
Provide tree depth on the LoaderContext while loading sourcemaps
It's important to know both the importer and the depth of the sourcemap tree when loading, to solve cases like babel/babel#14274. Here, a parent sourcemap contains a source location that, when resolved, has the same location as the parent. This easily happens when the file represents an in-place transformation of the original sourcemap (eg, `babel script.js --out-file script.js --source-maps`). Because they exist in the same location, it's extremely easy to create a loader which continues to return the transformed sourcemap when we intend to only return a `null` (no sourcemap) for the original source file.
1 parent 13c136f commit 43f275f

File tree

4 files changed

+100
-23
lines changed

4 files changed

+100
-23
lines changed

README.md

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
> Remap sequential sourcemaps through transformations to point at the original source code
44
5-
Remapping allows you to take the sourcemaps generated through transforming your code and
6-
"remap" them to the original source locations. Think "my minified code, transformed with babel and
7-
bundled with webpack", all pointing to the correct location in your original source code.
5+
Remapping allows you to take the sourcemaps generated through transforming your code and "remap"
6+
them to the original source locations. Think "my minified code, transformed with babel and bundled
7+
with webpack", all pointing to the correct location in your original source code.
88

99
With remapping, none of your source code transformations need to be aware of the input's sourcemap,
1010
they only need to generate an output sourcemap. This greatly simplifies building custom
@@ -25,12 +25,13 @@ function remapping(
2525
options?: { excludeContent: boolean, decodedMappings: boolean }
2626
): SourceMap;
2727

28-
// LoaderContext gives the loader the importing sourcemap, and the ability to override the "source"
29-
// location (where nested sources are resolved relative to, and where an original source exists),
30-
// and the ability to override the "content" of an original sourcemap for inclusion in the output
31-
// sourcemap.
28+
// LoaderContext gives the loader the importing sourcemap, tree depth, the ability to override the
29+
// "source" location (where child sources are resolved relative to, or the location of original
30+
// source), and the ability to override the "content" of an original source for inclusion in the
31+
// output sourcemap.
3232
type LoaderContext = {
3333
readonly importer: string;
34+
readonly depth: number;
3435
source: string;
3536
content: string | null | undefined;
3637
}
@@ -71,6 +72,9 @@ const remapped = remapping(
7172
if (file === 'transformed.js') {
7273
// The root importer is empty.
7374
console.assert(ctx.importer === '');
75+
// The depth in the sourcemap tree we're currently loading.
76+
// The root `minifiedTransformedMap` is depth 0, and its source children are depth 1, etc.
77+
console.assert(ctx.depth === 1);
7478

7579
return transformedMap;
7680
}
@@ -79,6 +83,8 @@ const remapped = remapping(
7983
console.assert(file === 'helloworld.js');
8084
// `transformed.js`'s sourcemap points into `helloworld.js`.
8185
console.assert(ctx.importer === 'transformed.js');
86+
// This is a source child of `transformed`, which is a source child of `minifiedTransformedMap`.
87+
console.assert(ctx.depth === 2);
8288
return null;
8389
}
8490
);
@@ -107,8 +113,9 @@ column of the 2nd line of the file `helloworld.js`".
107113
### Multiple transformations of a file
108114

109115
As a convenience, if you have multiple single-source transformations of a file, you may pass an
110-
array of sourcemap files in the order of most-recent transformation sourcemap first. So our above
111-
example could have been writen as:
116+
array of sourcemap files in the order of most-recent transformation sourcemap first. Note that this
117+
changes the `importer` and `depth` of each call to our loader. So our above example could have been
118+
written as:
112119

113120
```js
114121
const remapped = remapping(
@@ -130,9 +137,9 @@ console.log(remapped);
130137
#### `source`
131138

132139
The `source` property can overridden to any value to change the location of the current load. Eg,
133-
for an original source file, it allows us to change the filepath to the original source regardless
140+
for an original source file, it allows us to change the location to the original source regardless
134141
of what the sourcemap source entry says. And for transformed files, it allows us to change the
135-
resolving location for nested sources files of the loaded sourcemap.
142+
relative resolving location for child sources of the loaded sourcemap.
136143

137144
```js
138145
const remapped = remapping(
@@ -165,8 +172,9 @@ console.log(remapped);
165172
#### `content`
166173

167174
The `content` property can be overridden when we encounter an original source file. Eg, this allows
168-
you to manually provide the source content of the file regardless of whether the `sourcesContent`
169-
field is present in the parent sourcemap. Or, it can be set to `null` to remove the source content.
175+
you to manually provide the source content of the original file regardless of whether the
176+
`sourcesContent` field is present in the parent sourcemap. It can also be set to `null` to remove
177+
the source content.
170178

171179
```js
172180
const remapped = remapping(
@@ -199,13 +207,12 @@ console.log(remapped);
199207

200208
#### excludeContent
201209

202-
By default, `excludeContent` is `false`. Passing `{ excludeContent: true }`
203-
will exclude the `sourcesContent` field from the returned sourcemap. This is
204-
mainly useful when you want to reduce the size out the sourcemap.
210+
By default, `excludeContent` is `false`. Passing `{ excludeContent: true }` will exclude the
211+
`sourcesContent` field from the returned sourcemap. This is mainly useful when you want to reduce
212+
the size out the sourcemap.
205213

206214
#### decodedMappings
207215

208-
By default, `decodedMappings` is `false`. Passing `{ decodedMappings: true }`
209-
will leave the `mappings` field in a [decoded
210-
state](https://github.com/rich-harris/sourcemap-codec) instead of encoding
211-
into a VLQ string.
216+
By default, `decodedMappings` is `false`. Passing `{ decodedMappings: true }` will leave the
217+
`mappings` field in a [decoded state](https://github.com/rich-harris/sourcemap-codec) instead of
218+
encoding into a VLQ string.

src/build-source-map-tree.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,22 @@ export default function buildSourceMapTree(
3737
}
3838
}
3939

40-
let tree = build(map, '', loader);
40+
let tree = build(map, loader, '', 0);
4141
for (let i = maps.length - 1; i >= 0; i--) {
4242
tree = new SourceMapTree(maps[i], [tree]);
4343
}
4444
return tree;
4545
}
4646

47-
function build(map: TraceMap, importer: string, loader: SourceMapLoader): SourceMapTree {
47+
function build(
48+
map: TraceMap,
49+
loader: SourceMapLoader,
50+
importer: string,
51+
importerDepth: number
52+
): SourceMapTree {
4853
const { resolvedSources, sourcesContent } = map;
4954

55+
const depth = importerDepth + 1;
5056
const children = resolvedSources.map(
5157
(sourceFile: string | null, i: number): SourceMapTree | OriginalSource => {
5258
// The loading context gives the loader more information about why this file is being loaded
@@ -55,6 +61,7 @@ function build(map: TraceMap, importer: string, loader: SourceMapLoader): Source
5561
// an unmodified source file.
5662
const ctx: LoaderContext = {
5763
importer,
64+
depth,
5865
source: sourceFile || '',
5966
content: undefined,
6067
};
@@ -77,7 +84,7 @@ function build(map: TraceMap, importer: string, loader: SourceMapLoader): Source
7784

7885
// Else, it's a real sourcemap, and we need to recurse into it to load its
7986
// source files.
80-
return build(new TraceMap(sourceMap, source), source, loader);
87+
return build(new TraceMap(sourceMap, source), loader, source, depth);
8188
}
8289
);
8390

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export type SourceMapInput = string | RawSourceMap | DecodedSourceMap;
3838

3939
export type LoaderContext = {
4040
readonly importer: string;
41+
readonly depth: number;
4142
source: string;
4243
content: string | null | undefined;
4344
};

test/unit/build-source-map-tree.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,68 @@ describe('buildSourceMapTree', () => {
199199
});
200200
});
201201

202+
describe('depty', () => {
203+
test('is 1 for sources loaded from the root', () => {
204+
const loader = jest.fn();
205+
buildSourceMapTree(
206+
{
207+
...decodedMap,
208+
sources: ['first.js', 'second.js'],
209+
},
210+
loader
211+
);
212+
213+
expect(loader).toHaveBeenCalledTimes(2);
214+
expect(loader).toHaveBeenCalledWith(
215+
'first.js',
216+
expect.objectContaining({
217+
depth: 1,
218+
})
219+
);
220+
expect(loader).toHaveBeenCalledWith(
221+
'second.js',
222+
expect.objectContaining({
223+
depth: 1,
224+
})
225+
);
226+
});
227+
228+
test('is increased for nested sources', () => {
229+
const loader = jest.fn();
230+
loader.mockReturnValueOnce({
231+
...rawMap,
232+
sources: ['two.js'],
233+
});
234+
buildSourceMapTree(
235+
{
236+
...decodedMap,
237+
sources: ['first.js', 'second.js'],
238+
},
239+
loader
240+
);
241+
242+
expect(loader).toHaveBeenCalledTimes(3);
243+
expect(loader).toHaveBeenCalledWith(
244+
'first.js',
245+
expect.objectContaining({
246+
depth: 1,
247+
})
248+
);
249+
expect(loader).toHaveBeenCalledWith(
250+
'two.js',
251+
expect.objectContaining({
252+
depth: 2,
253+
})
254+
);
255+
expect(loader).toHaveBeenCalledWith(
256+
'second.js',
257+
expect.objectContaining({
258+
depth: 1,
259+
})
260+
);
261+
});
262+
});
263+
202264
describe('source', () => {
203265
test('matches the loader source param', () => {
204266
const loader = jest.fn();

0 commit comments

Comments
 (0)