build-source-map-tree.ts 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { TraceMap } from '@jridgewell/trace-mapping';
  2. import { OriginalSource, MapSource } from './source-map-tree';
  3. import type { Sources, MapSource as MapSourceType } from './source-map-tree';
  4. import type { SourceMapInput, SourceMapLoader, LoaderContext } from './types';
  5. function asArray<T>(value: T | T[]): T[] {
  6. if (Array.isArray(value)) return value;
  7. return [value];
  8. }
  9. /**
  10. * Recursively builds a tree structure out of sourcemap files, with each node
  11. * being either an `OriginalSource` "leaf" or a `SourceMapTree` composed of
  12. * `OriginalSource`s and `SourceMapTree`s.
  13. *
  14. * Every sourcemap is composed of a collection of source files and mappings
  15. * into locations of those source files. When we generate a `SourceMapTree` for
  16. * the sourcemap, we attempt to load each source file's own sourcemap. If it
  17. * does not have an associated sourcemap, it is considered an original,
  18. * unmodified source file.
  19. */
  20. export default function buildSourceMapTree(
  21. input: SourceMapInput | SourceMapInput[],
  22. loader: SourceMapLoader,
  23. ): MapSourceType {
  24. const maps = asArray(input).map((m) => new TraceMap(m, ''));
  25. const map = maps.pop()!;
  26. for (let i = 0; i < maps.length; i++) {
  27. if (maps[i].sources.length > 1) {
  28. throw new Error(
  29. `Transformation map ${i} must have exactly one source file.\n` +
  30. 'Did you specify these with the most recent transformation maps first?',
  31. );
  32. }
  33. }
  34. let tree = build(map, loader, '', 0);
  35. for (let i = maps.length - 1; i >= 0; i--) {
  36. tree = MapSource(maps[i], [tree]);
  37. }
  38. return tree;
  39. }
  40. function build(
  41. map: TraceMap,
  42. loader: SourceMapLoader,
  43. importer: string,
  44. importerDepth: number,
  45. ): MapSourceType {
  46. const { resolvedSources, sourcesContent, ignoreList } = map;
  47. const depth = importerDepth + 1;
  48. const children = resolvedSources.map((sourceFile: string | null, i: number): Sources => {
  49. // The loading context gives the loader more information about why this file is being loaded
  50. // (eg, from which importer). It also allows the loader to override the location of the loaded
  51. // sourcemap/original source, or to override the content in the sourcesContent field if it's
  52. // an unmodified source file.
  53. const ctx: LoaderContext = {
  54. importer,
  55. depth,
  56. source: sourceFile || '',
  57. content: undefined,
  58. ignore: undefined,
  59. };
  60. // Use the provided loader callback to retrieve the file's sourcemap.
  61. // TODO: We should eventually support async loading of sourcemap files.
  62. const sourceMap = loader(ctx.source, ctx);
  63. const { source, content, ignore } = ctx;
  64. // If there is a sourcemap, then we need to recurse into it to load its source files.
  65. if (sourceMap) return build(new TraceMap(sourceMap, source), loader, source, depth);
  66. // Else, it's an unmodified source file.
  67. // The contents of this unmodified source file can be overridden via the loader context,
  68. // allowing it to be explicitly null or a string. If it remains undefined, we fall back to
  69. // the importing sourcemap's `sourcesContent` field.
  70. const sourceContent =
  71. content !== undefined ? content : sourcesContent ? sourcesContent[i] : null;
  72. const ignored = ignore !== undefined ? ignore : ignoreList ? ignoreList.includes(i) : false;
  73. return OriginalSource(source, sourceContent, ignored);
  74. });
  75. return MapSource(map, children);
  76. }