source: frontend/node_modules/postcss/lib/map-generator.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 9.8 KB
Line 
1'use strict'
2
3let { dirname, relative, resolve, sep } = require('path')
4let { SourceMapConsumer, SourceMapGenerator } = require('source-map-js')
5let { pathToFileURL } = require('url')
6
7let Input = require('./input')
8
9let sourceMapAvailable = Boolean(SourceMapConsumer && SourceMapGenerator)
10let pathAvailable = Boolean(dirname && resolve && relative && sep)
11
12class MapGenerator {
13 constructor(stringify, root, opts, cssString) {
14 this.stringify = stringify
15 this.mapOpts = opts.map || {}
16 this.root = root
17 this.opts = opts
18 this.css = cssString
19 this.originalCSS = cssString
20 this.usesFileUrls = !this.mapOpts.from && this.mapOpts.absolute
21
22 this.memoizedFileURLs = new Map()
23 this.memoizedPaths = new Map()
24 this.memoizedURLs = new Map()
25 }
26
27 addAnnotation() {
28 let content
29
30 if (this.isInline()) {
31 content =
32 'data:application/json;base64,' + this.toBase64(this.map.toString())
33 } else if (typeof this.mapOpts.annotation === 'string') {
34 content = this.mapOpts.annotation
35 } else if (typeof this.mapOpts.annotation === 'function') {
36 content = this.mapOpts.annotation(this.opts.to, this.root)
37 } else {
38 content = this.outputFile() + '.map'
39 }
40 let eol = '\n'
41 if (this.css.includes('\r\n')) eol = '\r\n'
42
43 this.css += eol + '/*# sourceMappingURL=' + content + ' */'
44 }
45
46 applyPrevMaps() {
47 for (let prev of this.previous()) {
48 let from = this.toUrl(this.path(prev.file))
49 let root = prev.root || dirname(prev.file)
50 let map
51
52 if (this.mapOpts.sourcesContent === false) {
53 map = new SourceMapConsumer(prev.text)
54 if (map.sourcesContent) {
55 map.sourcesContent = null
56 }
57 } else {
58 map = prev.consumer()
59 }
60
61 this.map.applySourceMap(map, from, this.toUrl(this.path(root)))
62 }
63 }
64
65 clearAnnotation() {
66 if (this.mapOpts.annotation === false) return
67
68 if (this.root) {
69 let node
70 for (let i = this.root.nodes.length - 1; i >= 0; i--) {
71 node = this.root.nodes[i]
72 if (node.type !== 'comment') continue
73 if (node.text.startsWith('# sourceMappingURL=')) {
74 this.root.removeChild(i)
75 }
76 }
77 } else if (this.css) {
78 let startIndex
79 while ((startIndex = this.css.lastIndexOf('/*#')) !== -1) {
80 let endIndex = this.css.indexOf('*/', startIndex + 3)
81 if (endIndex === -1) break
82 while (startIndex > 0 && this.css[startIndex - 1] === '\n') {
83 startIndex--
84 }
85 this.css = this.css.slice(0, startIndex) + this.css.slice(endIndex + 2)
86 }
87 }
88 }
89
90 generate() {
91 this.clearAnnotation()
92 if (pathAvailable && sourceMapAvailable && this.isMap()) {
93 return this.generateMap()
94 } else {
95 let result = ''
96 this.stringify(this.root, i => {
97 result += i
98 })
99 return [result]
100 }
101 }
102
103 generateMap() {
104 if (this.root) {
105 this.generateString()
106 } else if (this.previous().length === 1) {
107 let prev = this.previous()[0].consumer()
108 prev.file = this.outputFile()
109 this.map = SourceMapGenerator.fromSourceMap(prev, {
110 ignoreInvalidMapping: true
111 })
112 } else {
113 this.map = new SourceMapGenerator({
114 file: this.outputFile(),
115 ignoreInvalidMapping: true
116 })
117 this.map.addMapping({
118 generated: { column: 0, line: 1 },
119 original: { column: 0, line: 1 },
120 source: this.opts.from
121 ? this.toUrl(this.path(this.opts.from))
122 : '<no source>'
123 })
124 }
125
126 if (this.isSourcesContent()) this.setSourcesContent()
127 if (this.root && this.previous().length > 0) this.applyPrevMaps()
128 if (this.isAnnotation()) this.addAnnotation()
129
130 if (this.isInline()) {
131 return [this.css]
132 } else {
133 return [this.css, this.map]
134 }
135 }
136
137 generateString() {
138 this.css = ''
139 this.map = new SourceMapGenerator({
140 file: this.outputFile(),
141 ignoreInvalidMapping: true
142 })
143
144 let line = 1
145 let column = 1
146
147 let noSource = '<no source>'
148 let mapping = {
149 generated: { column: 0, line: 0 },
150 original: { column: 0, line: 0 },
151 source: ''
152 }
153
154 let last, lines
155 this.stringify(this.root, (str, node, type) => {
156 this.css += str
157
158 if (node && type !== 'end') {
159 mapping.generated.line = line
160 mapping.generated.column = column - 1
161 if (node.source && node.source.start) {
162 mapping.source = this.sourcePath(node)
163 mapping.original.line = node.source.start.line
164 mapping.original.column = node.source.start.column - 1
165 this.map.addMapping(mapping)
166 } else {
167 mapping.source = noSource
168 mapping.original.line = 1
169 mapping.original.column = 0
170 this.map.addMapping(mapping)
171 }
172 }
173
174 lines = str.match(/\n/g)
175 if (lines) {
176 line += lines.length
177 last = str.lastIndexOf('\n')
178 column = str.length - last
179 } else {
180 column += str.length
181 }
182
183 if (node && type !== 'start') {
184 let p = node.parent || { raws: {} }
185 let childless =
186 node.type === 'decl' || (node.type === 'atrule' && !node.nodes)
187 if (!childless || node !== p.last || p.raws.semicolon) {
188 if (node.source && node.source.end) {
189 mapping.source = this.sourcePath(node)
190 mapping.original.line = node.source.end.line
191 mapping.original.column = node.source.end.column - 1
192 mapping.generated.line = line
193 mapping.generated.column = column - 2
194 this.map.addMapping(mapping)
195 } else {
196 mapping.source = noSource
197 mapping.original.line = 1
198 mapping.original.column = 0
199 mapping.generated.line = line
200 mapping.generated.column = column - 1
201 this.map.addMapping(mapping)
202 }
203 }
204 }
205 })
206 }
207
208 isAnnotation() {
209 if (this.isInline()) {
210 return true
211 }
212 if (typeof this.mapOpts.annotation !== 'undefined') {
213 return this.mapOpts.annotation
214 }
215 if (this.previous().length) {
216 return this.previous().some(i => i.annotation)
217 }
218 return true
219 }
220
221 isInline() {
222 if (typeof this.mapOpts.inline !== 'undefined') {
223 return this.mapOpts.inline
224 }
225
226 let annotation = this.mapOpts.annotation
227 if (typeof annotation !== 'undefined' && annotation !== true) {
228 return false
229 }
230
231 if (this.previous().length) {
232 return this.previous().some(i => i.inline)
233 }
234 return true
235 }
236
237 isMap() {
238 if (typeof this.opts.map !== 'undefined') {
239 return !!this.opts.map
240 }
241 return this.previous().length > 0
242 }
243
244 isSourcesContent() {
245 if (typeof this.mapOpts.sourcesContent !== 'undefined') {
246 return this.mapOpts.sourcesContent
247 }
248 if (this.previous().length) {
249 return this.previous().some(i => i.withContent())
250 }
251 return true
252 }
253
254 outputFile() {
255 if (this.opts.to) {
256 return this.path(this.opts.to)
257 } else if (this.opts.from) {
258 return this.path(this.opts.from)
259 } else {
260 return 'to.css'
261 }
262 }
263
264 path(file) {
265 if (this.mapOpts.absolute) return file
266 if (file.charCodeAt(0) === 60 /* `<` */) return file
267 if (/^\w+:\/\//.test(file)) return file
268 let cached = this.memoizedPaths.get(file)
269 if (cached) return cached
270
271 let from = this.opts.to ? dirname(this.opts.to) : '.'
272
273 if (typeof this.mapOpts.annotation === 'string') {
274 from = dirname(resolve(from, this.mapOpts.annotation))
275 }
276
277 let path = relative(from, file)
278 this.memoizedPaths.set(file, path)
279
280 return path
281 }
282
283 previous() {
284 if (!this.previousMaps) {
285 this.previousMaps = []
286 if (this.root) {
287 this.root.walk(node => {
288 if (node.source && node.source.input.map) {
289 let map = node.source.input.map
290 if (!this.previousMaps.includes(map)) {
291 this.previousMaps.push(map)
292 }
293 }
294 })
295 } else {
296 let input = new Input(this.originalCSS, this.opts)
297 if (input.map) this.previousMaps.push(input.map)
298 }
299 }
300
301 return this.previousMaps
302 }
303
304 setSourcesContent() {
305 let already = {}
306 if (this.root) {
307 this.root.walk(node => {
308 if (node.source) {
309 let from = node.source.input.from
310 if (from && !already[from]) {
311 already[from] = true
312 let fromUrl = this.usesFileUrls
313 ? this.toFileUrl(from)
314 : this.toUrl(this.path(from))
315 this.map.setSourceContent(fromUrl, node.source.input.css)
316 }
317 }
318 })
319 } else if (this.css) {
320 let from = this.opts.from
321 ? this.toUrl(this.path(this.opts.from))
322 : '<no source>'
323 this.map.setSourceContent(from, this.css)
324 }
325 }
326
327 sourcePath(node) {
328 if (this.mapOpts.from) {
329 return this.toUrl(this.mapOpts.from)
330 } else if (this.usesFileUrls) {
331 return this.toFileUrl(node.source.input.from)
332 } else {
333 return this.toUrl(this.path(node.source.input.from))
334 }
335 }
336
337 toBase64(str) {
338 if (Buffer) {
339 return Buffer.from(str).toString('base64')
340 } else {
341 return window.btoa(unescape(encodeURIComponent(str)))
342 }
343 }
344
345 toFileUrl(path) {
346 let cached = this.memoizedFileURLs.get(path)
347 if (cached) return cached
348
349 if (pathToFileURL) {
350 let fileURL = pathToFileURL(path).toString()
351 this.memoizedFileURLs.set(path, fileURL)
352
353 return fileURL
354 } else {
355 throw new Error(
356 '`map.absolute` option is not available in this PostCSS build'
357 )
358 }
359 }
360
361 toUrl(path) {
362 let cached = this.memoizedURLs.get(path)
363 if (cached) return cached
364
365 if (sep === '\\') {
366 path = path.replace(/\\/g, '/')
367 }
368
369 let url = encodeURI(path).replace(/[#?]/g, encodeURIComponent)
370 this.memoizedURLs.set(path, url)
371
372 return url
373 }
374}
375
376module.exports = MapGenerator
Note: See TracBrowser for help on using the repository browser.