UNPKG

3.92 kBJavaScriptView Raw
1'use strict'
2
3let { SourceMapConsumer, SourceMapGenerator } = require('source-map-js')
4let { existsSync, readFileSync } = require('fs')
5let { dirname, join } = require('path')
6
7function fromBase64(str) {
8 if (Buffer) {
9 return Buffer.from(str, 'base64').toString()
10 } else {
11 /* c8 ignore next 2 */
12 return window.atob(str)
13 }
14}
15
16class PreviousMap {
17 constructor(css, opts) {
18 if (opts.map === false) return
19 this.loadAnnotation(css)
20 this.inline = this.startWith(this.annotation, 'data:')
21
22 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
29 }
30
31 consumer() {
32 if (!this.consumerCache) {
33 this.consumerCache = new SourceMapConsumer(this.text)
34 }
35 return this.consumerCache
36 }
37
38 withContent() {
39 return !!(
40 this.consumer().sourcesContent &&
41 this.consumer().sourcesContent.length > 0
42 )
43 }
44
45 startWith(string, start) {
46 if (!string) return false
47 return string.substr(0, start.length) === start
48 }
49
50 getAnnotationURL(sourceMapString) {
51 return sourceMapString.replace(/^\/\*\s*# sourceMappingURL=/, '').trim()
52 }
53
54 loadAnnotation(css) {
55 let comments = css.match(/\/\*\s*# sourceMappingURL=/gm)
56 if (!comments) return
57
58 // sourceMappingURLs from comments, strings, etc.
59 let start = css.lastIndexOf(comments.pop())
60 let end = css.indexOf('*/', start)
61
62 if (start > -1 && end > -1) {
63 // Locate the last sourceMappingURL to avoid pickin
64 this.annotation = this.getAnnotationURL(css.substring(start, end))
65 }
66 }
67
68 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,/
73
74 if (charsetUri.test(text) || uri.test(text)) {
75 return decodeURIComponent(text.substr(RegExp.lastMatch.length))
76 }
77
78 if (baseCharsetUri.test(text) || baseUri.test(text)) {
79 return fromBase64(text.substr(RegExp.lastMatch.length))
80 }
81
82 let encoding = text.match(/data:application\/json;([^,]+),/)[1]
83 throw new Error('Unsupported source map encoding ' + encoding)
84 }
85
86 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
96
97 if (prev) {
98 if (typeof prev === 'string') {
99 return prev
100 } else if (typeof prev === 'function') {
101 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
110 }
111 } else if (prev instanceof SourceMapConsumer) {
112 return SourceMapGenerator.fromSourceMap(prev).toString()
113 } else if (prev instanceof SourceMapGenerator) {
114 return prev.toString()
115 } else if (this.isMap(prev)) {
116 return JSON.stringify(prev)
117 } else {
118 throw new Error(
119 'Unsupported previous source map format: ' + prev.toString()
120 )
121 }
122 } else if (this.inline) {
123 return this.decodeInline(this.annotation)
124 } else if (this.annotation) {
125 let map = this.annotation
126 if (file) map = join(dirname(file), map)
127 return this.loadFile(map)
128 }
129 }
130
131 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}
140
141module.exports = PreviousMap
142PreviousMap.default = PreviousMap