React Native跨平台鸿蒙开发高级应用原理:通过配置 Metro 打包器 来实现 React Native 应用的代码拆分(拆包)
本文介绍了React Native项目中Metro打包工具的安装与配置方法。在安装方面,可通过npm或yarn单独安装最新版Metro。配置Metro主要有三种方式,重点讲解了metro.config.js的配置结构,特别强调了拆包打包中关键的serializer选项。通过示例工程详细说明了如何利用createModuleIdFactory和processModuleFilter两个核心方法实现模
Metro安装
在运行 npm install 时 React Native 已经安装 Metro 了,其版本跟 React Native 版本有关,如果需要单独安装最新版 Metro,可以执行以下命令:
npm install --save-dev metro metro-core
或
yarn add --dev metro metro-core
Metro配置
配置 Metro 有三种方法,分别为 metro.config.js、metro.config.json 和 package.json 中添加 metro 字段,常用的方式为 metro.config.js。
Metro 配置内部结构请参考Metro官网。
module.exports = {
resolver: {
/* resolver options */
},
transformer: {
/* transformer options */
},
serializer: {
/* serializer options */
},
server: {
/* server options */
}
/* general options */
};
每个 options 内都有很多配置选项,而对于拆包打包来说,最重要的是 serializer 选项内的 createModuleIdFactory 与 processModuleFilter。
createModuleIdFactory:Metro 支持了通过此方法配置自定义模块 ID,同样支持字符串类型 ID,用于生成require语句的模块 ID,其类型为() => (path: string) => number,其中path为各个 module 的完整路径。此方法的另一个用途就是多次打包时,对于同一个模块生成相同的 ID,下次更新发版时,不会因 ID 不同找不到 Module。processModuleFilter:根据给出的条件,对 Module 进行过滤,将不需要的模块过滤掉。其类型为(module: Array<Module>) => boolean,其中module为输出的模块,里面带着相应的参数,根据返回的布尔值判断是否过滤当前模块。返回false为过滤,不打入 bundle。
下面参考配套的 Demo 工程 SampleProject 来具体说明一下该如何配置和使用 createModuleIdFactory 与 processModuleFilter。
-
首先创建一个
SampleProject/MainProject/build/multibundle/moduleId.js文件,其中自定义了createModuleIdFactoryWrap和postProcessModulesFilterWrap两个方法,用于对应createModuleIdFactory与processModuleFilter两个配置选项。-
createModuleIdFactoryWrap主要作用是判断模块是基础包还是业务包,然后将获取到的ModuleId分别存放到basicNameMap.json和pageNameMap.json文件中,留给postProcessModulesFilterWrap方法备用。basicNameMap.json代表基础包所涉及到的ModuleId集合,pageNameMap.json代表业务包所涉及到的ModuleId集合,这两个文件位于moduleId.js同目录的map文件夹下。 -
postProcessModulesFilterWrap方法只需要在业务包的metro.config.js中配置,主要作用是通过一系列判断条件来判断需要打包的模块是否已经存在于basicNameMap.json文件中,如果存在,则返回false,不进行打包;反之,则该模块需要进行打包。// SampleProject/MainProject/build/multibundle/moduleId.js const pathSep = require('path').sep; const fs = require('fs'); const SHA256 = require('crypto-js/sha256'); const basicNameArray = require('./map/basicNameMap.json'); const homepageArray = require('./map/pageNameMap.json'); function getModuleId(projectRootPath, modulePath, ...bundles) { let startIndex = modulePath.indexOf(projectRootPath); let pathRelative = modulePath.substr(startIndex + projectRootPath.length + 1); return String(SHA256(pathRelative)); } function createModuleIdFactoryWrap(projectRootPath, ...bundles) { return () => { return (path) => { let moduleId = getModuleId(projectRootPath, path); let jsItem = path + ' ---> ' + moduleId; if ('basic' == bundles[0]) { if (!basicNameArray.includes(jsItem)) { basicNameArray.push(jsItem); fs.writeFileSync( __dirname + pathSep + 'map' + pathSep + 'basicNameMap.json', JSON.stringify(basicNameArray), ); } } else { if (!homepageArray.includes(jsItem)) { homepageArray.push(jsItem); fs.writeFileSync( __dirname + pathSep + 'map' + pathSep + 'pageNameMap.json', JSON.stringify(homepageArray), ); } } return moduleId; }; }; } function postProcessModulesFilterWrap(projectRootPath) { // 返回false则不打入bundle中 console.log('----------postProcessModulesFilterWrap'); return (module) => { const path = module.path; if ( path.indexOf('__prelude__') >= 0 || path.indexOf( pathSep + 'node_modules' + pathSep + '@react-native' + pathSep + // 'js-polyfills', 'polyfills', ) >= 0 || path.indexOf( pathSep + 'node_modules' + pathSep + 'metro-runtime' + pathSep + 'src' + pathSep + 'polyfills', ) >= 0 ) { return false; } const moduleId = getModuleId(projectRootPath, path); let jsItem = path + ' ---> ' + moduleId; if (path.indexOf(pathSep + 'node_modules' + pathSep) > 0) { if ( 'js' + pathSep + 'script' + pathSep + 'virtual' == module.output[0].type ) { return true; } } // 正在打业务包 if ( basicNameArray.includes(jsItem) ) { return false; } return true; }; } module.exports = {createModuleIdFactoryWrap,postProcessModulesFilterWrap};
-
通过配置 Metro 打包器 来实现 React Native 应用的代码拆分(拆包)。其根本目的是将一个庞大的应用 bundle 文件,拆分成一个基础包(common.bundle)和多个**业务包/页面包(page1.bundle, page2.bundle…)。
核心目标:为什么要拆包?
减小首次加载体积:将 React、React Native 等基础库打包进基础包,用户只需在首次启动时加载一次。
按需加载:当用户访问某个特定页面时,才去动态加载该页面对应的业务包。这能极大提升应用的启动速度和运行时的用户体验。
实现拆包的两个关键配置,实现拆包最核心的是配置 serializer 选项下的两个方法:
- createModuleIdFactory:
作用:为每一个模块(js文件)生成一个唯一的、固定的ID。
为何重要:在多次打包(比如先打基础包,再打业务包)时,确保同一个模块在不同次打包中得到的ID是相同的。这是为了避免业务包中重复包含基础包中已有的模块。如果ID不同或变动,运行时可能因找不到模块而报错。
- processModuleFilter:
作用:像一个“过滤器”,决定哪些模块需要被打入当前的bundle中。
示例代码展示了一个完整的、可操作的拆包方案,其工作流程可以概括为两步:
- 打基础包:
在 metro.config.js 中配置 createModuleIdFactory 为 createModuleIdFactoryWrap,并传入参数 ‘basic’。
打包过程中,它会为所有模块生成ID,并将这些“模块路径 -> ID”的映射关系记录到 basicNameMap.json 文件中。
- 打业务包:
同样配置 createModuleIdFactory,但不传 ‘basic’ 参数,从而将映射关系记录到 pageNameMap.json。
同时,必须配置 processModuleFilter 为 postProcessModulesFilterWrap。
在打业务包时,这个过滤器会检查当前模块是否已经存在于 basicNameMap.json 中。
关键逻辑:如果发现某个模块已经在基础包里了,就返回 false 将其过滤掉,不打入当前的业务包中。
总而言之,这段说明提供了一套利用 Metro 内置配置,实现 React Native 代码拆包以优化性能的具体方法和代码范例。
更多推荐


所有评论(0)