当前位置: 首页 > 工具软件 > Metro JS > 使用案例 >

React Native官方拆包之metro bundle

司马祖鹤
2023-12-01

简介

接上一篇《JSBundle拆包之原理篇》

快速入门

安装

安装metro-core依赖主要有两种方式:npm和yarn。npm安装的命令如下:

npm install --save-dev metro metro-core

yarn方式的安装命令如下:

yarn add --dev metro metro-core

运行

metro bundle支持使用CLI脚手架方式运行和通过程序的编程调用它来运行。

在程序中使用metro需要先导入它,导入的方式如下:

const Metro = require('metro');

方法

metro提供了很多有用的函数,这些函数包括:

runMetro(config)

此方法用于给定配置,请求特定的服务。此时,您可以使用processRequest方法来hook HTTP(S)请求。例如:

'use strict';

const http = require('http');
const Metro = require('metro');

// We first load the config from the file system
Metro.loadConfig().then(config => {
  const metroBundlerServer = Metro.runMetro(config);

  const httpServer = http.createServer(
    metroBundlerServer.processRequest.bind(metroBundlerServer),
  );

  httpServer.listen(8081);
});

为了与Express apps兼容,当请求无法被Metro bundler处理时,processRequest也会调用它的第三个参数。这允许您将服务与现有服务集成,或者扩展一个新的服务。

const httpServer = http.createServer((req, res) => {
  metroBundlerServer.processRequest(req, res, () => {
    // Metro does not know how to handle the request.
  });
});

如果您正在使用Express,可以将processRequest作为中间件进行使用。

const express = require('express');
const app = express();

app.use(
  metroBundlerServer.processRequest.bind(metroBundlerServer),
);

app.listen(8081);

runServer(Config, Options)

runServer基于给定的配置和选项启动开发服务,并返回服务。我们建议使用runMetro而不是runServer。runServer可选的参数有:

  • host (string):服务的host驻留地址。
  • onReady (Function):在服务已经准备好时服务请求时被调用。
  • secure (boolean):服务是否需要运行在https上,而不是http上。
  • secureKey (string):使用https访问时secureKey。
  • secureCert (string):用于https访问的安全证书。
  • hmrEnabled (boolean):是否开启热更新功能。

runBuild(Config, Options)

此函数用于,给定一个配置和一组通常传递给服务器的选项,以及一组特定于包本身的选项,并用于构建一个包。runBuild支持的选项有:

  • dev (boolean):构建一个开发版本。例如,process.env.NODE_ENV = ‘development’。
  • entry (string):指向要绑定的条目文件。
  • onBegin (Function):绑定开始时被调用。
  • onComplete (Function): 绑定完成后调用。
  • onProgress (Function):在包期间调用,每次有关于模块计数/进度的新信息时被调用。
  • minify (boolean): 是否缩小bundle。
  • out (string):输出包的路径。
  • platform (‘web’ | ‘android’ | ‘ios’): 指定打包的平台。
  • sourceMap (boolean):是否生成源映射。
  • sourceMapUrl (string): 源映射的URL匹配,它默认为与包相同的URL,只是将扩展名从.bundle更改为.map。

可用选项

有关配置选项的详细信息,可用参考下面的连接:Configuring Metro

URL与 bundle 请求

Assets

为了获取Assets资源,您可以使用require方法来获取一个js文件,服务器将根据特定的require请求返回js文件的路径。当请求Assets资源时通常会原样返回。

除此之外,服务器还可以根据平台和请求的大小返回特定的Assets资源。指定平台的方法是通过点后缀(例如.ios)和at后缀(例如@2x)方式来进行的。

Bundle

任何js文件都可以作为bundle来请求根文件,这个文件将被看作是项目的根目录,根目录将包含所有递归在内的文件。为了请求bundle包,只需将扩展名从.js更改为.bundle即可。构建包的选项有:

  • dev: 是否以开发模式来构建包。
  • platform: 平台请求包,可以是ios或android。
  • minify: 代码是否应该缩小。
  • excludeSource: 源码是否应该包含在源映射中。

例如,请求http://localhost:8081/foo/bar/baz.bundle?dev=true&platform=ios将创建一个foo/bar/baz包,js为iOS开发模式。

Source maps

通过使用与包相同的URL为每个包构建源映射,只有当inlineSourceMap设置为false时才会工作。您传递给包的所有选项将被添加到源映射URL;否则,它们就不匹配。

JavaScript transformer

JavaScript transformer被用来进行JS代码转换,适用于访问Babel。这个transformer可以导出两种方法:

transform(module)

此方法主要用于转换代码。接收到的对象将会被转换为包含一个ast键代码。默认的转换器仅能完成将代码解析为AST,以此来完成最低限度的工作:

const babylon = require('@babel/parser');

module.exports.transform = (file: {filename: string, src: string}) => {
  const ast = babylon.parse(code, {sourceType: 'module'});

  return {ast};
};

如果您想要使用babel插件,您可以通过将代码传递给它来实现:

const {transformSync} = require('@babel/core');

module.exports.transform = file => {
  return transformSync(file.src, {
    // Babel options...
  });
};

getCacheKey()

此方法用于返回转换器缓存。当使用不同的转换器时,这允许正确地将转换后的文件绑定到转换它的转换器,且方法的结果必须是一个字符串。

概念

Metro是一个JavaScript的打包工具。它接收选项、一个条目文件,返回一个包含所有JavaScript的文件。Metro绑定程序主要涉及三个阶段:

  1. Resolution
  2. Transformation
  3. Serialization

Resolution

Metro需要从入口点构建所需的所有模块的图,要从另一个文件中找到所需的文件,需要使用Metro解析器。在现实开发中,这个阶段与Transformation阶段是并行的。

Transformation

所有模块都要经过Transformation阶段,Transformation负责将模块转换成目标平台可以理解的格式(如React Naitve)。模块的转换是基于拥有的核心数量来进行的。

Serialization

所有模块一经转换就会被序列化,Serialization会组合这些模块来生成一个或多个包,包就是将模块组合成一个JavaScript文件的包。

Modules

Metro被划分为多个模块,每个模块对应于流程中的每个步骤,每个模块都有自己的职责。这意味着我们每个模块可以根据您的需要进行交换。

构建

绑定时,每个模块都会被分配一个数字id,这意味着不支持动态需求。require通过数字版本更改、模块以不同的格式存储。支持三种不同的捆绑形式:

Plain bundle

这是一种标准的打包方式,在这种方式中,所有文件都用函数调用包装,然后添加到全局文件中,这对于只需要JS包(例如浏览器)的环境非常有用。只需要具有.bundle扩展名的入口点就可以完成它的构建。

Indexed RAM bundle

这种打包方式会将包打包成二进制文件,其格式包括以下部分:

  • 一组数字:用于验证文件。uint32必须位于文件的开头,值为0xFB0BD1E5。
  • 偏移表:该表是一个由32对uint32对组成的序列,带有一个表头。
  • 其他子模块,由一个空字节(\0)完成。例如:
` 0                   1                   2                   3                   4                   5                   6
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          Magic number                         |                          Header size                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Startup code size                       |                        Module 0 offset                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Module 0 length                        |                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                                                               +
|                                                                                                                               |
+                                                              ...                                                              +
|                                                                                                                               |
+                                                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |                        Module n offset                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Module n length                        | Module 0 code | Module 0 code |      ...      |       \0      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Module 1 code | Module 1 code |      ...      |       \0      |                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                                                               +
|                                                                                                                               |
+                                                              ...                                                              +
|                                                                                                                               |
+                                                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               | Module n code | Module n code |      ...      |       \0      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+`

这种结构对于同时加载内存中所有代码的环境来说是最优的:

  • 通过使用偏移表,可以在固定的时间内加载任何模块,其中模块x的代码位于文件[(x + 3) * sizeof(uint32)]。由于有一个空字符(\0)分隔所有模块,通常不需要使用长度,模块可以直接作为ASCIIZ字符串加载。
  • 启动代码总是可以在文件[sizeof(uint32)]中找到。

Indexed RAM bundle通常被用于iOS分包。

File RAM bundle

每个模块都会被存储为一个文件,例如,名称为js-modules/${id},创建了一个名为UNBUNDLE的额外文件,它唯一的内容是一个数字0xFB0BD1E5。注意,解包文件是在根目录下创建的。
Android通常使用这种方式分包,因为包内容是压缩的,而且访问压缩文件要快得多。如果使用索引方式(Indexed RAM bundle),则应立即解压缩所有绑定,以获取对应模块的代码。

缓存

Metro具有多层缓存,您可以设置多个缓存供Metro使用,而不是一个缓存。下面来看看Motro的多层缓存是如何工作的。

为什么要缓存

缓存提供了很大的性能优势,它们可以将打包的速度提高十倍以上。然而,许多系统使用的是非持久缓存。对于Metro来说,我们有一种更复杂的层系统缓存方式。例如,我们可以在服务器上存储缓存,这样,连接到同一服务器的所有打包都可以使用共享缓存。因此,CI服务器和本地开发的初始构建时间显著降低。

我们希望将缓存存储在多个位置,以便缓存可以执行回退操作。这就是为什么有一个多层缓存系统。

缓存的请求与缓存

在Metro中,系统使用了一个排序机制来决定使用哪个缓存。为了检索缓存,我们从上到下遍历缓存,直到找到结果;为了保存缓存,我们同样遍历缓存,直到找到具有缓存的存储。

假设您有两个缓存存储:一个在服务器上,另一个在本地文件系统上。那么,你可以这样指定:

const config = {
  cacheStores: [
    new FileStore({/*opts*/}),
    new NetworkStore({/*opts*/})
  ]
}

当我们检索缓存时,Metro将首先查看本地文件存储,如果不能找到缓存,它将检查NetworkStore。最后,如果没有缓存,它将生成一个新的缓存。一旦缓存生成,Metro将再次从上到下在所有存储中存储缓存。如果找到缓存,也会进行存储。例如,如果Metro在NetworkStore中找到缓存,它也会将其存储在FileStore中。

API

API

Methods

metro提供了如下一些方法:

  • loadConfig()
  • async runMetro(config)
  • async runBuild(config, )
  • async runServer(config, )
  • createConnectMiddleware(config, )

使用

编译文件:

const config = await Metro.loadConfig();

await Metro.runBuild(config, {
  entry: 'index.js',
  out: 'bundle.js',
});

运行服务并监视文件系统的更改:

const config = await Metro.loadConfig();

await Metro.runServer(config, {
  port: 8080,
});

Reference

下面公开的所有函数都会接受一个附加的配置选项,即metro.config.js,如意要使用它,你可以使用Metro.loadConfig来获得它。

loadConfig()

Basic options: config, cwd
加载Metro配置,如果指定,可以从选项中的config加载,也可以从cwd到根目录遍历直到找到一个文件(默认metro.config.js)。返回的配置将与Metro的默认值合并。

async runMetro(config)

基于配置创建一个Metro服务器并返回它,您可以将其用作现有服务器中的中间件。

async runBuild(config, )

绑定给定平台的条目,并将其保存到外部位置。如果设置了sourceMap,还会生成一个源映射。源映射将被内联,除非还定义了sourceMapUrl。在后一种情况下,将使用sourceMapUrl参数的basename生成一个新文件。

async runServer(config, )

启动一个完整的Metro HTTP服务,它将侦听指定的host:port,然后可以查询它以检索各种入口点的包。如果提供了安全系列选项,服务器将通过HTTPS公开。如果设置了hmrEnabled,服务器还将公开websocket服务器内容,并将HMR客户机注入生成的包中。

createConnectMiddleware(config, )

与其创建完整的服务器不同,此函数用于创建一个连接中间件来响应包请求。然后可以将此中间件插入您自己的服务器,端口参数是可选的,仅用于日志记录。

Metro配置

Metro配置可以通过以下三种方式创建:

  • metro.config.js
  • metro.config.json
  • The metro field in package.json

您还可以通过调用CLI时指定自定义文件,文件的格式为:

--config <path/to/config>

结构

每个模块都有一个单独的配置选项,Metro中常见的配置结构如下:

module.exports = {
  resolver: {
    /* resolver options */
  },
  transformer: {
    /* transformer options */
  },
  serializer: {
    /* serializer options */
  },
  server: {
    /* server options */
  }

  /* general options */
};

可用的选项参数可以参考下面的链接:General Options

OptionTypeDescription
cacheStoresArray<CacheStore<TransformResult<>>列出存储缓存的位置
cacheVersionstring将使整个metro缓存生成一个键
projectRootstring项目的根文件
watchFoldersArray < string>指定任何额外的监视文件夹
transformerPathstring转换器路径
watchboolean是否监视所有文件
reporter{update: () => void}是否监视打包过程中的状态
resetCacheboolean是否在启动构建时重置缓存
stickyWorkersboolean创建的worker是否应该基于文件名
maxWorkersnumber把序列化的包串联起来

服务器选项可以参考下面的链接:Server Options

OptionTypeDescription
portnumber监听的端口
useGlobalHotkeyboolean是否打开热更新快捷键,快捷键为CMD+R
enhanceMiddleware(Middleware, Server) => Middleware添加自定义中间件
enableVisualizerboolean启用metro-visualizer中间件

Metro的转化器选项如下:Transformer Options

OptionTypeDescription
asyncRequireModulePathstring处理异步请求模块
babelTransformerPathstring使用自定义babel转换器
dynamicDepsInPackagesstring (throwAtRuntime or reject)发现动态依赖的处理动作
enableBabelRCLookupboolean (default: true)是否使用.babelrc配置文件
enableBabelRuntimeboolean (default: true)是否使用@babel/transform/runtime插件
enableBabelRuntimeboolean (default: true)是否使用.babelrc配置文件

未完待续!!!!!

 类似资料: