createMappings.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. */
  4. "use strict";
  5. /**
  6. * Utilities for building V3 source-map `mappings` strings without pulling in a
  7. * full source-map library. The shape of the input is intentionally minimal —
  8. * one slot per generated line, each holding zero, one, or many segments — so
  9. * call sites that have a "one mapping per line" structure (like the CSS-module
  10. * exports emit in `lib/css/CssGenerator.js`) can build mappings directly,
  11. * while richer call sites can pass arrays of segments.
  12. *
  13. * TODO move this encoder into `webpack-sources` and replace the body of this
  14. * file with re-exports. The public shape (`encodeVLQ`, `encodeMappings(lines)`,
  15. * `MappingSegment`, `LineMappings`) is intended to match what would land
  16. * upstream so call sites don't have to change.
  17. */
  18. const VLQ_BASE64 =
  19. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  20. /**
  21. * Encode a signed integer as a base64 VLQ string per the source-map V3 spec.
  22. * @param {number} value signed integer to encode
  23. * @returns {string} base64 VLQ encoded value
  24. */
  25. const encodeVLQ = (value) => {
  26. let vlq = value < 0 ? (-value << 1) | 1 : value << 1;
  27. let result = "";
  28. do {
  29. let digit = vlq & 0x1f;
  30. vlq >>>= 5;
  31. if (vlq > 0) digit |= 0x20;
  32. result += VLQ_BASE64[digit];
  33. } while (vlq > 0);
  34. return result;
  35. };
  36. /**
  37. * @typedef {object} MappingSegment
  38. * @property {number=} generatedColumn 0-based generated column (defaults to 0)
  39. * @property {number=} sourceIndex index into the surrounding source map's `sources` array; omit for a generated-only segment
  40. * @property {number=} originalLine 0-based line in the original source (required when `sourceIndex` is set)
  41. * @property {number=} originalColumn 0-based column in the original source (required when `sourceIndex` is set)
  42. * @property {number=} nameIndex index into the surrounding source map's `names` array
  43. */
  44. /** @typedef {null | MappingSegment | MappingSegment[]} LineMappings */
  45. /**
  46. * Encode a V3 source-map `mappings` string from a per-generated-line
  47. * description of segments.
  48. *
  49. * Each entry of `lines` describes the mappings for one generated line:
  50. *
  51. * - `null` (or `undefined`) — the line has no mappings.
  52. * - a single `MappingSegment` — convenience for the common "one mapping at
  53. * column 0" case.
  54. * - `MappingSegment[]` — multiple segments on the same line.
  55. *
  56. * Lines are joined with `;`, segments within a line with `,`. All numeric
  57. * fields are encoded as deltas relative to the previous emitted segment, per
  58. * the V3 spec.
  59. * @param {LineMappings[]} lines per-generated-line mapping segments
  60. * @returns {string} VLQ-encoded V3 mappings string
  61. */
  62. const encodeMappings = (lines) => {
  63. let prevSourceIndex = 0;
  64. let prevOriginalLine = 0;
  65. let prevOriginalColumn = 0;
  66. let prevNameIndex = 0;
  67. const encodedLines = [];
  68. for (const line of lines) {
  69. if (line === null || line === undefined) {
  70. encodedLines.push("");
  71. continue;
  72. }
  73. const segments = Array.isArray(line) ? line : [line];
  74. let prevGeneratedColumn = 0;
  75. const encodedSegments = [];
  76. for (const segment of segments) {
  77. const generatedColumn = segment.generatedColumn || 0;
  78. let encoded = encodeVLQ(generatedColumn - prevGeneratedColumn);
  79. prevGeneratedColumn = generatedColumn;
  80. if (segment.sourceIndex !== undefined) {
  81. const originalLine = /** @type {number} */ (segment.originalLine);
  82. const originalColumn = /** @type {number} */ (segment.originalColumn);
  83. encoded += encodeVLQ(segment.sourceIndex - prevSourceIndex);
  84. encoded += encodeVLQ(originalLine - prevOriginalLine);
  85. encoded += encodeVLQ(originalColumn - prevOriginalColumn);
  86. prevSourceIndex = segment.sourceIndex;
  87. prevOriginalLine = originalLine;
  88. prevOriginalColumn = originalColumn;
  89. if (segment.nameIndex !== undefined) {
  90. encoded += encodeVLQ(segment.nameIndex - prevNameIndex);
  91. prevNameIndex = segment.nameIndex;
  92. }
  93. }
  94. encodedSegments.push(encoded);
  95. }
  96. encodedLines.push(encodedSegments.join(","));
  97. }
  98. return encodedLines.join(";");
  99. };
  100. module.exports.encodeMappings = encodeMappings;
  101. module.exports.encodeVLQ = encodeVLQ;