关于reactnatice android 集成离线sourcemap的

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 ( async function initUncaughtErrorHandler() {
const defaultGlobalHandler = ErrorUtils. getGlobalHandler();
 
ErrorUtils. setGlobalHandler( async ( error, isFatal) => {
try {
if (! __DEV__) {
error. stack = await getStackTrace( 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 const initSourceMaps = async opts => {
    if (! opts || ! opts. sourceMapBundle) {       
        throw new Error( 'Please specify sourceMapBundle option parameter');
    }
    options = opts;
};
 
export const getStackTrace = async error => {
    if (! options) { 
        throw new Error( 'Please firstly call initSourceMaps with options');
    }
    if (! sourceMapper) {
        sourceMapper = await createSourceMapper();
    }
    try {
        const minStackTrace = await fromError( 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 new Promise( function( resolve) {
var stackframes = _filtered( ErrorStackParser. parse( error), opts. filter);
resolve( Promise. all( stackframes. map( function( sf) {
return new Promise( 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 = await RNFS. 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 = await RNFS. 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文件的位置。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值