Spaces:
Running
Running
# @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. | |