123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- 'use strict'
-
- let { SourceMapConsumer, SourceMapGenerator } = require('source-map-js')
- let { dirname, relative, resolve, sep } = require('path')
- let { pathToFileURL } = require('url')
-
- let Input = require('./input')
-
- let sourceMapAvailable = Boolean(SourceMapConsumer && SourceMapGenerator)
- let pathAvailable = Boolean(dirname && resolve && relative && sep)
-
- class MapGenerator {
- constructor(stringify, root, opts, cssString) {
- this.stringify = stringify
- this.mapOpts = opts.map || {}
- this.root = root
- this.opts = opts
- this.css = cssString
- this.originalCSS = cssString
- this.usesFileUrls = !this.mapOpts.from && this.mapOpts.absolute
-
- this.memoizedFileURLs = new Map()
- this.memoizedPaths = new Map()
- this.memoizedURLs = new Map()
- }
-
- addAnnotation() {
- let content
-
- if (this.isInline()) {
- content =
- 'data:application/json;base64,' + this.toBase64(this.map.toString())
- } else if (typeof this.mapOpts.annotation === 'string') {
- content = this.mapOpts.annotation
- } else if (typeof this.mapOpts.annotation === 'function') {
- content = this.mapOpts.annotation(this.opts.to, this.root)
- } else {
- content = this.outputFile() + '.map'
- }
- let eol = '\n'
- if (this.css.includes('\r\n')) eol = '\r\n'
-
- this.css += eol + '/*# sourceMappingURL=' + content + ' */'
- }
-
- applyPrevMaps() {
- for (let prev of this.previous()) {
- let from = this.toUrl(this.path(prev.file))
- let root = prev.root || dirname(prev.file)
- let map
-
- if (this.mapOpts.sourcesContent === false) {
- map = new SourceMapConsumer(prev.text)
- if (map.sourcesContent) {
- map.sourcesContent = null
- }
- } else {
- map = prev.consumer()
- }
-
- this.map.applySourceMap(map, from, this.toUrl(this.path(root)))
- }
- }
-
- clearAnnotation() {
- if (this.mapOpts.annotation === false) return
-
- if (this.root) {
- let node
- for (let i = this.root.nodes.length - 1; i >= 0; i--) {
- node = this.root.nodes[i]
- if (node.type !== 'comment') continue
- if (node.text.indexOf('# sourceMappingURL=') === 0) {
- this.root.removeChild(i)
- }
- }
- } else if (this.css) {
- this.css = this.css.replace(/\n*\/\*#[\S\s]*?\*\/$/gm, '')
- }
- }
-
- generate() {
- this.clearAnnotation()
- if (pathAvailable && sourceMapAvailable && this.isMap()) {
- return this.generateMap()
- } else {
- let result = ''
- this.stringify(this.root, i => {
- result += i
- })
- return [result]
- }
- }
-
- generateMap() {
- if (this.root) {
- this.generateString()
- } else if (this.previous().length === 1) {
- let prev = this.previous()[0].consumer()
- prev.file = this.outputFile()
- this.map = SourceMapGenerator.fromSourceMap(prev, {
- ignoreInvalidMapping: true
- })
- } else {
- this.map = new SourceMapGenerator({
- file: this.outputFile(),
- ignoreInvalidMapping: true
- })
- this.map.addMapping({
- generated: { column: 0, line: 1 },
- original: { column: 0, line: 1 },
- source: this.opts.from
- ? this.toUrl(this.path(this.opts.from))
- : '<no source>'
- })
- }
-
- if (this.isSourcesContent()) this.setSourcesContent()
- if (this.root && this.previous().length > 0) this.applyPrevMaps()
- if (this.isAnnotation()) this.addAnnotation()
-
- if (this.isInline()) {
- return [this.css]
- } else {
- return [this.css, this.map]
- }
- }
-
- generateString() {
- this.css = ''
- this.map = new SourceMapGenerator({
- file: this.outputFile(),
- ignoreInvalidMapping: true
- })
-
- let line = 1
- let column = 1
-
- let noSource = '<no source>'
- let mapping = {
- generated: { column: 0, line: 0 },
- original: { column: 0, line: 0 },
- source: ''
- }
-
- let lines, last
- this.stringify(this.root, (str, node, type) => {
- this.css += str
-
- if (node && type !== 'end') {
- mapping.generated.line = line
- mapping.generated.column = column - 1
- if (node.source && node.source.start) {
- mapping.source = this.sourcePath(node)
- mapping.original.line = node.source.start.line
- mapping.original.column = node.source.start.column - 1
- this.map.addMapping(mapping)
- } else {
- mapping.source = noSource
- mapping.original.line = 1
- mapping.original.column = 0
- this.map.addMapping(mapping)
- }
- }
-
- lines = str.match(/\n/g)
- if (lines) {
- line += lines.length
- last = str.lastIndexOf('\n')
- column = str.length - last
- } else {
- column += str.length
- }
-
- if (node && type !== 'start') {
- let p = node.parent || { raws: {} }
- let childless =
- node.type === 'decl' || (node.type === 'atrule' && !node.nodes)
- if (!childless || node !== p.last || p.raws.semicolon) {
- if (node.source && node.source.end) {
- mapping.source = this.sourcePath(node)
- mapping.original.line = node.source.end.line
- mapping.original.column = node.source.end.column - 1
- mapping.generated.line = line
- mapping.generated.column = column - 2
- this.map.addMapping(mapping)
- } else {
- mapping.source = noSource
- mapping.original.line = 1
- mapping.original.column = 0
- mapping.generated.line = line
- mapping.generated.column = column - 1
- this.map.addMapping(mapping)
- }
- }
- }
- })
- }
-
- isAnnotation() {
- if (this.isInline()) {
- return true
- }
- if (typeof this.mapOpts.annotation !== 'undefined') {
- return this.mapOpts.annotation
- }
- if (this.previous().length) {
- return this.previous().some(i => i.annotation)
- }
- return true
- }
-
- isInline() {
- if (typeof this.mapOpts.inline !== 'undefined') {
- return this.mapOpts.inline
- }
-
- let annotation = this.mapOpts.annotation
- if (typeof annotation !== 'undefined' && annotation !== true) {
- return false
- }
-
- if (this.previous().length) {
- return this.previous().some(i => i.inline)
- }
- return true
- }
-
- isMap() {
- if (typeof this.opts.map !== 'undefined') {
- return !!this.opts.map
- }
- return this.previous().length > 0
- }
-
- isSourcesContent() {
- if (typeof this.mapOpts.sourcesContent !== 'undefined') {
- return this.mapOpts.sourcesContent
- }
- if (this.previous().length) {
- return this.previous().some(i => i.withContent())
- }
- return true
- }
-
- outputFile() {
- if (this.opts.to) {
- return this.path(this.opts.to)
- } else if (this.opts.from) {
- return this.path(this.opts.from)
- } else {
- return 'to.css'
- }
- }
-
- path(file) {
- if (this.mapOpts.absolute) return file
- if (file.charCodeAt(0) === 60 /* `<` */) return file
- if (/^\w+:\/\//.test(file)) return file
- let cached = this.memoizedPaths.get(file)
- if (cached) return cached
-
- let from = this.opts.to ? dirname(this.opts.to) : '.'
-
- if (typeof this.mapOpts.annotation === 'string') {
- from = dirname(resolve(from, this.mapOpts.annotation))
- }
-
- let path = relative(from, file)
- this.memoizedPaths.set(file, path)
-
- return path
- }
-
- previous() {
- if (!this.previousMaps) {
- this.previousMaps = []
- if (this.root) {
- this.root.walk(node => {
- if (node.source && node.source.input.map) {
- let map = node.source.input.map
- if (!this.previousMaps.includes(map)) {
- this.previousMaps.push(map)
- }
- }
- })
- } else {
- let input = new Input(this.originalCSS, this.opts)
- if (input.map) this.previousMaps.push(input.map)
- }
- }
-
- return this.previousMaps
- }
-
- setSourcesContent() {
- let already = {}
- if (this.root) {
- this.root.walk(node => {
- if (node.source) {
- let from = node.source.input.from
- if (from && !already[from]) {
- already[from] = true
- let fromUrl = this.usesFileUrls
- ? this.toFileUrl(from)
- : this.toUrl(this.path(from))
- this.map.setSourceContent(fromUrl, node.source.input.css)
- }
- }
- })
- } else if (this.css) {
- let from = this.opts.from
- ? this.toUrl(this.path(this.opts.from))
- : '<no source>'
- this.map.setSourceContent(from, this.css)
- }
- }
-
- sourcePath(node) {
- if (this.mapOpts.from) {
- return this.toUrl(this.mapOpts.from)
- } else if (this.usesFileUrls) {
- return this.toFileUrl(node.source.input.from)
- } else {
- return this.toUrl(this.path(node.source.input.from))
- }
- }
-
- toBase64(str) {
- if (Buffer) {
- return Buffer.from(str).toString('base64')
- } else {
- return window.btoa(unescape(encodeURIComponent(str)))
- }
- }
-
- toFileUrl(path) {
- let cached = this.memoizedFileURLs.get(path)
- if (cached) return cached
-
- if (pathToFileURL) {
- let fileURL = pathToFileURL(path).toString()
- this.memoizedFileURLs.set(path, fileURL)
-
- return fileURL
- } else {
- throw new Error(
- '`map.absolute` option is not available in this PostCSS build'
- )
- }
- }
-
- toUrl(path) {
- let cached = this.memoizedURLs.get(path)
- if (cached) return cached
-
- if (sep === '\\') {
- path = path.replace(/\\/g, '/')
- }
-
- let url = encodeURI(path).replace(/[#?]/g, encodeURIComponent)
- this.memoizedURLs.set(path, url)
-
- return url
- }
- }
-
- module.exports = MapGenerator
|