关于reactnatice android 集成离线sourcemap的

来源:互联网 发布:mac安装第三方软件 编辑:程序博客网 时间:2024/05/18 00:01

rn打离线包往往会遇到一个问题,代码报错的行号是bundle文件中的行号,而且变量名也是编译过的,这样代码的阅读性就会下降,甚至根本找不到原因。而rn自己对离线sourcemap的支持非常不好(感觉很不科学啊,online的根本不需要sourcemap啊),甚至依赖包根本不能用。

所以我总结了一下离线sourcemap在android项目中的应用。


1.首先你要把sourcemap文件打包到android项目中,差不多像这样


2.其次,你的rn项目要依赖react-native-fs包,install出来以后把把下面android包中rnfs文件夹拷到你自己的android项目中当做你自己的操作类。大概像这样


3.往你项目的packageManager中添加rnfspackage,并在app启动的时候创建sourcemap文件在本地,像这样

4.在rn中创建错误收集handler

import ErrorUtils from'ErrorUtils';
import { alert } from'react-native';
import { initSourceMaps, getStackTrace } from'./SourceMapHandler';
import { nativeCallModuleWrapper } from'./NativeModuleWrappers/NativeCallModuleWrapper';

const setupErrorHandler =async (sourceMapBundleName)=> {
await initSourceMaps({sourceMapBundle: sourceMapBundleName,collapseInLine: true });

await (asyncfunction initUncaughtErrorHandler() {
const defaultGlobalHandler =ErrorUtils.getGlobalHandler();

ErrorUtils.setGlobalHandler(async (error, isFatal)=> {
try {
if (!__DEV__) {
error.stack = awaitgetStackTrace(error);
}

nativeCallModuleWrapper.reportCrash(error.message, error.stack);
} catch (ex) {
alert(`${ex.message}----Unable to setup global handler----${ex.stack}`);
}

if (__DEV__ && defaultGlobalHandler) {
defaultGlobalHandler(error, isFatal);
}
});
}());
};

export default setupErrorHandler;

5.创建错误收集handler需要用到的方法

import RNFS from'react-native-fs';
import SourceMap from"source-map";
import StackTrace from"stacktrace-js";
import ErrorStackParser from"error-stack-parser";

let sourceMapper = undefined;
let options = undefined;

/**
* Init Source mapper with options
* Required:
* @param {String} [opts.sourceMapBundle] - source map bundle, for example "main.jsbundle.map"
* Optional:
* @param {String} [opts.projectPath] - project path to remove from files path
* @param {Boolean} [opts.collapseInLine] — Will collapse all stack trace in one line, otherwise return lines array
*/
export constinitSourceMaps = asyncopts => {
    if (!opts || !opts.sourceMapBundle) {       
        throw new Error('Please specify sourceMapBundle option parameter');
    }
    options = opts;
};

export constgetStackTrace = asyncerror => {
    if (!options) { 
        throw new Error('Please firstly call initSourceMaps with options');
    }
    if (!sourceMapper) {
        sourceMapper = awaitcreateSourceMapper();
    }
    try {
        const minStackTrace = awaitfromError(error);
        const stackTrace = minStackTrace.map(row=> {
            const mapped =sourceMapper(row);
            const source = mapped.source ||"";
            const fileName = options.projectPath ? source.split(options.projectPath).pop() : source;
            const functionName = mapped.name ||"unknown";
            return {
                fileName,
                functionName,
                lineNumber: mapped.line,
                columnNumber: mapped.column,
                position: `${functionName}@${fileName}:${mapped.line}:${mapped.column}`
            };
        });
        return options.collapseInLine ? stackTrace.map(i=> i.position).join('\n') : stackTrace;
    }
    catch (error) {
        alert(error.message);
        throw error;
    }
};

function _filtered(stackframes, filter) {
if (typeof filter ==='function') {
return stackframes.filter(filter);
}
return stackframes;
}

function fromError(error, opts){
    var _options = {
filter: function(stackframe) {
// Filter out stackframes for this library by default
return (stackframe.functionName ||'').indexOf('StackTrace$$') ===-1 &&
(stackframe.functionName ||'').indexOf('ErrorStackParser$$') ===-1 &&
(stackframe.functionName ||'').indexOf('StackTraceGPS$$') ===-1 &&
(stackframe.functionName ||'').indexOf('StackGenerator$$') ===-1;
},
sourceCache: {}
};
opts = _merge(_options, opts);
//var gps = new StackTraceGPS(opts);
return newPromise(function(resolve) {
var stackframes =_filtered(ErrorStackParser.parse(error), opts.filter);
resolve(Promise.all(stackframes.map(function(sf) {
return newPromise(function(resolve) {
//function resolveOriginal() {
resolve(sf);
//}

//gps.pinpoint(sf).then(resolve, resolveOriginal)['catch'](resolveOriginal);
});
})));
}.bind(this));
}

function _merge(first, second) {
var target = {};

[first, second].forEach(function(obj) {
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
target[prop] = obj[prop];
}
}
return target;
});

return target;
}

const createSourceMapper =async () => {
    RNFS.MainBundlePath="/storage/emulated/0/Android/data/com.test/cache"
    const path = `${RNFS.MainBundlePath}/${options.sourceMapBundle}`;
    try {
        const fileExists = awaitRNFS.exists(path);
        if (!fileExists) {
            throw new Error(__DEV__ ?
                'Unable to read source maps in DEV mode' :
                `Unable to read source maps, possibly invalid sourceMapBundle file, please check that it exists here: ${RNFS.MainBundlePath}/${options.sourceMapBundle}`
            );
        }

        const mapContents = awaitRNFS.readFile(path,'utf8');
        const sourceMaps =JSON.parse(mapContents);
        const mapConsumer =new SourceMap.SourceMapConsumer(sourceMaps);

        return sourceMapper = row => {
            return mapConsumer.originalPositionFor({
                line: row.lineNumber,
                column: row.columnNumber,
            });
        };
    }
    catch (error) {
        throw error;
    }
};
一定要设置好RNFS.MainBundlePath到你创建sourcemap文件的位置。

原创粉丝点击