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檔案的位置。