接上一篇《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提供了很多有用的函数,这些函数包括:
此方法用于给定配置,请求特定的服务。此时,您可以使用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基于给定的配置和选项启动开发服务,并返回服务。我们建议使用runMetro而不是runServer。runServer可选的参数有:
此函数用于,给定一个配置和一组通常传递给服务器的选项,以及一组特定于包本身的选项,并用于构建一个包。runBuild支持的选项有:
有关配置选项的详细信息,可用参考下面的连接:Configuring Metro
为了获取Assets资源,您可以使用require方法来获取一个js文件,服务器将根据特定的require请求返回js文件的路径。当请求Assets资源时通常会原样返回。
除此之外,服务器还可以根据平台和请求的大小返回特定的Assets资源。指定平台的方法是通过点后缀(例如.ios)和at后缀(例如@2x)方式来进行的。
任何js文件都可以作为bundle来请求根文件,这个文件将被看作是项目的根目录,根目录将包含所有递归在内的文件。为了请求bundle包,只需将扩展名从.js更改为.bundle即可。构建包的选项有:
例如,请求http://localhost:8081/foo/bar/baz.bundle?dev=true&platform=ios将创建一个foo/bar/baz包,js为iOS开发模式。
通过使用与包相同的URL为每个包构建源映射,只有当inlineSourceMap设置为false时才会工作。您传递给包的所有选项将被添加到源映射URL;否则,它们就不匹配。
JavaScript transformer被用来进行JS代码转换,适用于访问Babel。这个transformer可以导出两种方法:
此方法主要用于转换代码。接收到的对象将会被转换为包含一个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...
});
};
此方法用于返回转换器缓存。当使用不同的转换器时,这允许正确地将转换后的文件绑定到转换它的转换器,且方法的结果必须是一个字符串。
Metro是一个JavaScript的打包工具。它接收选项、一个条目文件,返回一个包含所有JavaScript的文件。Metro绑定程序主要涉及三个阶段:
Metro需要从入口点构建所需的所有模块的图,要从另一个文件中找到所需的文件,需要使用Metro解析器。在现实开发中,这个阶段与Transformation阶段是并行的。
所有模块都要经过Transformation阶段,Transformation负责将模块转换成目标平台可以理解的格式(如React Naitve)。模块的转换是基于拥有的核心数量来进行的。
所有模块一经转换就会被序列化,Serialization会组合这些模块来生成一个或多个包,包就是将模块组合成一个JavaScript文件的包。
Metro被划分为多个模块,每个模块对应于流程中的每个步骤,每个模块都有自己的职责。这意味着我们每个模块可以根据您的需要进行交换。
绑定时,每个模块都会被分配一个数字id,这意味着不支持动态需求。require通过数字版本更改、模块以不同的格式存储。支持三种不同的捆绑形式:
这是一种标准的打包方式,在这种方式中,所有文件都用函数调用包装,然后添加到全局文件中,这对于只需要JS包(例如浏览器)的环境非常有用。只需要具有.bundle扩展名的入口点就可以完成它的构建。
这种打包方式会将包打包成二进制文件,其格式包括以下部分:
` 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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+`
这种结构对于同时加载内存中所有代码的环境来说是最优的:
Indexed RAM bundle通常被用于iOS分包。
每个模块都会被存储为一个文件,例如,名称为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中。
metro提供了如下一些方法:
编译文件:
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,
});
下面公开的所有函数都会接受一个附加的配置选项,即metro.config.js,如意要使用它,你可以使用Metro.loadConfig来获得它。
Basic options: config, cwd
加载Metro配置,如果指定,可以从选项中的config加载,也可以从cwd到根目录遍历直到找到一个文件(默认metro.config.js)。返回的配置将与Metro的默认值合并。
基于配置创建一个Metro服务器并返回它,您可以将其用作现有服务器中的中间件。
绑定给定平台的条目,并将其保存到外部位置。如果设置了sourceMap,还会生成一个源映射。源映射将被内联,除非还定义了sourceMapUrl。在后一种情况下,将使用sourceMapUrl参数的basename生成一个新文件。
启动一个完整的Metro HTTP服务,它将侦听指定的host:port,然后可以查询它以检索各种入口点的包。如果提供了安全系列选项,服务器将通过HTTPS公开。如果设置了hmrEnabled,服务器还将公开websocket服务器内容,并将HMR客户机注入生成的包中。
与其创建完整的服务器不同,此函数用于创建一个连接中间件来响应包请求。然后可以将此中间件插入您自己的服务器,端口参数是可选的,仅用于日志记录。
Metro配置可以通过以下三种方式创建:
您还可以通过调用CLI时指定自定义文件,文件的格式为:
--config <path/to/config>
每个模块都有一个单独的配置选项,Metro中常见的配置结构如下:
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配置文件 |
未完待续!!!!!