You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

218 lines
7.1 KiB

3 months ago
  1. # @ampproject/remapping
  2. > Remap sequential sourcemaps through transformations to point at the original source code
  3. Remapping allows you to take the sourcemaps generated through transforming your code and "remap"
  4. them to the original source locations. Think "my minified code, transformed with babel and bundled
  5. with webpack", all pointing to the correct location in your original source code.
  6. With remapping, none of your source code transformations need to be aware of the input's sourcemap,
  7. they only need to generate an output sourcemap. This greatly simplifies building custom
  8. transformations (think a find-and-replace).
  9. ## Installation
  10. ```sh
  11. npm install @ampproject/remapping
  12. ```
  13. ## Usage
  14. ```typescript
  15. function remapping(
  16. map: SourceMap | SourceMap[],
  17. loader: (file: string, ctx: LoaderContext) => (SourceMap | null | undefined),
  18. options?: { excludeContent: boolean, decodedMappings: boolean }
  19. ): SourceMap;
  20. // LoaderContext gives the loader the importing sourcemap, tree depth, the ability to override the
  21. // "source" location (where child sources are resolved relative to, or the location of original
  22. // source), and the ability to override the "content" of an original source for inclusion in the
  23. // output sourcemap.
  24. type LoaderContext = {
  25. readonly importer: string;
  26. readonly depth: number;
  27. source: string;
  28. content: string | null | undefined;
  29. }
  30. ```
  31. `remapping` takes the final output sourcemap, and a `loader` function. For every source file pointer
  32. in the sourcemap, the `loader` will be called with the resolved path. If the path itself represents
  33. a transformed file (it has a sourcmap associated with it), then the `loader` should return that
  34. sourcemap. If not, the path will be treated as an original, untransformed source code.
  35. ```js
  36. // Babel transformed "helloworld.js" into "transformed.js"
  37. const transformedMap = JSON.stringify({
  38. file: 'transformed.js',
  39. // 1st column of 2nd line of output file translates into the 1st source
  40. // file, line 3, column 2
  41. mappings: ';CAEE',
  42. sources: ['helloworld.js'],
  43. version: 3,
  44. });
  45. // Uglify minified "transformed.js" into "transformed.min.js"
  46. const minifiedTransformedMap = JSON.stringify({
  47. file: 'transformed.min.js',
  48. // 0th column of 1st line of output file translates into the 1st source
  49. // file, line 2, column 1.
  50. mappings: 'AACC',
  51. names: [],
  52. sources: ['transformed.js'],
  53. version: 3,
  54. });
  55. const remapped = remapping(
  56. minifiedTransformedMap,
  57. (file, ctx) => {
  58. // The "transformed.js" file is an transformed file.
  59. if (file === 'transformed.js') {
  60. // The root importer is empty.
  61. console.assert(ctx.importer === '');
  62. // The depth in the sourcemap tree we're currently loading.
  63. // The root `minifiedTransformedMap` is depth 0, and its source children are depth 1, etc.
  64. console.assert(ctx.depth === 1);
  65. return transformedMap;
  66. }
  67. // Loader will be called to load transformedMap's source file pointers as well.
  68. console.assert(file === 'helloworld.js');
  69. // `transformed.js`'s sourcemap points into `helloworld.js`.
  70. console.assert(ctx.importer === 'transformed.js');
  71. // This is a source child of `transformed`, which is a source child of `minifiedTransformedMap`.
  72. console.assert(ctx.depth === 2);
  73. return null;
  74. }
  75. );
  76. console.log(remapped);
  77. // {
  78. // file: 'transpiled.min.js',
  79. // mappings: 'AAEE',
  80. // sources: ['helloworld.js'],
  81. // version: 3,
  82. // };
  83. ```
  84. In this example, `loader` will be called twice:
  85. 1. `"transformed.js"`, the first source file pointer in the `minifiedTransformedMap`. We return the
  86. associated sourcemap for it (its a transformed file, after all) so that sourcemap locations can
  87. be traced through it into the source files it represents.
  88. 2. `"helloworld.js"`, our original, unmodified source code. This file does not have a sourcemap, so
  89. we return `null`.
  90. The `remapped` sourcemap now points from `transformed.min.js` into locations in `helloworld.js`. If
  91. you were to read the `mappings`, it says "0th column of the first line output line points to the 1st
  92. column of the 2nd line of the file `helloworld.js`".
  93. ### Multiple transformations of a file
  94. As a convenience, if you have multiple single-source transformations of a file, you may pass an
  95. array of sourcemap files in the order of most-recent transformation sourcemap first. Note that this
  96. changes the `importer` and `depth` of each call to our loader. So our above example could have been
  97. written as:
  98. ```js
  99. const remapped = remapping(
  100. [minifiedTransformedMap, transformedMap],
  101. () => null
  102. );
  103. console.log(remapped);
  104. // {
  105. // file: 'transpiled.min.js',
  106. // mappings: 'AAEE',
  107. // sources: ['helloworld.js'],
  108. // version: 3,
  109. // };
  110. ```
  111. ### Advanced control of the loading graph
  112. #### `source`
  113. The `source` property can overridden to any value to change the location of the current load. Eg,
  114. for an original source file, it allows us to change the location to the original source regardless
  115. of what the sourcemap source entry says. And for transformed files, it allows us to change the
  116. relative resolving location for child sources of the loaded sourcemap.
  117. ```js
  118. const remapped = remapping(
  119. minifiedTransformedMap,
  120. (file, ctx) => {
  121. if (file === 'transformed.js') {
  122. // We pretend the transformed.js file actually exists in the 'src/' directory. When the nested
  123. // source files are loaded, they will now be relative to `src/`.
  124. ctx.source = 'src/transformed.js';
  125. return transformedMap;
  126. }
  127. console.assert(file === 'src/helloworld.js');
  128. // We could futher change the source of this original file, eg, to be inside a nested directory
  129. // itself. This will be reflected in the remapped sourcemap.
  130. ctx.source = 'src/nested/transformed.js';
  131. return null;
  132. }
  133. );
  134. console.log(remapped);
  135. // {
  136. // …,
  137. // sources: ['src/nested/helloworld.js'],
  138. // };
  139. ```
  140. #### `content`
  141. The `content` property can be overridden when we encounter an original source file. Eg, this allows
  142. you to manually provide the source content of the original file regardless of whether the
  143. `sourcesContent` field is present in the parent sourcemap. It can also be set to `null` to remove
  144. the source content.
  145. ```js
  146. const remapped = remapping(
  147. minifiedTransformedMap,
  148. (file, ctx) => {
  149. if (file === 'transformed.js') {
  150. // transformedMap does not include a `sourcesContent` field, so usually the remapped sourcemap
  151. // would not include any `sourcesContent` values.
  152. return transformedMap;
  153. }
  154. console.assert(file === 'helloworld.js');
  155. // We can read the file to provide the source content.
  156. ctx.content = fs.readFileSync(file, 'utf8');
  157. return null;
  158. }
  159. );
  160. console.log(remapped);
  161. // {
  162. // …,
  163. // sourcesContent: [
  164. // 'console.log("Hello world!")',
  165. // ],
  166. // };
  167. ```
  168. ### Options
  169. #### excludeContent
  170. By default, `excludeContent` is `false`. Passing `{ excludeContent: true }` will exclude the
  171. `sourcesContent` field from the returned sourcemap. This is mainly useful when you want to reduce
  172. the size out the sourcemap.
  173. #### decodedMappings
  174. By default, `decodedMappings` is `false`. Passing `{ decodedMappings: true }` will leave the
  175. `mappings` field in a [decoded state](https://github.com/rich-harris/sourcemap-codec) instead of
  176. encoding into a VLQ string.