sourcemap

metro v0.66.2 react-native-community/cli v6.4.0 mozilla/source-map 0.5.7

source-map的基本使用生产与解析

bundle

打包

npx react-native bundle --entry-file index.js --platform ios --dev false --bundle-output RNBundle/index.ios.bundle --assets-dest RNBundle/ --sourcemap-output RNBundle/index.ios.bundle.map --verbose

metro-symbolicate解析bundle

export default class TestClass {
  testFunc() {
    a = a + 1;
    console.log('hello');
    let b = b + 1;
  }
}

sourcemap解析命令 npx metro-symbolicate ./index.bundle.map < ./track.js

track.js文件是crash的堆栈

ReferenceError: Property 'a' doesn't exist, js engine: hermes

value
    index.ios.bundle:420:242
anonymous
    index.ios.bundle:6:179
h
    index.ios.bundle:2:1585
d
    index.ios.bundle:2:958
o
    index.ios.bundle:2:496
global
    index.ios.bundle:423:3

mozilla/source-map解析bundle

最终的调用的是source-map相关内容,如下测试代码


const sourceMap = require('source-map');
const fs = require('fs');

/**
 * 异步解析行、列对应的函数信息
 * @param {number} line  行
 * @param {number} column 列
 * @returns Promise
 */
const asyncSymbolicate = async (line = 0, column = 0) => {
    return new Promise((resolve, reject) => {
        if (line < 0 || column < 0) {
            reject('invalid params');
            return;
        }
        fs.readFile('./index.bundle.map', 'utf8', (err, data) => {
            if (err) {
                reject(err);
            } else {
                const smc = new sourceMap.SourceMapConsumer(data);
                smc.then(consumer => {
                    console.log('line'+line)
                    const info = consumer.originalPositionFor({
                        line: line,
                        column: column
                    });
                    console.log('info'+JSON.stringify(info))
                    resolve(info);
                }).catch(err => {
                    reject(err);
                });
            }
        });
    });
};

测试一个crash,变量a未定义

a = a + 1

输入的内容,分别表示文件路径、行、列

index.ios.bundle:420:296

输出

{
  source: '/AwesomeProject/awesomeproject/test.js',
  line: 3,
  column: 4,
  name: 'a'
}

hermes 解析

若hermes产物含 sourcemap, 可以直接用react-native打包出来的sourcemap进行解析,方式跟源码包一样。

hermes source map剥离

若hermes 打包时选择-output-source-map 则 sourcemap会从hermes包内剥离,减少hermes包的大小,但解析前要先合并sourcemap

首先基于源码打包后的文件,进行hermes打包

  ./node_modules/hermes-engine/osx-bin/hermesc -emit-binary -out BCRNBundle/temp/index.ios.bundle BCRNBundle/index.ios.bundle -output-source-map

BCRNBundle/index.ios.bundle为源码包路径,执行命令后在temp目录下产生新的index.ios.bundle和map文件

然后进行sourcemap合并

  node ./node_modules/react-native/scripts/compose-source-maps.js RNBundle/index.ios.bundle.map BCRNBundle/hbc/index.ios.bundle.map -o BCRNBundle/index.ios.bundle.map

解析方式同源码解析

参考文档 https://reactnative.cn/docs/symbolication

ram-bundle打包

npx react-native ram-bundle --platform ios --dev true --entry-file index.js  --bundle-output  release/ios/RNBundle/index.ios.bundle  --assets-dest release/ios/RNBundle --sourcemap-output release/ios/ios-release.bundle.map

crash 堆栈

516.js:15:13

可以使用metro-symbolicate解析crash

metro-source-map

source map 生成

describe('build map from raw mappings', () => {
  it('returns a `Generator` instance', () => {
    expect(fromRawMappings([])).toBeInstanceOf(Generator);
  });

  it('returns a working source map containing all mappings', () => {
    const input = [
      {
        code: lines(11),
        functionMap: {names: ['<global>'], mappings: 'AAA'},
        map: [
          [1, 2],
          [3, 4, 5, 6, 'apples'],
          [7, 8, 9, 10],
          [11, 12, 13, 14, 'pears'],
        ],
        source: 'code1',
        path: 'path1',
      },
      {
        code: lines(3),
        functionMap: {names: ['<global>'], mappings: 'AAA'},
        map: [
          [1, 2],
          [3, 4, 15, 16, 'bananas'],
        ],
        source: 'code2',
        path: 'path2',
      },
      {
        code: lines(23),
        functionMap: null,
        map: [
          [11, 12],
          [13, 14, 15, 16, 'bananas'],
          [17, 18, 19, 110],
          [21, 112, 113, 114, 'pears'],
        ],
        source: 'code3',
        path: 'path3',
      },
    ];

    expect(fromRawMappings(input).toMap()).toEqual({
      mappings:
        'E;;IAIMA;;;;QAII;;;;YAIIC;E;;ICEEC;;;;;;;;;;;Y;;cCAAA;;;;kBAI8F;;;;gHA8FID',
      names: ['apples', 'pears', 'bananas'],
      sources: ['path1', 'path2', 'path3'],
      sourcesContent: ['code1', 'code2', 'code3'],
      x_facebook_sources: [
        [{names: ['<global>'], mappings: 'AAA'}],
        [{names: ['<global>'], mappings: 'AAA'}],
        null,
      ],
      version: 3,
    });
  });

从原始的map生成source map

  • fromRawMappings流程,返回值Generator
    • 调用addMappingsForFile函数,传入map参数
    • 遍历map数组,调用addMapping函数
    • 最后调用Generator的相关函数,比如addSimpleMapping函数,传入generatedLine、generatedColumn
    • 调用B64Builder的startSegment函数,传入参数为当前列减去上一列。(base64相关操作)
  • Generator的toMap函数,返回source map

source map 格式

见source-map.js 文件MixedSourceMap类型

bundle sourcemap格式,见BasicSourceMap

sourcemap是一个json文件,字段解释

  • sources,数组,rn的所有文件路径
  • sourcesContent,sources对应的文件内容数组
  • x_facebook_sources,里面是与sources对应一致的函数名数组
    • names,该文件的函数名数组
    • mappings,
  • names,所有的函数数组
  • mappings,mapping一个分号为1行,逗号为一个segment

ram-bundle sourcemap格式,见IndexMap

  • sections,元素为json,包含以下两个字段,相当于把bundle source map切割为很多sections
    • map,这里的字段和bundle打包一样
    • offset
  • x_facebook_offsets,所有文件的offset
  • x_metro_module_paths,所有文件路径

source map 解析

  it('performs lookup correctly in a non-indexed map', () => {
    const map = {
      version: 3,
      names: ['first', 'second'],
      sources: ['foo.js', 'bar.js'],
      mappings: 'AAAAA,CCCCC',
    };
    const consumer = new Consumer(map);
    expect(
      consumer.originalPositionFor({line: add1(0), column: add0(0)}),
    ).toEqual(
      objectContaining({source: 'foo.js', line: 1, column: 0, name: 'first'}),
    );
  });

Consumer的初始化

  • DelegatingConsumer初始化,传入MixedSourceMap
  • 若有mapping,初始化MappingsConsumer(basic),若无mappings,初始化SectionsConsumer(index),它们都继承AbstractConsumer

调用originalPositionFor函数

对于MappingsConsumer

  • 调用_decodeAndCacheMappings函数,解码sourcemap文件,得到mappings对象
  • 调用greatestLowerBound函数,遍历mappings,比对generatedLine,generatedColumn(堆栈),拿到原文件的line、column

compose source map

见composeSourceMaps.js文件,上文compose-source-maps.js合并源码map和hermes map,调用的就是composeSourceMaps.js文件的composeSourceMaps函数

mozilla/source-map

source map spec https://sourcemaps.info/spec.html

source map解析

mozilla/source-map的流程与metro-source-map类似

  • SourceMapConsumer(bundle BasicSourceMapConsumer,ram-bundle IndexedSourceMapConsumer),mozilla/source-map
  • originalPositionFor,返回source(源文件绝对路径),参数line,colum,数据来自于mapping

mapping格式

{
  generatedLine: 1099, 
  generatedColumn: 25,
  source: null,
  originalLine: 11,
  originalColumn: 19,
  name: null
}

generatedLine,generatedColumn是crash时堆栈的行列,originalLine,originalColumn为解析出来的源文件行列。 source为sourcemap文件中sources数组的位置,这里可以取出源文件绝对路径

originalPositionFor的过程,mozilla/source-map,调用_findMapping函数遍历mapping,找到对应的originalLine和originalColumn

results matching ""

    No results matching ""