source-maps.js 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { SourceMapConsumer } from 'source-map-js'
  2. /**
  3. * Parse the source maps from a PostCSS result
  4. *
  5. * @param {import('postcss').Result} result
  6. */
  7. export function parseSourceMaps(result) {
  8. let map = result.map
  9. ? result.map.toJSON()
  10. : (() => {
  11. let css = result.toString()
  12. let sourceMappingURL = css.match(/\/\*# sourceMappingURL=(.*) \*\//)?.[1]
  13. let raw = sourceMappingURL
  14. ? Buffer.from(sourceMappingURL.split(',')[1], 'base64').toString()
  15. : null
  16. return JSON.parse(raw ?? '{}')
  17. })()
  18. return {
  19. sources: map.sources,
  20. annotations: annotatedMappings(map),
  21. }
  22. }
  23. /**
  24. * A string annotation that represents a source map
  25. *
  26. * It's not meant to be exhaustive just enough to
  27. * verify that the source map is working and that
  28. * lines are mapped back to the original source
  29. *
  30. * Including when using @apply with multiple classes
  31. *
  32. * @param {import('source-map-js').RawSourceMap} map
  33. */
  34. function annotatedMappings(map) {
  35. const smc = new SourceMapConsumer(map)
  36. const annotations = {}
  37. smc.eachMapping((mapping) => {
  38. let annotation = (annotations[mapping.generatedLine] = annotations[mapping.generatedLine] || {
  39. ...mapping,
  40. original: {
  41. start: [mapping.originalLine, mapping.originalColumn],
  42. end: [mapping.originalLine, mapping.originalColumn],
  43. },
  44. generated: {
  45. start: [mapping.generatedLine, mapping.generatedColumn],
  46. end: [mapping.generatedLine, mapping.generatedColumn],
  47. },
  48. })
  49. annotation.generated.end[0] = mapping.generatedLine
  50. annotation.generated.end[1] = mapping.generatedColumn
  51. annotation.original.end[0] = mapping.originalLine
  52. annotation.original.end[1] = mapping.originalColumn
  53. })
  54. return Object.values(annotations).map((annotation) => {
  55. return `${formatRange(annotation.original)} -> ${formatRange(annotation.generated)}`
  56. })
  57. }
  58. /**
  59. * @param {object} range
  60. * @param {[number, number]} range.start
  61. * @param {[number, number]} range.end
  62. */
  63. function formatRange(range) {
  64. if (range.start[0] === range.end[0]) {
  65. // This range is on the same line
  66. // and the columns are the same
  67. if (range.start[1] === range.end[1]) {
  68. return `${range.start[0]}:${range.start[1]}`
  69. }
  70. // This range is on the same line
  71. // but the columns are different
  72. return `${range.start[0]}:${range.start[1]}-${range.end[1]}`
  73. }
  74. // This range spans multiple lines
  75. return `${range.start[0]}:${range.start[1]}-${range.end[0]}:${range.end[1]}`
  76. }