我不懂原生,只作 js 部分,有懂这方面的大佬,也能够补充原生部分,git地址: github.com/Mr-jiangzhi…前端
环境:node
macOS: 10.14.4;react
node(nvm): 10.15.3;android
react-native-cli: 2.0.1;ios
react-native: 0.60.0;git
.
├── .babelrc
├── .buckconfig
├── .editorconfig
├── .eslintrc.js
├── .flowconfig
├── .git
├── .gitattributes
├── .gitignore
├── .patch (存放增量文件)
├── .watchmanconfig
├── README.md
├── __tests__
├── android (原生代码,原生开发维护)
├── app.json
├── babel.config.js
├── bundles (bundle输出目录)
├── business.config.js (打包业务模块的配置)
├── common.config.js (打包公共基础模块的配置)
├── common.js (公共基础模块的入口文件,将第三方依赖/shim等文件引入进来)
├── config (配置文件目录)
├── index.js (开发模式的入口文件)
├── ios (原生代码,原生开发维护)
├── jsconfig.json
├── package.json
├── scripts (脚本目录)
├── src (前端页面,前端开发维护)
├── upload (存放需上传服务器的文件)
└── yarn.lock
复制代码
一般,咱们将一个大的 jsbundle 包拆分为基础包和业务包:github
基于 react-native bundle
命令拆包(实际上是基于metro) 命令为咱们提供了 --config
参数,让咱们能够本身指定配置文件,这样咱们就能够经过两个配置文件(common 和 business)来分别进行打包了:npm
react-native bundle [其余配置] --config common
:打公共基础包react-native bundle [其余配置] --config business
:打业务包下面咱们来看看 metro 的这个 config 文件:json
module.exports = {
resolver: {
/* resolver options */
},
transformer: {
/* transformer options */
},
serializer: {
/* serializer options */
},
server: {
/* server options */
},
/* general options */
};
复制代码
对于拆包咱们能用到的就是 serializer
中的 createModuleIdFactory
和 postProcessBundleSourcemap
这两个方法:react-native
createModuleIdFactory
:
require
statements.postProcessBundleSourcemap
:
config
目录新建 createModuleIdFactory.js
和 postProcessBundleSourcemap.js
createModuleIdFactory.js
:
/** * 生成模块Id * 基础打包配置 */
const path = require('path');
const pathSep = path.posix.sep;
function createModuleIdFactory() {
const projectRootPath = process.cwd(); //获取命令行执行的目录
return modulePath => {
// console.log(modulePath)
let moduleName = '';
if (modulePath.indexOf(`node_modules${pathSep}react-native${pathSep}Libraries${pathSep}`) > 0) {
//这里是去除路径中的'node_modules/react-native/Libraries/‘以前(包括)的字符串,能够减小包大小,无关紧要
moduleName = modulePath.substr(modulePath.lastIndexOf(pathSep) + 1);
} else if (modulePath.indexOf(projectRootPath) === 0) {
//这里是取相对路径,不这么弄的话就会打出_user_smallnew_works_....这么长的路径,还会把计算机名打进去
moduleName = modulePath.substr(projectRootPath.length + 1);
}
moduleName = moduleName.replace('.js', ''); //js png字符串不必打进去
moduleName = moduleName.replace('.png', '');
let regExp = pathSep === '\\' ? new RegExp('\\\\', 'gm') : new RegExp(pathSep, 'gm');
moduleName = moduleName.replace(regExp, '_'); //把path中的/换成下划线(适配Windows平台路径问题)
// console.log(moduleName);
return moduleName;
};
}
module.exports = createModuleIdFactory;
复制代码
postProcessBundleSourcemap.js
:
// 业务代码
const path = require('path');
const pathSep = path.posix.sep;
// 这里简单暴力地吧preclude和node_modules目录下的文件所有过滤掉,只打本身写的代码。
// 只有本身写的才算是业务代码
function postProcessModulesFilter(module) {
//返回false则过滤不编译
if (module.path.indexOf('__prelude__') >= 0) {
return false;
}
if (module.path.indexOf(pathSep + 'node_modules' + pathSep) > 0) {
if (`js${pathSep}script${pathSep}virtual` === module.output[0].type) {
return true;
}
if (
module.path.indexOf(
`${pathSep}node_modules${pathSep}@babel${pathSep}runtime${pathSep}helpers`
) > 0
) {
//添加这个判断,让@babel/runtime打进包去
return true;
}
return false;
}
return true;
}
module.exports = postProcessModulesFilter;
复制代码
common.config.js
和 business.config.js
common.config.js
:
// 基础打包配置
const createModuleIdFactory = require('./config/createModuleIdFactory');
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: true,
inlineRequires: false,
},
}),
},
serializer: {
// 全部模块一经转换就会被序列化,Serialization会组合这些模块来生成一个或多个包,包就是将模块组合成一个JavaScript文件的包。
// 函数传入的是你要打包的module文件的绝对路径返回的是这个module的id
// 配置createModuleIdFactory让其每次打包都module们使用固定的id(路径相关)
createModuleIdFactory: createModuleIdFactory,
/* serializer options */
},
};
复制代码
business.config.js
:
// 业务代码
const createModuleIdFactory = require('./config/createModuleIdFactory');
const postProcessModulesFilter = require('./config/postProcessModulesFilter');
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: true,
inlineRequires: false,
},
}),
},
serializer: {
// 函数传入的是你要打包的module文件的绝对路径返回的是这个module的id
createModuleIdFactory: createModuleIdFactory,
// A filter function to discard specific modules from the output.
// 数传入的是module信息,返回是boolean值,若是是false就过滤不打包
// 配置processModuleFilter过滤基础包打出业务包
processModuleFilter: postProcessModulesFilter,
/* serializer options */
},
};
复制代码
config
目录新建 index.js
,用于设置打包配置index.js
:
// 基础打包配置
/** * version - js bundle版本,初始值是1,每次更新请手动加1 * common - 公共基础包bundle * bundles - 业务模块基础包bundle * { "animate": true, // ios平台会使用, 当从A页面转场到B页面的时候,控制[self.navigationControllersetNavigationBarHidden: animated:];中的animate "statusBgColor": "#408EF5", //导航状态栏的背景色 "type": "push", // 进入rn的形式(2种, push和present) "source": "Login", // 用于拆包打包时的入口entry_file "moduleName": "platform", // 模块名称,和 AppRegistry.registerComponent('platform', () => App);一一对应 "bundleName": "platform.bundle" // 此模块打包出来的bundle名称, 生产环境使用 } */
module.exports = {
version: 1,
common: {
moduleName: 'platform',
bundleName: 'platform.bundle',
},
bundles: [
{
animate: false,
statusBgColor: '#408EF5',
type: 'push',
source: 'mine',
moduleName: 'rbd_mine',
bundleName: 'rbd_mine.bundle',
},
{
animate: true,
statusBgColor: '#ffffff',
type: 'push',
source: 'discover',
moduleName: 'rbd_discover',
bundleName: 'rbd_discover.bundle',
},
],
};
复制代码
node
脚本,进行打包,包含热更新(文件级别增量升级)注:<>
表示必选,[]
表示可选,-v 默认取 config/index.js 的 version
目前是文件级别增量升级,下一步是内容级别增量升级(diff-match-patch)
package.json
增长
"bin": {
"rn": "./scripts/command.js"
},
复制代码
在项目目录执行 npm link
,以后就能够用 rn
脚手架命令了:
rn pack <platform> <type> [-v version]
: 打包 android/ios 的 version 版本的 common/business 包,并压缩至 upload 目录rn patch <platform> [-v version]
: 打包 android/ios 的 version 版本的增量包,并压缩至 upload 目录rn hash <target> [-v version]
: target 能够是文件路径(此时无需-v 参数)、ios、android,获取文件指纹(md5)脚本内容有点多,请直接看代码吧: command
为了保证 jsbundle 版本和原生 native 版本保持同步,须要前端和原生人员共同维护一个配置文件,例如放到 firebase:
附:repo
===🧐🧐 文中不足,欢迎指正 🤪🤪===