diff --git a/README.md b/README.md index 07abb24..1463c9f 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ > Remap sequential sourcemaps through transformations to point at the original source code -Remapping allows you to take the sourcemaps generated through transforming your code and -"remap" them to the original source locations. Think "my minified code, transformed with babel and -bundled with webpack", all pointing to the correct location in your original source code. +Remapping allows you to take the sourcemaps generated through transforming your code and "remap" +them to the original source locations. Think "my minified code, transformed with babel and bundled +with webpack", all pointing to the correct location in your original source code. With remapping, none of your source code transformations need to be aware of the input's sourcemap, they only need to generate an output sourcemap. This greatly simplifies building custom @@ -25,12 +25,13 @@ function remapping( options?: { excludeContent: boolean, decodedMappings: boolean } ): SourceMap; -// LoaderContext gives the loader the importing sourcemap, and the ability to override the "source" -// location (where nested sources are resolved relative to, and where an original source exists), -// and the ability to override the "content" of an original sourcemap for inclusion in the output -// sourcemap. +// LoaderContext gives the loader the importing sourcemap, tree depth, the ability to override the +// "source" location (where child sources are resolved relative to, or the location of original +// source), and the ability to override the "content" of an original source for inclusion in the +// output sourcemap. type LoaderContext = { readonly importer: string; + readonly depth: number; source: string; content: string | null | undefined; } @@ -71,6 +72,9 @@ const remapped = remapping( if (file === 'transformed.js') { // The root importer is empty. console.assert(ctx.importer === ''); + // The depth in the sourcemap tree we're currently loading. + // The root `minifiedTransformedMap` is depth 0, and its source children are depth 1, etc. + console.assert(ctx.depth === 1); return transformedMap; } @@ -79,6 +83,8 @@ const remapped = remapping( console.assert(file === 'helloworld.js'); // `transformed.js`'s sourcemap points into `helloworld.js`. console.assert(ctx.importer === 'transformed.js'); + // This is a source child of `transformed`, which is a source child of `minifiedTransformedMap`. + console.assert(ctx.depth === 2); return null; } ); @@ -107,8 +113,9 @@ column of the 2nd line of the file `helloworld.js`". ### Multiple transformations of a file As a convenience, if you have multiple single-source transformations of a file, you may pass an -array of sourcemap files in the order of most-recent transformation sourcemap first. So our above -example could have been writen as: +array of sourcemap files in the order of most-recent transformation sourcemap first. Note that this +changes the `importer` and `depth` of each call to our loader. So our above example could have been +written as: ```js const remapped = remapping( @@ -130,9 +137,9 @@ console.log(remapped); #### `source` The `source` property can overridden to any value to change the location of the current load. Eg, -for an original source file, it allows us to change the filepath to the original source regardless +for an original source file, it allows us to change the location to the original source regardless of what the sourcemap source entry says. And for transformed files, it allows us to change the -resolving location for nested sources files of the loaded sourcemap. +relative resolving location for child sources of the loaded sourcemap. ```js const remapped = remapping( @@ -165,8 +172,9 @@ console.log(remapped); #### `content` The `content` property can be overridden when we encounter an original source file. Eg, this allows -you to manually provide the source content of the file regardless of whether the `sourcesContent` -field is present in the parent sourcemap. Or, it can be set to `null` to remove the source content. +you to manually provide the source content of the original file regardless of whether the +`sourcesContent` field is present in the parent sourcemap. It can also be set to `null` to remove +the source content. ```js const remapped = remapping( @@ -199,13 +207,12 @@ console.log(remapped); #### excludeContent -By default, `excludeContent` is `false`. Passing `{ excludeContent: true }` -will exclude the `sourcesContent` field from the returned sourcemap. This is -mainly useful when you want to reduce the size out the sourcemap. +By default, `excludeContent` is `false`. Passing `{ excludeContent: true }` will exclude the +`sourcesContent` field from the returned sourcemap. This is mainly useful when you want to reduce +the size out the sourcemap. #### decodedMappings -By default, `decodedMappings` is `false`. Passing `{ decodedMappings: true }` -will leave the `mappings` field in a [decoded -state](https://github.com/rich-harris/sourcemap-codec) instead of encoding -into a VLQ string. +By default, `decodedMappings` is `false`. Passing `{ decodedMappings: true }` will leave the +`mappings` field in a [decoded state](https://github.com/rich-harris/sourcemap-codec) instead of +encoding into a VLQ string. diff --git a/src/build-source-map-tree.ts b/src/build-source-map-tree.ts index 58f635f..616e088 100644 --- a/src/build-source-map-tree.ts +++ b/src/build-source-map-tree.ts @@ -37,16 +37,22 @@ export default function buildSourceMapTree( } } - let tree = build(map, '', loader); + let tree = build(map, loader, '', 0); for (let i = maps.length - 1; i >= 0; i--) { tree = new SourceMapTree(maps[i], [tree]); } return tree; } -function build(map: TraceMap, importer: string, loader: SourceMapLoader): SourceMapTree { +function build( + map: TraceMap, + loader: SourceMapLoader, + importer: string, + importerDepth: number +): SourceMapTree { const { resolvedSources, sourcesContent } = map; + const depth = importerDepth + 1; const children = resolvedSources.map( (sourceFile: string | null, i: number): SourceMapTree | OriginalSource => { // 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 // an unmodified source file. const ctx: LoaderContext = { importer, + depth, source: sourceFile || '', content: undefined, }; @@ -77,7 +84,7 @@ function build(map: TraceMap, importer: string, loader: SourceMapLoader): Source // Else, it's a real sourcemap, and we need to recurse into it to load its // source files. - return build(new TraceMap(sourceMap, source), source, loader); + return build(new TraceMap(sourceMap, source), loader, source, depth); } ); diff --git a/src/types.ts b/src/types.ts index cc240ec..3ae1107 100644 --- a/src/types.ts +++ b/src/types.ts @@ -38,6 +38,7 @@ export type SourceMapInput = string | RawSourceMap | DecodedSourceMap; export type LoaderContext = { readonly importer: string; + readonly depth: number; source: string; content: string | null | undefined; }; diff --git a/test/unit/build-source-map-tree.ts b/test/unit/build-source-map-tree.ts index c7273e4..c3fe90f 100644 --- a/test/unit/build-source-map-tree.ts +++ b/test/unit/build-source-map-tree.ts @@ -199,6 +199,68 @@ describe('buildSourceMapTree', () => { }); }); + describe('depty', () => { + test('is 1 for sources loaded from the root', () => { + const loader = jest.fn(); + buildSourceMapTree( + { + ...decodedMap, + sources: ['first.js', 'second.js'], + }, + loader + ); + + expect(loader).toHaveBeenCalledTimes(2); + expect(loader).toHaveBeenCalledWith( + 'first.js', + expect.objectContaining({ + depth: 1, + }) + ); + expect(loader).toHaveBeenCalledWith( + 'second.js', + expect.objectContaining({ + depth: 1, + }) + ); + }); + + test('is increased for nested sources', () => { + const loader = jest.fn(); + loader.mockReturnValueOnce({ + ...rawMap, + sources: ['two.js'], + }); + buildSourceMapTree( + { + ...decodedMap, + sources: ['first.js', 'second.js'], + }, + loader + ); + + expect(loader).toHaveBeenCalledTimes(3); + expect(loader).toHaveBeenCalledWith( + 'first.js', + expect.objectContaining({ + depth: 1, + }) + ); + expect(loader).toHaveBeenCalledWith( + 'two.js', + expect.objectContaining({ + depth: 2, + }) + ); + expect(loader).toHaveBeenCalledWith( + 'second.js', + expect.objectContaining({ + depth: 1, + }) + ); + }); + }); + describe('source', () => { test('matches the loader source param', () => { const loader = jest.fn();