React-Native实现多页面多bundle打包配置

2,507 阅读4分钟

前言

最近有一个需求希望一个页面可以提供给原生应用加载,同时也支持打包成web资源。为了保证在该页面在原生端有类原生的效果,避免出现渲染白屏的情况,我最终采用了react-native来实现。原生端集成js bundle文件,react-nativesdk可以将js解析成原生组件,从而提高了页面渲染性能。当页面过多,打出来的包过大的时候就会涉及到拆包。这篇文章主要是记录一个react-native项目创建和拆包的配置。大家如果有更好的实现方式也欢迎在评论区讨论呀!

首先是创建一个react-native工程,可以直接参考网上的创建方式。我这边是利用expo创建的一个react-nativeexpo 是一个用于构建 React Native 应用的开发工具集,它提供了一整套工具和服务,帮助开发者更轻松地创建、部署和管理 React Native 应用。这样创建出来的工程启动后,可以直接下载expo手机app预览调试,非常方便。同时expo创建的脚手架内置了react-native-web。我们也可以直接生成web页面。同时也能打包web代码。expo还有很多玩法,目前还没研究透,大家可以在官网进行学习

web多入口配置

react-native支持使用npx expo export:web 打包成web的静态资源,同时也支持webpack配置。所以可以直接使用webpack的配置来实现多入口打包。

我们找到项目根目录下的webpack.config.js文件,添加对应的配置

image.png

我这边因为想要每个页面都是单独的模块,能直接单独部署运行,所以利用了fileManagerPlugin插件进行了文件移动删除压缩等处理。同时也利用splitChunks进行了公共包的打包处理。大家可以根据webpack配置在expo脚手架工程中自由改造(直接修改config中的内容即可),可能在配置过程中,与我们之前web项目的webpack配置有所区别,大家可以开启调试模式,打印出config的内容,然后进行更改,可以添加需要的pluginrules,打包后的资源如下:

image.png

打包成多bundle

在前面我们说过,我们会把react-native打包成bundle文件,提供给原生app集成。集成方式可以参考官网bundle文件打包(以安卓为例)主要是执行以下指令。指定入口文件,平台已经打包后的资源文件存放路径。

react-native bundle --entry-file App.js --platform android --dev false --bundle-output android/index.android.bundle --assets-dest android

该指令只能指定单入口打包,如果要多入口,需要进行改造。同时如果多个文件共用了一些公共资源,也可以将公共资源打包成一个文件,原生app就不需要重复集成。

多入口实现:

主要思路是根据不同的js入口文件进行循环打包处理,首先是创建一个js文件,在js中循环执行打包指令

image.png

根据不同的入口名称设置不同的打包资源存放路径,注意:bundle指令在打包前不会自动创建对应的目标文件夹,所以我们做一些文件创建和删除处理,用于清理上一次打包的资源文件。打包前如果没有创建对应的文件夹,打包会报错。

公共包拆包:

细心的朋友应该看到了上图的打包指令中有一个metro.business.config.js文件。这个文件就是我们可以用来拆包处理的。首先在根目录下创建common.js文件,该文件主要引用一些公共资源包,示例如下:

require('react');
require('react-native');

接着在根目录下创建 metro.common.config.js文件,用于打包公共模块,代码示例如下:

const fs = require('fs');

function getModuleId(path) {
  // 根据文件的相对路径构建 moduleId
  const projectRootPath = __dirname;
  let moduleId = path.substr(projectRootPath.length + 1);
  return moduleId;
}

function createModuleIdFactory() {
    return getModuleId
}

function processModuleFilter(module) {
    const moduleId = getModuleId(module['path'])
    // 把 moduleId 写入 moduleIdList.txt 文件,记录基础模块的moduleId
    fs.appendFileSync('./moduleIdList.txt', `${moduleId}\n`);
    return true
}

module.exports = {
  serializer: {
    createModuleIdFactory,
    processModuleFilter,
  },
};

接着执行公共包打包指令(以安卓为例)

react-native bundle --platform android --config metro.common.config.js --dev false --entry-file common.js --bundle-output android/common.android.bundle

执行完之后,会在根目录下生成moduleIdList.txt文件,里面包含了一些基础模块的moduleId,示例如下:

image.png

接着创建业务打包配置文件metro.business.config.js,代码示例如下:

const fs = require('fs');

// 读取 moduleIdList.txt,转换为数组
const moduleIdList = fs.readFileSync('./moduleIdList.txt', 'utf8').toString().split('\n');

function getModuleId(path) {
    // 根据文件的相对路径构建 moduleId
    const projectRootPath = __dirname;
    let moduleId = path.substr(projectRootPath.length + 1);
    return moduleId;
}

function createModuleIdFactory() {
    return getModuleId
}

function processModuleFilter(module) {
    const moduleId = getModuleId(module['path'])
     if (moduleIdList.indexOf(moduleId) >= 0) {
         // 过滤掉上一步记录的基础模块的moduleId
         return false;
     }
    return true;
}

module.exports = {
    serializer: {
        createModuleIdFactory,
        processModuleFilter,
    }
}

这样执行上面的多入口业务包的时候会过滤掉基础模块。打包出来的资源如下图:

image.png

总结

这样我们就实现了一套react-native资源包的一整个灵活打包处理,因为是刚接触react-native,对里面的配置和实现还没有完全学习到位,或许还有更好的实现方式。欢迎大佬来指正。