| # @ampproject/remapping |
| |
| > 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. |
| |
| 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 |
| transformations (think a find-and-replace). |
| |
| ## Installation |
| |
| ```sh |
| npm install @ampproject/remapping |
| ``` |
| |
| ## Usage |
| |
| ```typescript |
| function remapping( |
| map: SourceMap | SourceMap[], |
| loader: (file: string, ctx: LoaderContext) => (SourceMap | null | undefined), |
| options?: { excludeContent: boolean, decodedMappings: boolean } |
| ): 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; |
| } |
| ``` |
| |
| `remapping` takes the final output sourcemap, and a `loader` function. For every source file pointer |
| in the sourcemap, the `loader` will be called with the resolved path. If the path itself represents |
| a transformed file (it has a sourcmap associated with it), then the `loader` should return that |
| sourcemap. If not, the path will be treated as an original, untransformed source code. |
| |
| ```js |
| // Babel transformed "helloworld.js" into "transformed.js" |
| const transformedMap = JSON.stringify({ |
| file: 'transformed.js', |
| // 1st column of 2nd line of output file translates into the 1st source |
| // file, line 3, column 2 |
| mappings: ';CAEE', |
| sources: ['helloworld.js'], |
| version: 3, |
| }); |
| |
| // Uglify minified "transformed.js" into "transformed.min.js" |
| const minifiedTransformedMap = JSON.stringify({ |
| file: 'transformed.min.js', |
| // 0th column of 1st line of output file translates into the 1st source |
| // file, line 2, column 1. |
| mappings: 'AACC', |
| names: [], |
| sources: ['transformed.js'], |
| version: 3, |
| }); |
| |
| const remapped = remapping( |
| minifiedTransformedMap, |
| (file, ctx) => { |
| |
| // The "transformed.js" file is an transformed file. |
| 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; |
| } |
| |
| // Loader will be called to load transformedMap's source file pointers as well. |
| 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; |
| } |
| ); |
| |
| console.log(remapped); |
| // { |
| // file: 'transpiled.min.js', |
| // mappings: 'AAEE', |
| // sources: ['helloworld.js'], |
| // version: 3, |
| // }; |
| ``` |
| |
| In this example, `loader` will be called twice: |
| |
| 1. `"transformed.js"`, the first source file pointer in the `minifiedTransformedMap`. We return the |
| associated sourcemap for it (its a transformed file, after all) so that sourcemap locations can |
| be traced through it into the source files it represents. |
| 2. `"helloworld.js"`, our original, unmodified source code. This file does not have a sourcemap, so |
| we return `null`. |
| |
| The `remapped` sourcemap now points from `transformed.min.js` into locations in `helloworld.js`. If |
| you were to read the `mappings`, it says "0th column of the first line output line points to the 1st |
| 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. 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( |
| [minifiedTransformedMap, transformedMap], |
| () => null |
| ); |
| |
| console.log(remapped); |
| // { |
| // file: 'transpiled.min.js', |
| // mappings: 'AAEE', |
| // sources: ['helloworld.js'], |
| // version: 3, |
| // }; |
| ``` |
| |
| ### Advanced control of the loading graph |
| |
| #### `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 location to the original source regardless |
| of what the sourcemap source entry says. And for transformed files, it allows us to change the |
| relative resolving location for child sources of the loaded sourcemap. |
| |
| ```js |
| const remapped = remapping( |
| minifiedTransformedMap, |
| (file, ctx) => { |
| |
| if (file === 'transformed.js') { |
| // We pretend the transformed.js file actually exists in the 'src/' directory. When the nested |
| // source files are loaded, they will now be relative to `src/`. |
| ctx.source = 'src/transformed.js'; |
| return transformedMap; |
| } |
| |
| console.assert(file === 'src/helloworld.js'); |
| // We could futher change the source of this original file, eg, to be inside a nested directory |
| // itself. This will be reflected in the remapped sourcemap. |
| ctx.source = 'src/nested/transformed.js'; |
| return null; |
| } |
| ); |
| |
| console.log(remapped); |
| // { |
| // …, |
| // sources: ['src/nested/helloworld.js'], |
| // }; |
| ``` |
| |
| |
| #### `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 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( |
| minifiedTransformedMap, |
| (file, ctx) => { |
| |
| if (file === 'transformed.js') { |
| // transformedMap does not include a `sourcesContent` field, so usually the remapped sourcemap |
| // would not include any `sourcesContent` values. |
| return transformedMap; |
| } |
| |
| console.assert(file === 'helloworld.js'); |
| // We can read the file to provide the source content. |
| ctx.content = fs.readFileSync(file, 'utf8'); |
| return null; |
| } |
| ); |
| |
| console.log(remapped); |
| // { |
| // …, |
| // sourcesContent: [ |
| // 'console.log("Hello world!")', |
| // ], |
| // }; |
| ``` |
| |
| ### Options |
| |
| #### 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. |
| |
| #### 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. |