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;
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文件的位置。