前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React Native官方拆包之metro bundle

React Native官方拆包之metro bundle

作者头像
xiangzhihong
发布2022-11-30 15:02:56
1K0
发布2022-11-30 15:02:56
举报
文章被收录于专栏:向治洪向治洪

简介

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

快速入门

安装

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

代码语言:javascript
复制
npm install --save-dev metro metro-core

yarn方式的安装命令如下:

代码语言:javascript
复制
yarn add --dev metro metro-core

运行

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

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

代码语言:javascript
复制
const Metro = require('metro');

方法

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

runMetro(config)

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

代码语言:javascript
复制
'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也会调用它的第三个参数。这允许您将服务与现有服务集成,或者扩展一个新的服务。

代码语言:javascript
复制
const httpServer = http.createServer((req, res) => {
  metroBundlerServer.processRequest(req, res, () => {
    // Metro does not know how to handle the request.
  });
});

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

代码语言:javascript
复制
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,以此来完成最低限度的工作:

代码语言:javascript
复制
const babylon = require('@babel/parser');

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

  return {ast};
};

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

代码语言:javascript
复制
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)完成。例如:
代码语言:javascript
复制
` 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中,系统使用了一个排序机制来决定使用哪个缓存。为了检索缓存,我们从上到下遍历缓存,直到找到结果;为了保存缓存,我们同样遍历缓存,直到找到具有缓存的存储。

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

代码语言:javascript
复制
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, )

使用

编译文件:

代码语言:javascript
复制
const config = await Metro.loadConfig();

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

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

代码语言:javascript
复制
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时指定自定义文件,文件的格式为:

代码语言:javascript
复制
--config <path/to/config>

结构

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

代码语言:javascript
复制
module.exports = {
  resolver: {
    /* resolver options */
  },
  transformer: {
    /* transformer options */
  },
  serializer: {
    /* serializer options */
  },
  server: {
    /* server options */
  }

  /* general options */
};

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

Option

Type

Description

cacheStores

Array<CacheStore<TransformResult<>>

列出存储缓存的位置

cacheVersion

string

将使整个metro缓存生成一个键

projectRoot

string

项目的根文件

watchFolders

Array < string>

指定任何额外的监视文件夹

transformerPath

string

转换器路径

watch

boolean

是否监视所有文件

reporter

{update: () => void}

是否监视打包过程中的状态

resetCache

boolean

是否在启动构建时重置缓存

stickyWorkers

boolean

创建的worker是否应该基于文件名

maxWorkers

number

把序列化的包串联起来

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

Option

Type

Description

port

number

监听的端口

useGlobalHotkey

boolean

是否打开热更新快捷键,快捷键为CMD+R

enhanceMiddleware

(Middleware, Server) => Middleware

添加自定义中间件

enableVisualizer

boolean

启用metro-visualizer中间件

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

Option

Type

Description

asyncRequireModulePath

string

处理异步请求模块

babelTransformerPath

string

使用自定义babel转换器

dynamicDepsInPackages

string (throwAtRuntime or reject)

发现动态依赖的处理动作

enableBabelRCLookup

boolean (default: true)

是否使用.babelrc配置文件

enableBabelRuntime

boolean (default: true)

是否使用@babel/transform/runtime插件

enableBabelRuntime

boolean (default: true)

是否使用.babelrc配置文件

未完待续!!!!!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-12-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
    • 快速入门
      • 安装
      • 运行
      • 方法
      • 可用选项
      • URL与 bundle 请求
      • JavaScript transformer
    • 概念
      • Resolution
      • Transformation
      • Serialization
    • Modules
      • 构建
        • Plain bundle
        • Indexed RAM bundle
        • File RAM bundle
      • 缓存
        • 为什么要缓存
        • 缓存的请求与缓存
    • API
      • API
        • Methods
        • 使用
        • Reference
      • Metro配置
        • 结构
    相关产品与服务
    对象存储
    对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档