天天看點

關于reactnatice android 內建離線sourcemap的

rn打離線包往往會遇到一個問題,代碼報錯的行号是bundle檔案中的行号,而且變量名也是編譯過的,這樣代碼的閱讀性就會下降,甚至根本找不到原因。而rn自己對離線sourcemap的支援非常不好(感覺很不科學啊,online的根本不需要sourcemap啊),甚至依賴包根本不能用。

是以我總結了一下離線sourcemap在android項目中的應用。

1.首先你要把sourcemap檔案打包到android項目中,差不多像這樣

關于reactnatice android 內建離線sourcemap的

2.其次,你的rn項目要依賴react-native-fs包,install出來以後把把下面android包中rnfs檔案夾拷到你自己的android項目中當做你自己的操作類。大概像這樣

關于reactnatice android 內建離線sourcemap的
關于reactnatice android 內建離線sourcemap的

3.往你項目的packageManager中添加rnfspackage,并在app啟動的時候建立sourcemap檔案在本地,像這樣

關于reactnatice android 內建離線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檔案的位置。