blob: bcc378d5b771b8a67cbce73d59cfb30486c08354 [file] [log] [blame]
Tim van der Lippe16b82282021-11-08 13:50:26 +00001'use strict'
Mathias Bynens79e2cf02020-05-29 16:46:17 +02002
Tim van der Lippe16b82282021-11-08 13:50:26 +00003let { SourceMapConsumer, SourceMapGenerator } = require('source-map-js')
4let { existsSync, readFileSync } = require('fs')
5let { dirname, join } = require('path')
Mathias Bynens79e2cf02020-05-29 16:46:17 +02006
7function fromBase64(str) {
8 if (Buffer) {
Tim van der Lippe16b82282021-11-08 13:50:26 +00009 return Buffer.from(str, 'base64').toString()
Mathias Bynens79e2cf02020-05-29 16:46:17 +020010 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +000011 // istanbul ignore next
12 return window.atob(str)
Mathias Bynens79e2cf02020-05-29 16:46:17 +020013 }
14}
Mathias Bynens79e2cf02020-05-29 16:46:17 +020015
Tim van der Lippe16b82282021-11-08 13:50:26 +000016class PreviousMap {
17 constructor(css, opts) {
18 if (opts.map === false) return
19 this.loadAnnotation(css)
20 this.inline = this.startWith(this.annotation, 'data:')
Mathias Bynens79e2cf02020-05-29 16:46:17 +020021
Tim van der Lippe16b82282021-11-08 13:50:26 +000022 let prev = opts.map ? opts.map.prev : undefined
23 let text = this.loadMap(opts.from, prev)
24 if (!this.mapFile && opts.from) {
25 this.mapFile = opts.from
26 }
27 if (this.mapFile) this.root = dirname(this.mapFile)
28 if (text) this.text = text
Mathias Bynens79e2cf02020-05-29 16:46:17 +020029 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020030
Tim van der Lippe16b82282021-11-08 13:50:26 +000031 consumer() {
Mathias Bynens79e2cf02020-05-29 16:46:17 +020032 if (!this.consumerCache) {
Tim van der Lippe16b82282021-11-08 13:50:26 +000033 this.consumerCache = new SourceMapConsumer(this.text)
Mathias Bynens79e2cf02020-05-29 16:46:17 +020034 }
Tim van der Lippe16b82282021-11-08 13:50:26 +000035 return this.consumerCache
Mathias Bynens79e2cf02020-05-29 16:46:17 +020036 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020037
Tim van der Lippe16b82282021-11-08 13:50:26 +000038 withContent() {
39 return !!(
40 this.consumer().sourcesContent &&
41 this.consumer().sourcesContent.length > 0
42 )
43 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020044
Tim van der Lippe16b82282021-11-08 13:50:26 +000045 startWith(string, start) {
46 if (!string) return false
47 return string.substr(0, start.length) === start
48 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020049
Tim van der Lippe16b82282021-11-08 13:50:26 +000050 getAnnotationURL(sourceMapString) {
51 return sourceMapString.replace(/^\/\*\s*# sourceMappingURL=/, '').trim()
52 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020053
Tim van der Lippe16b82282021-11-08 13:50:26 +000054 loadAnnotation(css) {
55 let comments = css.match(/\/\*\s*# sourceMappingURL=/gm)
56 if (!comments) return
Mathias Bynens79e2cf02020-05-29 16:46:17 +020057
Tim van der Lippe16b82282021-11-08 13:50:26 +000058 // sourceMappingURLs from comments, strings, etc.
59 let start = css.lastIndexOf(comments.pop())
60 let end = css.indexOf('*/', start)
Mathias Bynens79e2cf02020-05-29 16:46:17 +020061
Tim van der Lippe16b82282021-11-08 13:50:26 +000062 if (start > -1 && end > -1) {
63 // Locate the last sourceMappingURL to avoid pickin
64 this.annotation = this.getAnnotationURL(css.substring(start, end))
Mathias Bynens79e2cf02020-05-29 16:46:17 +020065 }
Tim van der Lippe16b82282021-11-08 13:50:26 +000066 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020067
Tim van der Lippe16b82282021-11-08 13:50:26 +000068 decodeInline(text) {
69 let baseCharsetUri = /^data:application\/json;charset=utf-?8;base64,/
70 let baseUri = /^data:application\/json;base64,/
71 let charsetUri = /^data:application\/json;charset=utf-?8,/
72 let uri = /^data:application\/json,/
Mathias Bynens79e2cf02020-05-29 16:46:17 +020073
Tim van der Lippe16b82282021-11-08 13:50:26 +000074 if (charsetUri.test(text) || uri.test(text)) {
75 return decodeURIComponent(text.substr(RegExp.lastMatch.length))
Mathias Bynens79e2cf02020-05-29 16:46:17 +020076 }
77
78 if (baseCharsetUri.test(text) || baseUri.test(text)) {
Tim van der Lippe16b82282021-11-08 13:50:26 +000079 return fromBase64(text.substr(RegExp.lastMatch.length))
Mathias Bynens79e2cf02020-05-29 16:46:17 +020080 }
81
Tim van der Lippe16b82282021-11-08 13:50:26 +000082 let encoding = text.match(/data:application\/json;([^,]+),/)[1]
83 throw new Error('Unsupported source map encoding ' + encoding)
84 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020085
Tim van der Lippe16b82282021-11-08 13:50:26 +000086 loadFile(path) {
87 this.root = dirname(path)
88 if (existsSync(path)) {
89 this.mapFile = path
90 return readFileSync(path, 'utf-8').toString().trim()
91 }
92 }
93
94 loadMap(file, prev) {
95 if (prev === false) return false
Mathias Bynens79e2cf02020-05-29 16:46:17 +020096
97 if (prev) {
98 if (typeof prev === 'string') {
Tim van der Lippe16b82282021-11-08 13:50:26 +000099 return prev
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200100 } else if (typeof prev === 'function') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000101 let prevPath = prev(file)
102 if (prevPath) {
103 let map = this.loadFile(prevPath)
104 if (!map) {
105 throw new Error(
106 'Unable to load previous source map: ' + prevPath.toString()
107 )
108 }
109 return map
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200110 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000111 } else if (prev instanceof SourceMapConsumer) {
112 return SourceMapGenerator.fromSourceMap(prev).toString()
113 } else if (prev instanceof SourceMapGenerator) {
114 return prev.toString()
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200115 } else if (this.isMap(prev)) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000116 return JSON.stringify(prev)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200117 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000118 throw new Error(
119 'Unsupported previous source map format: ' + prev.toString()
120 )
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200121 }
122 } else if (this.inline) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000123 return this.decodeInline(this.annotation)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200124 } else if (this.annotation) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000125 let map = this.annotation
126 if (file) map = join(dirname(file), map)
127 return this.loadFile(map)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200128 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000129 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200130
Tim van der Lippe16b82282021-11-08 13:50:26 +0000131 isMap(map) {
132 if (typeof map !== 'object') return false
133 return (
134 typeof map.mappings === 'string' ||
135 typeof map._mappings === 'string' ||
136 Array.isArray(map.sections)
137 )
138 }
139}
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200140
Tim van der Lippe16b82282021-11-08 13:50:26 +0000141module.exports = PreviousMap
142PreviousMap.default = PreviousMap