Skip to content

Commit

Permalink
Works with maps from several inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
ai committed Jun 13, 2014
1 parent b627ecd commit a78e3c0
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 168 deletions.
15 changes: 0 additions & 15 deletions lib/lazy.coffee

This file was deleted.

108 changes: 28 additions & 80 deletions lib/map-generator.coffee
Original file line number Diff line number Diff line change
@@ -1,105 +1,53 @@
base64js = require('base64-js')
mozilla = require('source-map')

Result = require('./result')
lazy = require('./lazy')
path = require('path')
fs = require('fs')
Result = require('./result')
path = require('path')

# All tools to generate source maps
class MapGenerator
constructor: (@root, @opts) ->

# Is `string` is starting with `start`
startWith: (string, start) ->
string[0..start.length-1] == start

# Should map be generated
isMap: ->
return @opts.map if typeof(@opts.map) == 'boolean'
!!@opts.inlineMap || !!@prevMap()
return !!@opts.map if @opts.map?
!!@opts.inlineMap || @previous().length > 0

# Return source map arrays from previous compilation step (like Sass)
previous: ->
unless @previousMaps
@previousMaps = []
@root.eachInside (node) =>
if node.source?.map?
if @previousMaps.indexOf(node.source.map) == -1
@previousMaps.push(node.source.map)
@previousMaps

# Should we inline source map to annotation comment
lazy @, 'isInline', ->
isInline: ->
return @opts.inlineMap if @opts.inlineMap?
@isPrevInline()

# Is source map from previous compilation step is inline to annotation comment
lazy @, 'isPrevInline', ->
return false unless @prevAnnotation()

text = @prevAnnotation().text
@startWith(text, '# sourceMappingURL=data:')

# Source map from previous compilation step (like Sass)
lazy @, 'prevMap', ->
return @opts.map if @opts.map and typeof(@opts.map) != 'boolean'
@previous().some (i) -> i.inline

if @isPrevInline()
@encodeInline(@prevAnnotation().text)
else if @opts.from
map = @opts.from + '.map'
if @prevAnnotation()
file = @prevAnnotation().text.replace('# sourceMappingURL=', '')
map = path.join(path.dirname(@opts.from), file)

if fs.existsSync?(map)
fs.readFileSync(map).toString()
else
false

# Lazy load for annotation comment from previous compilation step
lazy @, 'prevAnnotation', ->
# Clear source map annotation comment
clearAnnotation: ->
last = @root.last
return null unless last

if last.type == 'comment' and @startWith(last.text, '# sourceMappingURL=')
last
else
null

# Encode different type of inline
encodeInline: (text) ->
uri = '# sourceMappingURL=data:application/json,'
base64 = '# sourceMappingURL=data:application/json;base64,'

if @startWith(text, uri)
decodeURIComponent( text[uri.length..-1] )

else if @startWith(text, base64)
text = text[base64.length..-1]
bytes = base64js.toByteArray(text)
(String.fromCharCode(byte) for byte in bytes).join('')

else
throw new Error('Unknown source map encoding')

# Clear source map annotation comment
clearAnnotation: ->
@prevAnnotation()?.removeSelf()
if last.type == 'comment' and last.text.match(/^# sourceMappingURL=/)
last.removeSelf()

# Apply source map from previous compilation step (like Sass)
applyPrevMap: ->
if @prevMap()
prev = @prevMap()

prev = if typeof(prev) == 'string'
JSON.parse(prev)
else if prev instanceof mozilla.SourceMapConsumer
mozilla.SourceMapGenerator.fromSourceMap(prev).toJSON()
else if typeof(prev) == 'object' and prev.toJSON
prev.toJSON()
else
prev
applyPrevMaps: ->
return if @previous().length == 0

prev = new mozilla.SourceMapConsumer(prev)
from = @relative(@opts.from)
@map.applySourceMap(prev, from, path.dirname(from))
for prev in @previous()
from = @relative(prev.file)
map = new mozilla.SourceMapConsumer(prev.map)
@map.applySourceMap(map, from, path.dirname(from))

# Add source map annotation comment if it is needed
addAnnotation: ->
return if @opts.mapAnnotation == false
return if @prevMap() and not @prevAnnotation()
return if @previous().length > 0 and @previous().every (i) -> !i.annotation

content = if @isInline()
bytes = (char.charCodeAt(0) for char in @map.toString())
Expand All @@ -116,7 +64,7 @@ class MapGenerator
# Return Result object with map
generateMap: ->
@stringify()
@applyPrevMap()
@applyPrevMaps()
@addAnnotation()

if @isInline()
Expand Down
8 changes: 8 additions & 0 deletions lib/parse.coffee
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
SyntaxError = require('./syntax-error')
PreviousMap = require('./previous-map')
Declaration = require('./declaration')
Comment = require('./comment')
AtRule = require('./at-rule')
Expand Down Expand Up @@ -31,6 +32,12 @@ class Parser
@nextLetter()
@endFile()

setMap: ->
map = new PreviousMap(@root, @opts)
if map.map
@root.prevMap = map
@root.eachInside (i) -> i.source.map = map

nextLetter: ->
@inString() ||
@inComment() ||
Expand Down Expand Up @@ -390,4 +397,5 @@ class Parser
module.exports = (source, opts = { }) ->
parser = new Parser(source, opts)
parser.loop()
parser.setMap()
parser.root
69 changes: 69 additions & 0 deletions lib/previous-map.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
base64js = require('base64-js')
mozilla = require('source-map')
path = require('path')
fs = require('fs')

class PreviousMap
constructor: (root, opts) ->
@file = opts.from

@loadAnnotation(root)
@inline = @startWith(@annotation, '# sourceMappingURL=data:') if @annotation

text = @loadMap(opts)
@map = text if text

# Is `string` is starting with `start`
startWith: (string, start) ->
return false unless string
string[0..start.length-1] == start

# Load for annotation comment from previous compilation step
loadAnnotation: (root) ->
last = root.last
return unless last

if last.type == 'comment' and @startWith(last.text, '# sourceMappingURL=')
@annotation = last.text

# Encode different type of inline
decodeInline: (text) ->
uri = '# sourceMappingURL=data:application/json,'
base64 = '# sourceMappingURL=data:application/json;base64,'

if @startWith(text, uri)
decodeURIComponent( text[uri.length..-1] )

else if @startWith(text, base64)
text = text[base64.length..-1]
bytes = base64js.toByteArray(text)
(String.fromCharCode(byte) for byte in bytes).join('')

else
throw new Error('Unknown source map encoding')

# Load previous map
loadMap: (opts) ->
if opts.map and typeof(opts.map) != 'boolean'
if typeof(opts.map) == 'string'
opts.map
else if opts.map instanceof mozilla.SourceMapConsumer
mozilla.SourceMapGenerator.fromSourceMap(opts.map).toString()
else if opts.map instanceof mozilla.SourceMapGenerator
opts.map.toString()
else
JSON.stringify(opts.map)

else if @inline
@decodeInline(@annotation)

else if @file
if @annotation
file = @annotation.replace('# sourceMappingURL=', '')
map = path.join(path.dirname(@file), file)
else
map = @file + '.map'

fs.readFileSync(map).toString() if fs.existsSync?(map)

module.exports = PreviousMap
24 changes: 0 additions & 24 deletions test/lazy.coffee

This file was deleted.

49 changes: 0 additions & 49 deletions test/map.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -147,27 +147,6 @@ describe 'source maps', ->
base64 = new Buffer(common2.map).toString('base64')
inline2.css.should.endWith(base64 + ' */')

it 'supports uri encoding in inline map', ->
css = "a {\n" +
" color: #000000;\n" +
"}\n" +
"/*# sourceMappingURL=data:application/json,%7B%22version%22%3A3%2C" +
"%22file%22%3A%22test.css%22%2C%22sources%22%3A%5B%22test.less%22" +
"%5D%2C%22names%22%3A%5B%5D%2C%22mappings%22%3A%22AAAA%3BEAAI%2CcAA" +
"A%22%7D */"

result = @doubler.process(css, from: 'test.css', to: 'test.css')

result.should.not.have.property('map')
result.css.should.match(/# sourceMappingURL=data:/)

it 'raises on unknown inline encoding', ->
css = "a { }\n" +
"/*# sourceMappingURL=data:application/json;" +
"md5,68b329da9893e34099c7d8ad5cb9c940*/"

( => @doubler.process(css) ).should.throw('Unknown source map encoding')

it 'allows change map type', ->
css = 'a { }'

Expand All @@ -184,34 +163,6 @@ describe 'source maps', ->
step2.should.have.property('map')
step2.css.should.not.match(/# sourceMappingURL=data:/)

it 'checks map file near CSS', ->
step1 = @doubler.process 'a { }',
from: 'a.css'
to: @dir + '/a.css'
map: true

fs.outputFileSync(@dir + '/a.css.map', step1.map)
step2 = @lighter.process step1.css,
from: @dir + '/a.css'
to: 'b.css'

step2.should.have.property('map')

it 'read map file from annotation', ->
step1 = @doubler.process 'a { }',
from: 'a.css'
to: @dir + '/a.css'
map: true

fs.outputFileSync(@dir + '/b.css.map', step1.map)
css = step1.css.replace('a.css.map', 'b.css.map')

step2 = @lighter.process css,
from: @dir + '/a.css'
to: 'c.css'

step2.should.have.property('map')

it 'miss check files on requires', ->
step1 = @doubler.process 'a { }',
from: 'a.css'
Expand Down

0 comments on commit a78e3c0

Please sign in to comment.