• 跳到… +
    browser.coffee cake.coffee coffeescript.coffee command.coffee grammar.coffee helpers.coffee index.coffee lexer.coffee nodes.coffee optparse.coffee register.coffee repl.coffee rewriter.coffee scope.litcoffee sourcemap.litcoffee
  • sourcemap.litcoffee

  • §

    原始碼對應表讓 JavaScript 執行環境能將執行的 JavaScript 對應回原始的原始碼。這可以是經過壓縮的 JavaScript,但就我們而言,我們關注的是將經過美化的 JavaScript 對應回 CoffeeScript。

    為了產生對應表,我們必須追蹤語法樹中每個節點的原始位置(行號、欄位號),並能夠產生一個 對應表檔案,這是一個經過 VLQ 編碼的 JSON 序列化資訊的精簡表示,以便與產生的 JavaScript 一起寫出。

    LineMap

  • §

    一個 LineMap 物件會追蹤單一行輸出的 JavaScript 原始行和欄位位置的資訊。SourceMaps 是以 LineMaps 的形式實作的。

    class LineMap
      constructor: (@line) ->
        @columns = []
    
      add: (column, [sourceLine, sourceColumn], options={}) ->
        return if @columns[column] and options.noReplace
        @columns[column] = {line: @line, column, sourceLine, sourceColumn}
    
      sourceLocation: (column) ->
        column-- until (mapping = @columns[column]) or (column <= 0)
        mapping and [mapping.sourceLine, mapping.sourceColumn]
  • §

    SourceMap

  • §

    將單一產生的 JavaScript 檔案中的位置對應回原始 CoffeeScript 原始檔中的位置。

    這對於原始碼對應表如何在磁碟上表示是刻意不予置評的。一旦編譯器準備好產生「v3」樣式的原始碼對應表,我們就能夠遍歷行和欄位緩衝區的陣列來產生它。

    class SourceMap
      constructor: ->
        @lines = []
  • §

    將對應關係新增至這個 SourceMap。sourceLocation 和 generatedLocation 都是 [line, column] 陣列。如果 options.noReplace 為 true,則如果已經有指定 line 和 column 的對應關係,則此動作不會產生任何效果。

      add: (sourceLocation, generatedLocation, options = {}) ->
        [line, column] = generatedLocation
        lineMap = (@lines[line] or= new LineMap(line))
        lineMap.add column, sourceLocation, options
  • §

    查詢已產生程式碼中給定 line 和 column 的原始位置。

      sourceLocation: ([line, column]) ->
        line-- until (lineMap = @lines[line]) or (line <= 0)
        lineMap and lineMap.sourceLocation column
  • §

    快取

  • §

    靜態來源地圖快取 filename: map。這些用於轉換堆疊追蹤,目前在 CoffeeScript.compile 中設定為所有使用來源地圖選項編譯的檔案。

      @sourceMaps: Object.create null
    
      @registerCompiled: (filename, source, sourcemap) =>
        if sourcemap?
          @sourceMaps[filename] = sourcemap
    
      @getSourceMap: (filename) =>
        @sourceMaps[filename]
  • §

    V3 SourceMap 產生

  • §

    建立 V3 來源地圖,將產生的 JSON 回傳為字串。options.sourceRoot 可用於指定寫入來源地圖的 sourceRoot。此外,options.sourceFiles 和 options.generatedFile 可用於分別設定「來源」和「檔案」。

      generate: (options = {}, code = null) ->
        writingline       = 0
        lastColumn        = 0
        lastSourceLine    = 0
        lastSourceColumn  = 0
        needComma         = no
        buffer            = ""
    
        for lineMap, lineNumber in @lines when lineMap
          for mapping in lineMap.columns when mapping
            while writingline < mapping.line
              lastColumn = 0
              needComma = no
              buffer += ";"
              writingline++
  • §

    如果我們已經在這行寫入一個區段,則寫入逗號。

            if needComma
              buffer += ","
              needComma = no
  • §

    寫入下一個區段。區段可以是 1、4 或 5 個值。如果只有一個,則表示產生的欄位與來源程式碼中的任何內容都不相符。

    產生的來源中相對於目前行中任何先前記錄欄位的起始欄位

            buffer += @encodeVlq mapping.column - lastColumn
            lastColumn = mapping.column
  • §

    來源清單中的索引

            buffer += @encodeVlq 0
  • §

    原始來源中的起始行,相對於前一個來源行。

            buffer += @encodeVlq mapping.sourceLine - lastSourceLine
            lastSourceLine = mapping.sourceLine
  • §

    相對於前一欄,原始來源中的起始欄位。

            buffer += @encodeVlq mapping.sourceColumn - lastSourceColumn
            lastSourceColumn = mapping.sourceColumn
            needComma = yes
  • §

    產生「v3」來源地圖的標準 JSON 物件格式。

        sources = if options.sourceFiles
          options.sourceFiles
        else if options.filename
          [options.filename]
        else
          ['<anonymous>']
    
        v3 =
          version:    3
          file:       options.generatedFile or ''
          sourceRoot: options.sourceRoot or ''
          sources:    sources
          names:      []
          mappings:   buffer
    
        v3.sourcesContent = [code] if options.sourceMap or options.inlineMap
    
        v3
  • §

    Base64 VLQ 編碼

  • §

    請注意,SourceMap VLQ 編碼是「反向的」。MIDI 風格的 VLQ 編碼會將原始值的最高有效位元 (MSB) 放入 VLQ 編碼值的 MSB 中(請參閱 維基百科)。SourceMap VLQ 則相反,將原始值的最低有效四個位元編碼成 VLQ 編碼值的位元組。

      VLQ_SHIFT            = 5
      VLQ_CONTINUATION_BIT = 1 << VLQ_SHIFT             # 0010 0000
      VLQ_VALUE_MASK       = VLQ_CONTINUATION_BIT - 1   # 0001 1111
    
      encodeVlq: (value) ->
        answer = ''
  • §

    最低有效位元表示符號。

        signBit = if value < 0 then 1 else 0
  • §

    下一個位元組是實際值。

        valueToEncode = (Math.abs(value) << 1) + signBit
  • §

    即使 valueToEncode 為 0,也要編碼至少一個字元。

        while valueToEncode or not answer
          nextChunk = valueToEncode & VLQ_VALUE_MASK
          valueToEncode = valueToEncode >> VLQ_SHIFT
          nextChunk |= VLQ_CONTINUATION_BIT if valueToEncode
          answer += @encodeBase64 nextChunk
    
        answer
  • §

    一般 Base64 編碼

  • §
      BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    
      encodeBase64: (value) ->
        BASE64_CHARS[value] or throw new Error "Cannot Base64 encode value: #{value}"
  • §

    我們用於來源地圖的 API 只是 SourceMap 類別。

    module.exports = SourceMap