天天看点

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