本文涉及 react-native及 metro 版本
react-native@0.63.2metro@0.58.0
先來看一波本文的實(shí)例代碼:很簡單吧,一個(gè)你好,世界
// App.js
import React from "react";
import { StyleSheet, Text, View } from "react-native";
export default class App extends React.Component {
render() {
return (
<React.Fragment>
<View style={styles.body}>
<Text style={styles.text}>你好,世界</Text>
</View>
</React.Fragment>
);
}
}
const styles = StyleSheet.create({
body: {
backgroundColor: "white",
flex: 1,
justifyContent: "center",
alignItems: "center",
},
text: {
textAlign: "center",
color: "red",
},
});
一、前言
眾所周知,
react-native(下文簡稱rn)需要打成bundle包供android,ios加載;通常我們的打包命令為react-native bundle --entry-file index.js --bundle-output ./bundle/ios.bundle --platform ios --assets-dest ./bundle --dev false;運(yùn)行上述命令之后,rn 會(huì)默認(rèn)使用metro作為打包工具,生成bundle包。
生成的 bundle 包大致分為四層:
- var 聲明層: 對當(dāng)前運(yùn)行環(huán)境, bundle 啟動(dòng)時(shí)間,以及進(jìn)程相關(guān)信息;
-
polyfill 層:
!(function(r){}), 定義了對define(__d)、require(__r)、clear(__c)的支持,以及 module(react-native 及第三方 dependences 依賴的 module) 的加載邏輯; - 模塊定義層: __d 定義的代碼塊,包括 RN 框架源碼 js 部分、自定義 js 代碼部分、圖片資源信息,供 require 引入使用
- require 層: r 定義的代碼塊,找到 d 定義的代碼塊 并執(zhí)行
格式如下:
// var聲明層
var __BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now(),__DEV__=false,process=this.process||{};process.env=process.env||{};process.env.NODE_ENV=process.env.NODE_ENV||"production";
//polyfill層
!(function(r){"use strict";r.__r=o,r.__d=function(r,i,n){if(null!=e[i])return;var o={dependencyMap:n,factory:r,hasError:!1,importedAll:t,importedDefault:t,isInitialized:!1,publicModule:{exports:{}}};e[i]=o}
...
// 模型定義層
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=r(d[1]),o=n(r(d[2])),u=r(d[3]);t.AppRegistry.registerComponent(u.name,function(){return o.default})},0,[1,2,402,403]);
....
__d(function(a,e,t,i,R,S,c){R.exports={name:"ReactNativeSSR",displayName:"ReactNativeSSR"}},403,[]);
// require層
__r(93);
__r(0);
看完上面的代碼不知你是否疑問?
var定義層和polyfill的代碼是在什么時(shí)機(jī)生成的?-
我們知道
_d()有三個(gè)參數(shù),分別是對應(yīng)factory函數(shù),當(dāng)前moduleId以及module依賴關(guān)系-
metro使用什么去做整個(gè)工程的依賴分析? -
moduleId如何生成?
-
metro如何打包?
日常開發(fā)中我們可能并么有在意,整個(gè) rn 打包邏輯;現(xiàn)在就讓筆者帶您走入 rn 打包的世界!
二、metro 打包流程
通過翻閱源碼和 Metro 官網(wǎng),我們知道 metro 打包的整個(gè)流程大致分為:
命令參數(shù)解析metro 打包服務(wù)啟動(dòng)-
打包 js 和資源文件- 解析,轉(zhuǎn)化和生成
停止打包服務(wù)

1. 命令參數(shù)解析
首先我們來看看 react-native bundle的實(shí)現(xiàn)以及參數(shù)如何解析;由于 bundle 是 react-native 的一個(gè)子命令,那么我們尋找的思路可以從 react-native 包入手;其文件路徑如下
// node_modules/react-native/local-cli/cli.js
// react-native 命令入口
var cli = require('@react-native-community/cli');
if (require.main === module) {
cli.run();
}
// node_modules/react-native/node_modules/@react-native-community/cli/build/index.js
run() -> setupAndRun() -> var _commands = require("./commands");
// 在node_modules/react-native/node_modules/@react-native-community/cli/build/commands/index.js 中注冊了 react-native的所有命令
var _start = _interopRequireDefault(require("./start/start"));
var _bundle = _interopRequireDefault(require("./bundle/bundle"));
var _ramBundle = _interopRequireDefault(require("./bundle/ramBundle"));
var _link = _interopRequireDefault(require("./link/link"));
var _unlink = _interopRequireDefault(require("./link/unlink"));
var _install = _interopRequireDefault(require("./install/install"));
var _uninstall = _interopRequireDefault(require("./install/uninstall"));
var _upgrade = _interopRequireDefault(require("./upgrade/upgrade"));
var _info = _interopRequireDefault(require("./info/info"));
var _config = _interopRequireDefault(require("./config/config"));
var _init = _interopRequireDefault(require("./init"));
var _doctor = _interopRequireDefault(require("./doctor"));
由于本文主要分析 react-native 打包流程,所以只需查看react-native/node_modules/@react-native-community/cli/build/commands/bundle/bundle.js即可。
在 bundle.js 文件中主要注冊了 bundle 命令,但是具體的實(shí)現(xiàn)卻使用了buildBundle.js.
// node_modules/react-native/node_modules/@react-native-community/cli/build/commands/bundle/bundle.js
var _buildBundle = _interopRequireDefault(require("./buildBundle"));
var _bundleCommandLineArgs = _interopRequireDefault(
require("./bundleCommandLineArgs")
);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
function bundleWithOutput(_, config, args, output) {
// bundle打包的具體實(shí)現(xiàn)
return (0, _buildBundle.default)(args, config, output);
}
var _default = {
name: "bundle",
description: "builds the javascript bundle for offline use",
func: bundleWithOutput,
options: _bundleCommandLineArgs.default,
// Used by `ramBundle.js`
withOutput: bundleWithOutput,
};
exports.default = _default;
const withOutput = bundleWithOutput;
exports.withOutput = withOutput;
2. Metro Server 啟動(dòng)
在node_modules/react-native/node_modules/@react-native-community/cli/build/commands/bundle/buildBundle.js文件中默認(rèn)導(dǎo)出的 buildBundle 方法才是整個(gè)react-native bundle執(zhí)行的入口。在入口中主要做了如下幾件事情:
- 合并 metro 默認(rèn)配置和自定義配置,并設(shè)置 maxWorkers,resetCache
- 根據(jù)解析得到參數(shù),構(gòu)建 requestOptions,傳遞給打包函數(shù)
- 實(shí)例化 metro Server
- 啟動(dòng) metro 構(gòu)建 bundle
- 處理資源文件,解析
- 關(guān)閉 Metro Server
// node_modules/react-native/node_modules/@react-native-community/cli/build/commands/bundle/buildBundle.js
// metro打包服務(wù),也是metro的核心
function _Server() {
const data = _interopRequireDefault(require("metro/src/Server"));
_Server = function() {
return data;
};
return data;
}
function _bundle() {
const data = _interopRequireDefault(
require("metro/src/shared/output/bundle")
);
_bundle = function() {
return data;
};
return data;
}
// 保存資源文件
var _saveAssets = _interopRequireDefault(require("./saveAssets"));
// 提供了metro的默認(rèn)配置
var _loadMetroConfig = _interopRequireDefault(
require("../../tools/loadMetroConfig")
);
async function buildBundle(args, ctx, output = _bundle().default) {
// 合并metro默認(rèn)配置和自定義配置,并設(shè)置maxWorkers,resetCache
const config = await (0, _loadMetroConfig.default)(ctx, {
maxWorkers: args.maxWorkers,
resetCache: args.resetCache,
config: args.config,
});
// ...
process.env.NODE_ENV = args.dev ? "development" : "production";
// 根據(jù)命令行的入?yún)?--sourcemap-output 構(gòu)建 sourceMapUrl
let sourceMapUrl = args.sourcemapOutput;
if (sourceMapUrl && !args.sourcemapUseAbsolutePath) {
sourceMapUrl = _path().default.basename(sourceMapUrl);
}
// 根據(jù)解析得到參數(shù),構(gòu)建requestOptions,傳遞給打包函數(shù)
const requestOpts = {
entryFile: args.entryFile,
sourceMapUrl,
dev: args.dev,
minify: args.minify !== undefined ? args.minify : !args.dev,
platform: args.platform,
};
// 實(shí)例化metro 服務(wù)
const server = new (_Server()).default(config);
try {
// 啟動(dòng)打包, what? 作者不是說的是Server打包嗎?為什么是output? 答:下面會(huì)講解
const bundle = await output.build(server, requestOpts);
// 將打包生成的bundle保存到對應(yīng)的目錄
await output.save(bundle, args, _cliTools().logger.info); // Save the assets of the bundle
// 處理資源文件,解析,并在下一步保存在--assets-dest指定的位置
const outputAssets = await server.getAssets({
..._Server().default.DEFAULT_BUNDLE_OPTIONS,
...requestOpts,
bundleType: "todo",
}); // When we're done saving bundle output and the assets, we're done.
// 保存資源文件到指定目錄
return await (0, _saveAssets.default)(
outputAssets,
args.platform,
args.assetsDest
);
} finally {
// 停止metro 打包服務(wù)
server.end();
}
}
var _default = buildBundle;
exports.default = _default;
從上述代碼可以看到具體的打包實(shí)現(xiàn)都在output.build(server, requestOpts)中,output是outputBundle類型,這部分代碼在 Metro JS` 中,具體的路徑為:node_modules/metro/src/shared/output/bundle.js
// node_modules/metro/src/shared/output/bundle.js
function buildBundle(packagerClient, requestOptions) {
return packagerClient.build(
_objectSpread({}, Server.DEFAULT_BUNDLE_OPTIONS, requestOptions, {
bundleType: "bundle",
})
);
}
exports.build = buildBundle;
exports.save = saveBundleAndMap;
exports.formatName = "bundle";
可以看到雖說使用的output.build(server, requestOpts)進(jìn)行打包,其實(shí)是使用傳入的packagerClient.build進(jìn)行打包。而packagerClient是我們剛傳入的Server。而Server就是下面我們要分析打包流程。其源碼位置為:node_modules/metro/src/Server.js
metro 構(gòu)建 bundle: 流程入口
通過上面的分析,我們已經(jīng)知曉整個(gè)react-native bundle 打包服務(wù)的啟動(dòng)在node_modules/metro/src/Server.js的build方法中:
class Server {
// 構(gòu)建函數(shù),初始化屬性
constructor(config, options) {
var _this = this;
this._config = config;
this._createModuleId = config.serializer.createModuleIdFactory();
this._bundler = new IncrementalBundler(config, {
watch: options ? options.watch : undefined,
});
this._nextBundleBuildID = 1;
}
build(options) {
var _this2 = this;
return _asyncToGenerator(function*() {
// 將傳遞進(jìn)來的參數(shù),按照模塊進(jìn)行拆分,一遍更好的管理;其拆分的格式如下:
// {
// entryFile: options.entryFile,
// transformOptions: {
// customTransformOptions: options.customTransformOptions,
// dev: options.dev,
// hot: options.hot,
// minify: options.minify,
// platform: options.platform,
// type: "module"
// },
// serializerOptions: {
// excludeSource: options.excludeSource,
// inlineSourceMap: options.inlineSourceMap,
// modulesOnly: options.modulesOnly,
// runModule: options.runModule,
// sourceMapUrl: options.sourceMapUrl,
// sourceUrl: options.sourceUrl
// },
// graphOptions: {
// shallow: options.shallow
// },
// onProgress: options.onProgress
// }
const _splitBundleOptions = splitBundleOptions(options),
entryFile = _splitBundleOptions.entryFile,
graphOptions = _splitBundleOptions.graphOptions,
onProgress = _splitBundleOptions.onProgress,
serializerOptions = _splitBundleOptions.serializerOptions,
transformOptions = _splitBundleOptions.transformOptions;
// metro打包核心:解析(Resolution)和轉(zhuǎn)換(Transformation)
const _ref13 = yield _this2._bundler.buildGraph(
entryFile,
transformOptions,
{
onProgress,
shallow: graphOptions.shallow,
}
),
prepend = _ref13.prepend,
graph = _ref13.graph;
// 獲取構(gòu)建入口文件路徑
const entryPoint = path.resolve(_this2._config.projectRoot, entryFile);
// 初始化構(gòu)建參數(shù),此處的參數(shù)來源于: 命令行 && 自定義metro配置metro.config.js && 默認(rèn)的metro配置
const bundleOptions = {
asyncRequireModulePath:
_this2._config.transformer.asyncRequireModulePath,
processModuleFilter: _this2._config.serializer.processModuleFilter,
createModuleId: _this2._createModuleId, // 里面自定義/默認(rèn)的createModuleIdFactory給每個(gè)module生成id; 其默認(rèn)生成規(guī)則詳情請見: node_modules/metro/src/lib/createModuleIdFactory.js
getRunModuleStatement: _this2._config.serializer.getRunModuleStatement, // 給方法簽名
// 默認(rèn)值為 getRunModuleStatement: moduleId => `__r(${JSON.stringify(moduleId)});`,
// 詳情請見: node_modules/metro-config/src/defaults/index.js
dev: transformOptions.dev,
projectRoot: _this2._config.projectRoot,
modulesOnly: serializerOptions.modulesOnly,
runBeforeMainModule: _this2._config.serializer.getModulesRunBeforeMainModule(
path.relative(_this2._config.projectRoot, entryPoint)
), // 指定在主模塊前運(yùn)行的模塊, 默認(rèn)值: getModulesRunBeforeMainModule: () => []
// 詳情請見: node_modules/metro-config/src/defaults/index.js
runModule: serializerOptions.runModule,
sourceMapUrl: serializerOptions.sourceMapUrl,
sourceUrl: serializerOptions.sourceUrl,
inlineSourceMap: serializerOptions.inlineSourceMap,
};
let bundleCode = null;
let bundleMap = null;
// 是否使用自定義生成,如果是,則調(diào)用自定義生成的函數(shù),獲取最終代碼
if (_this2._config.serializer.customSerializer) {
const bundle = _this2._config.serializer.customSerializer(
entryPoint,
prepend,
graph,
bundleOptions
);
if (typeof bundle === "string") {
bundleCode = bundle;
} else {
bundleCode = bundle.code;
bundleMap = bundle.map;
}
} else {
// 此處筆者將其拆分成兩個(gè)步驟,比較容易分析
// 將解析及轉(zhuǎn)化之后的數(shù)據(jù),生成如下格式化的數(shù)據(jù)
// {
// pre: string, // var定義部分及poyfill部分的代碼
// post: string, // require部分代碼
// modules: [[number, string]], // 模塊定義部分,第一個(gè)參數(shù)為number,第二個(gè)參數(shù)為具體的代碼
// }
var base = baseJSBundle(entryPoint, prepend, graph, bundleOptions);
// 將js module進(jìn)行排序并進(jìn)行字符串拼接生成最終的代碼
bundleCode = bundleToString(base).code;
}
//
if (!bundleMap) {
bundleMap = sourceMapString(
_toConsumableArray(prepend).concat(
_toConsumableArray(_this2._getSortedModules(graph))
),
{
excludeSource: serializerOptions.excludeSource,
processModuleFilter: _this2._config.serializer.processModuleFilter,
}
);
}
return {
code: bundleCode,
map: bundleMap,
};
})();
}
}
在這個(gè) build 函數(shù)中,首先執(zhí)行了 buildGraph,而 this._bundler 的初始化發(fā)生在 Server 的 constructor 中。
this._bundler = new IncrementalBundler(config, {
watch: options ? options.watch : undefined,
});
此處的_bundler是 IncrementalBundler 的實(shí)例,它的 buildGraph 函數(shù)完成了打包過程中前兩步 Resolution 和 Transformation 。 下面我們就來詳細(xì)查看一下 Metro 解析,轉(zhuǎn)換過程。
metro 構(gòu)建 bundle: 解析和轉(zhuǎn)換
在上面一節(jié)我們知道 metro 使用IncrementalBundler進(jìn)行 js 代碼的解析和轉(zhuǎn)換,在 Metro 使用IncrementalBundler進(jìn)行解析轉(zhuǎn)換的主要作用是:
- 返回了以入口文件為入口的所有相關(guān)依賴文件的依賴圖譜和 babel 轉(zhuǎn)換后的代碼;
- 返回了var 定義部分及 polyfill 部分所有相關(guān)依賴文件的依賴圖譜和 babel 轉(zhuǎn)換后的代碼;
整體流程如圖所示:

通過上述的流程我們總結(jié)如下幾點(diǎn):
- 整個(gè) metro 進(jìn)行依賴分析和 babel 轉(zhuǎn)換主要通過了JestHasteMap 去做依賴分析;
- 在做依賴分析的通過,metro 會(huì)監(jiān)聽當(dāng)前目錄的文件變化,然后以最小變化生成最終依賴關(guān)系圖譜;
- 不管是入口文件解析還是 polyfill 文件的依賴解析都是使用了JestHasteMap ;
下面,我們來分析其具體過程如下:
// node_modules/metro/src/IncrementalBundler.js
buildGraph(entryFile, transformOptions) {
var _this2 = this;
let otherOptions =
arguments.length > 2 && arguments[2] !== undefined
? arguments[2]
: {
onProgress: null,
shallow: false
};
return _asyncToGenerator(function*() {
// 核心構(gòu)建在buildGraphForEntries中,通過入口文件進(jìn)行依賴解析,得到bundle require部分和模塊定義部分,其生成的格式為
// {
// dependencies: new Map(),
// entryPoints,
// importBundleNames: new Set()
// }
const graph = yield _this2.buildGraphForEntries(
[entryFile],
transformOptions,
otherOptions
);
const transformOptionsWithoutType = {
customTransformOptions: transformOptions.customTransformOptions,
dev: transformOptions.dev,
experimentalImportSupport: transformOptions.experimentalImportSupport,
hot: transformOptions.hot,
minify: transformOptions.minify,
unstable_disableES6Transforms:
transformOptions.unstable_disableES6Transforms,
platform: transformOptions.platform
};
// bundle前面的var聲明和polyfill,生成的格式為:
// [
// {
// inverseDependencies: Set(0) {},
// path: '/Users/mrgaogang/Desktop/react-native-ssr/ReactNativeSSR/node_modules/react-native/Libraries/polyfills/Object.es7.js',
// dependencies: Map(0) {},
// getSource: [Function: getSource],
// output: [ [Object] ]
// }
// ]
const prepend = yield getPrependedScripts(
_this2._config,
transformOptionsWithoutType,
_this2._bundler,
_this2._deltaBundler
);
return {
prepend,
graph
};
})();
}
require 和模塊定義部分解析和依賴生成
在 buildGraphForEntries中利用_deltaBundler.buildGraph生成 graph,
// node_modules/metro/src/IncrementalBundler.js
buildGraphForEntries(entryFiles, transformOptions) {
return _asyncToGenerator(function*() {
const absoluteEntryFiles = entryFiles.map(entryFile =>
path.resolve(_this._config.projectRoot, entryFile)
);
// 調(diào)用 DeltaBundler.buildGraph
const graph = yield _this._deltaBundler.buildGraph(absoluteEntryFiles, {
// ... 一些其他的參數(shù)
});
// ....
return graph;
})();
// node_modules/metro/src/DeltaBundler.js
buildGraph(entryPoints, options) {
var _this = this;
return _asyncToGenerator(function*() {
// 使用node_modules/metro/src/Bundler.js 獲取模塊依賴圖譜
const depGraph = yield _this._bundler.getDependencyGraph();
// 監(jiān)聽文件變化,如果文件存在變化則更新文件之間的依賴
const deltaCalculator = new DeltaCalculator(
entryPoints,
depGraph,
options
);
// 計(jì)算模塊之間的變化,包括模塊的增加刪除和修改,如果有變化則第一時(shí)間更新
yield deltaCalculator.getDelta({
reset: true,
shallow: options.shallow
});
// 根據(jù)返回的依賴圖譜以及文件變化檢測之后的結(jié)果,返回如下格式的的模塊依賴信息。(完整格式化后面會(huì)給出)
// {
// dependencies: new Map(),
// entryPoints,
// importBundleNames: new Set()
// }
const graph = deltaCalculator.getGraph();
_this._deltaCalculators.set(graph, deltaCalculator);
return graph;
})();
}
// node_modules/metro/src/Bundler.js
// 依賴圖譜分析
class Bundler {
constructor(config, options) {
// Bundler又使用DependencyGraph進(jìn)行依賴分析,生成依賴圖譜
this._depGraphPromise = DependencyGraph.load(config, options);
this._depGraphPromise
.then(dependencyGraph => {
this._transformer = new Transformer(
config,
dependencyGraph.getSha1.bind(dependencyGraph)
);
})
.catch(error => {
console.error("Failed to construct transformer: ", error);
});
}
getDependencyGraph() {
return this._depGraphPromise;
}
}
// 依賴分析圖譜 DependencyGraph.load使用 JestHasteMap進(jìn)行依賴分析
// node_modules/metro/src/node-haste/DependencyGraph.js
static _createHaste(config, watch) {
return new JestHasteMap({
cacheDirectory: config.hasteMapCacheDirectory,
computeDependencies: false,
computeSha1: true,
extensions: config.resolver.sourceExts.concat(config.resolver.assetExts),
forceNodeFilesystemAPI: !config.resolver.useWatchman,
hasteImplModulePath: config.resolver.hasteImplModulePath,
ignorePattern: config.resolver.blacklistRE || / ^/,
mapper: config.resolver.virtualMapper,
maxWorkers: config.maxWorkers,
mocksPattern: "",
name: "metro-" + JEST_HASTE_MAP_CACHE_BREAKER,
platforms: config.resolver.platforms,
retainAllFiles: true,
resetCache: config.resetCache,
rootDir: config.projectRoot,
roots: config.watchFolders,
throwOnModuleCollision: true,
useWatchman: config.resolver.useWatchman,
watch: watch == null ? !ci.isCI : watch
});
}
static load(config, options) {
return _asyncToGenerator(function*() {
const haste = DependencyGraph._createHaste(
config,
options && options.watch
);
const _ref2 = yield haste.build(),
hasteFS = _ref2.hasteFS,
moduleMap = _ref2.moduleMap;
return new DependencyGraph({
haste,
initialHasteFS: hasteFS,
initialModuleMap: moduleMap,
config
});
})();
}
// JestHasteMap是一個(gè)用于node.js靜態(tài)資源的依賴項(xiàng)管理系統(tǒng)。它提供了為節(jié)點(diǎn)模塊解析和Facebook的haste模塊系統(tǒng)靜態(tài)解析JavaScript模塊依賴性的功能。
// 由于haste map創(chuàng)建是同步的,且大多數(shù)任務(wù)被I / O阻塞,因此采用了電腦的多內(nèi)核進(jìn)行并行操作。
經(jīng)過DependencyGraph.load和DeltaCalculator之后,生成的依賴圖譜格式如下:
{
dependencies: Map(404) {
// 每一個(gè)模塊的依賴信息等
'/Users/mrgaogang/Desktop/react-native-ssr/ReactNativeSSR/index.js' => {
inverseDependencies: Set(1) {
'/Users/mrgaogang/Desktop/react-native-ssr/ReactNativeSSR/index.js'
},
path: '/Users/mrgaogang/Desktop/react-native-ssr/ReactNativeSSR/App.js', // 模塊路徑
dependencies: Map(8) { // 該模塊依賴的其他模塊
},
getSource: [Function: getSource],
output: [
{
data: {
code: ``, // 打包的改模塊的代碼
lineCount: 1,
map: [
],
functionMap: {
names: [ '<global>', 'App', 'render' ],
mappings: 'AAA;eCW;ECC;GDQ;CDC'
}
},
type: 'js/module' // 類型,metro會(huì)通過是否startWidth('js')判斷是否為js模塊
}
]
},
},
entryPoints: [ // 入口
'/Users/mrgaogang/Desktop/react-native-ssr/ReactNativeSSR/index.js'
],
importBundleNames: Set(0) {}
}
var 及 polyfill 部分解析
前面看到在IncrementalBundler.js的 buildGraph中通過getPrependedScripts獲取到var 和 polyfill部分的代碼;下面我們一些查看一下getPrependedScripts:
// node_modules/metro/src/lib/getPreludeCode.js
function _getPrependedScripts() {
_getPrependedScripts = _asyncToGenerator(function*(
config,
options,
bundler,
deltaBundler
) {
// 獲取所有的polyfills,包括默認(rèn)的和自定義的polyfill
// 默認(rèn)的polyfill請見: node_modules/react-native/node_modules/@react-native-community/cli/build/tools/loadMetroConfig.js getDefaultConfig:function 中使用了 node_modules/react-native/rn-get-polyfills.js 也即
// module.exports = () => [
// require.resolve('./Libraries/polyfills/console.js'),
// require.resolve('./Libraries/polyfills/error-guard.js'),
// require.resolve('./Libraries/polyfills/Object.es7.js'),
// ];
const polyfillModuleNames = config.serializer
.getPolyfills({
platform: options.platform,
})
.concat(config.serializer.polyfillModuleNames);
const transformOptions = _objectSpread({}, options, {
type: "script",
});
// 通過 deltaBundler.buildGraph 分析 如下四個(gè)文件及自定義polyfill的依賴關(guān)系圖譜
// metro/src/lib/polyfills/require.js
// require.resolve('./Libraries/polyfills/console.js'),
// require.resolve('./Libraries/polyfills/error-guard.js'),
// require.resolve('./Libraries/polyfills/Object.es7.js'),
const graph = yield deltaBundler.buildGraph(
[defaults.moduleSystem].concat(_toConsumableArray(polyfillModuleNames)),
{
resolve: yield transformHelpers.getResolveDependencyFn(
bundler,
options.platform
),
transform: yield transformHelpers.getTransformFn(
[defaults.moduleSystem].concat(
_toConsumableArray(polyfillModuleNames)
),
bundler,
deltaBundler,
config,
transformOptions
),
onProgress: null,
experimentalImportBundleSupport:
config.transformer.experimentalImportBundleSupport,
shallow: false,
}
);
return [
// 返回 var定義部分和 經(jīng)過 deltaBundler.buildGraph 分析的之后的polyfill依賴圖譜
_getPrelude({
dev: options.dev,
}),
].concat(_toConsumableArray(graph.dependencies.values()));
});
return _getPrependedScripts.apply(this, arguments);
}
function _getPrelude(_ref) {
let dev = _ref.dev;
const code = getPreludeCode({
isDev: dev,
});
const name = "__prelude__";
return {
dependencies: new Map(),
getSource: () => Buffer.from(code),
inverseDependencies: new Set(),
path: name,
output: [
{
type: "js/script/virtual",
data: {
code,
lineCount: countLines(code),
map: [],
},
},
],
};
}
// node_modules/metro/src/lib/getPreludeCode.js
// var定義部分的代碼
function getPreludeCode(_ref) {
let extraVars = _ref.extraVars,
isDev = _ref.isDev;
const vars = [
"__BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now()",
`__DEV__=${String(isDev)}`,
].concat(_toConsumableArray(formatExtraVars(extraVars)), [
"process=this.process||{}",
]);
return `var ${vars.join(",")};${processEnv(
isDev ? "development" : "production"
)}`;
}
此處還有一個(gè)部分作者沒有詳細(xì)進(jìn)行講述,那就是使用JestHasteMap 進(jìn)行文件依賴解析詳細(xì)部分;后續(xù)筆者會(huì)單獨(dú)出一篇文章進(jìn)行講解,關(guān)于查閱。
至此,metro 對入口文件及 polyfills 依賴分析及代碼生成以及講述完畢,回過頭再看一下此章節(jié)的開頭部分,不知您是否已豁然開朗。講述了 Metro 的解析和轉(zhuǎn)換,下面部分將講述 Metro 如果通過轉(zhuǎn)換后的文件依賴圖譜生成最終的 bundle 代碼。
metro 構(gòu)建 bundle: 生成
回到最開始的 Server 服務(wù)啟動(dòng)代碼部分,我們發(fā)現(xiàn)經(jīng)過buildGraph之后得到了prepend: var及polyfill部分的代碼和依賴關(guān)系以及graph: 入口文件的依賴關(guān)系及代碼;在沒有提供自定義生成的情況下 metro 使用了baseJSBundle將依賴關(guān)系圖譜和每個(gè)模塊的代碼經(jīng)過一系列的操作最終使用 bundleToString 轉(zhuǎn)換成最終的代碼。
// metro打包核心:解析(Resolution)和轉(zhuǎn)換(Transformation)
const _ref13 = yield _this2._bundler.buildGraph(
entryFile,
transformOptions,
{
onProgress,
shallow: graphOptions.shallow,
}
),
prepend = _ref13.prepend,
graph = _ref13.graph;
// ....
// 此處筆者將其拆分成兩個(gè)步驟,比較容易分析
// 將解析及轉(zhuǎn)化之后的數(shù)據(jù),生成如下格式化的數(shù)據(jù)
// {
// pre: string, // var定義部分及poyfill部分的代碼
// post: string, // require部分代碼
// modules: [[number, string]], // 模塊定義部分,第一個(gè)參數(shù)為number,第二個(gè)參數(shù)為具體的代碼
// }
var base = baseJSBundle(entryPoint, prepend, graph, bundleOptions);
// 將js module進(jìn)行排序并進(jìn)行字符串拼接生成最終的代碼
bundleCode = bundleToString(base).code;
在關(guān)注baseJSBundle之前,我們先來回顧一下,graph 和 prepend 的數(shù)據(jù)結(jié)構(gòu):其主要包括如下幾個(gè)信息:
- 文件相關(guān)的依賴關(guān)系
- 指定 module 經(jīng)過 babel 之后的代碼
// graph
[
{
dependencies: Map(404) { // 入口文件下每個(gè)文件所依賴其他文件的關(guān)系圖譜
'/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/index.js' => {
{
inverseDependencies: Set(1) {
'/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/index.js'
},
path: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/App.js',
dependencies: Map(8) {
'@babel/runtime/helpers/createClass' => {
absolutePath: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/node_modules/@babel/runtime/helpers/createClass.js',
data: {
name: '@babel/runtime/helpers/createClass',
data: { isAsync: false }
}
},
// ....
'react' => {
absolutePath: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/node_modules/react/index.js',
data: { name: 'react', data: { isAsync: false } }
},
'react-native' => {
absolutePath: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/node_modules/react-native/index.js',
data: { name: 'react-native', data: { isAsync: false } }
}
},
getSource: [Function: getSource],
output: [
{
data: {// 對應(yīng)文件轉(zhuǎn)換后的代碼
code: `__d(function(g,r,i,a,m,e,d){var t=r(d[0]);Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var n=t(r(d[1])),u=t(r(d[2])),l=t(r(d[3])),c=t(r(d[4])),f=t(r(d[5])),o=t(r(d[6])),s=r(d[7]);function y(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}var p=(function(t){(0,l.default)(R,t);var p,h,x=(p=R,h=y(),function(){var t,n=(0,f.default)(p);if(h){var u=(0,f.default)(this).constructor;t=Reflect.construct(n,arguments,u)}else t=n.apply(this,arguments);return(0,c.default)(this,t)});function R(){return(0,n.default)(this,R),x.apply(this,arguments)}return(0,u.default)(R,[{key:"render",value:function(){return o.default.createElement(o.default.Fragment,null,o.default.createElement(s.View,{style:v.body},o.default.createElement(s.Text,{style:v.text},"\\u4f60\\u597d\\uff0c\\u4e16\\u754c")))}}]),R})(o.default.Component);e.default=p;var v=s.StyleSheet.create({body:{backgroundColor:'white',flex:1,justifyContent:'center',alignItems:'center'},text:{textAlign:'center',color:'red'}})});`,
lineCount: 1,
map: [
[ 1, 177, 9, 0, '_react' ],
[ 1, 179, 9, 0, '_interopRequireDefault' ],
[ 1, 181, 9, 0, 'r' ],
[ 1, 183, 9, 0, 'd' ],
[ 1, 185, 9, 0 ],
[ 1, 190, 10, 0, '_reactNative' ],
// .....
],
functionMap: {
names: [ '<global>', 'App', 'render' ],
mappings: 'AAA;eCW;ECC;GDQ;CDC'
}
},
type: 'js/module'
}
]
}
},
'/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/App.js' => {
inverseDependencies: [Set],
path: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/App.js',
dependencies: [Map],
getSource: [Function: getSource],
output: [Array]
},
'/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/app.json' => {
inverseDependencies: [Set],
path: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/app.json',
dependencies: Map(0) {},
getSource: [Function: getSource],
output: [Array]
}
},
entryPoints: [ //入口文件
'/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/index.js'
],
importBundleNames: Set(0) {}
}
]
baseJSBundle
下面我們我們重點(diǎn)關(guān)注一下baseJSBundle是如何處理上述的數(shù)據(jù)結(jié)構(gòu)的:
-
baseJSBundle整體調(diào)用了三次processModules分別用于解析出:preCode,postCode和modules其對應(yīng)的分別是var 和 polyfills 部分的代碼 , require 部分的代碼 , _d 部分的代碼 -
processModules經(jīng)過兩次filter過濾出所有類型為js/類型的數(shù)據(jù),第二次過濾使用用戶自定義filter函數(shù);過濾完成之后使用wrapModule轉(zhuǎn)換成_d(factory,moduleId,dependencies)的代碼 - baseJSBundle
// node_modules/metro/src/DeltaBundler/Serializers/baseJSBundle.js
function baseJSBundle(entryPoint, preModules, graph, options) {
for (const module of graph.dependencies.values()) {
options.createModuleId(module.path);
}
const processModulesOptions = {
filter: options.processModuleFilter,
createModuleId: options.createModuleId,
dev: options.dev,
projectRoot: options.projectRoot,
}; // Do not prepend polyfills or the require runtime when only modules are requested
if (options.modulesOnly) {
preModules = [];
}
// 通過processModules將metro解析后的prepend依賴關(guān)系圖譜和代碼,filter+join成對應(yīng)的bundle出的代碼
const preCode = processModules(preModules, processModulesOptions)
.map((_ref) => {
let _ref2 = _slicedToArray(_ref, 2),
_ = _ref2[0],
code = _ref2[1];
return code;
})
.join("\n");
const modules = _toConsumableArray(graph.dependencies.values()).sort(
(a, b) => options.createModuleId(a.path) - options.createModuleId(b.path)
);
// 使用getAppendScripts獲取入口文件及所有的runBeforeMainModule文件的依賴圖譜和 使用 getRunModuleStatement 方法生成_r(moduleId)的代碼,調(diào)用processModules生成最終代碼
const postCode = processModules(
getAppendScripts(
entryPoint,
_toConsumableArray(preModules).concat(_toConsumableArray(modules)),
graph.importBundleNames,
{
asyncRequireModulePath: options.asyncRequireModulePath,
createModuleId: options.createModuleId,
getRunModuleStatement: options.getRunModuleStatement,
inlineSourceMap: options.inlineSourceMap,
projectRoot: options.projectRoot,
runBeforeMainModule: options.runBeforeMainModule,
runModule: options.runModule,
sourceMapUrl: options.sourceMapUrl,
sourceUrl: options.sourceUrl,
}
),
processModulesOptions
)
.map((_ref3) => {
let _ref4 = _slicedToArray(_ref3, 2),
_ = _ref4[0],
code = _ref4[1];
return code;
})
.join("\n");
return {
pre: preCode,
post: postCode,
modules: processModules(
// 使用processModules獲取所有`_d`部分的代碼數(shù)組
_toConsumableArray(graph.dependencies.values()),
processModulesOptions
).map((_ref5) => {
let _ref6 = _slicedToArray(_ref5, 2),
module = _ref6[0],
code = _ref6[1];
return [options.createModuleId(module.path), code];
}),
};
}
- processModules
processModules 經(jīng)過兩次 filter 過濾出所有類型為 js/類型的數(shù)據(jù),第二次過濾使用用戶自定義 filter 函數(shù);過濾完成之后使用 wrapModule 轉(zhuǎn)換成_d(factory,moduleId,dependencies)的代碼
// node_modules/metro/src/DeltaBundler/Serializers/helpers/processModules.js
function processModules(modules, _ref) {
let _ref$filter = _ref.filter,
filter = _ref$filter === void 0 ? () => true : _ref$filter,
createModuleId = _ref.createModuleId,
dev = _ref.dev,
projectRoot = _ref.projectRoot;
return _toConsumableArray(modules)
.filter(isJsModule)
.filter(filter)
.map((module) => [
module,
wrapModule(module, {
createModuleId,
dev,
projectRoot,
}),
]);
}
// node_modules/metro/src/DeltaBundler/Serializers/helpers/js.js
function wrapModule(module, options) {
const output = getJsOutput(module);
// 如果類型為js/script則直接返回其代碼
if (output.type.startsWith("js/script")) {
return output.data.code;
}
const moduleId = options.createModuleId(module.path);
// d(factory,moduleId,dependencies)后面兩個(gè)參數(shù)生成
const params = [
moduleId,
Array.from(module.dependencies.values()).map((dependency) => {
return options.createModuleId(dependency.absolutePath);
}),
]; // Add the module relative path as the last parameter (to make it easier to do
// requires by name when debugging).
if (options.dev) {
params.push(path.relative(options.projectRoot, module.path));
}
// 進(jìn)行代碼轉(zhuǎn)換,因?yàn)樵讷@取到的依賴圖譜中只有_d(factory),需要加上用moduleId和依賴關(guān)系
return addParamsToDefineCall.apply(void 0, [output.data.code].concat(params));
}
function getJsOutput(module) {
const jsModules = module.output.filter((_ref) => {
let type = _ref.type;
return type.startsWith("js/");
});
invariant(
jsModules.length === 1,
`Modules must have exactly one JS output, but ${module.path} has ${
jsModules.length
} JS outputs.`
);
const jsOutput = jsModules[0];
invariant(
Number.isFinite(jsOutput.data.lineCount),
`JS output must populate lineCount, but ${module.path} has ${
jsOutput.type
} output with lineCount '${jsOutput.data.lineCount}'`
);
return jsOutput;
}
function isJsModule(module) {
return module.output.filter(isJsOutput).length > 0;
}
function isJsOutput(output) {
return output.type.startsWith("js/");
}
// node_modules/metro/src/lib/addParamsToDefineCall.js
function addParamsToDefineCall(code) {
const index = code.lastIndexOf(")");
for (
var _len = arguments.length,
paramsToAdd = new Array(_len > 1 ? _len - 1 : 0),
_key = 1;
_key < _len;
_key++
) {
paramsToAdd[_key - 1] = arguments[_key];
}
const params = paramsToAdd.map((param) =>
param !== undefined ? JSON.stringify(param) : "undefined"
);
return code.slice(0, index) + "," + params.join(",") + code.slice(index);
}
- getAppendScripts
上面講到 getAppendScripts 主要作用是: 獲取入口文件及所有的 runBeforeMainModule 文件的依賴圖譜和 使用 getRunModuleStatement 方法生成_r(moduleId)的代碼
function getAppendScripts(entryPoint, modules, importBundleNames, options) {
const output = [];
// 如果有importBundleNames插入對應(yīng)代碼
if (importBundleNames.size) {
const importBundleNamesObject = Object.create(null);
importBundleNames.forEach((absolutePath) => {
const bundlePath = path.relative(options.projectRoot, absolutePath);
importBundleNamesObject[options.createModuleId(absolutePath)] =
bundlePath.slice(0, -path.extname(bundlePath).length) + ".bundle";
});
const code = `(function(){var $=${options.getRunModuleStatement(
options.createModuleId(options.asyncRequireModulePath)
)}$.addImportBundleNames(${String(
JSON.stringify(importBundleNamesObject)
)})})();`;
output.push({
path: "$importBundleNames",
dependencies: new Map(),
getSource: () => Buffer.from(""),
inverseDependencies: new Set(),
output: [
{
type: "js/script/virtual",
data: {
code,
lineCount: countLines(code),
map: [],
},
},
],
});
}
if (options.runModule) {
// 聚合runBeforeMainModule和入口文件,前講過runBeforeMainModule的默認(rèn)值為: /node_modules/metro/src/lib/polyfills/require.js
const paths = _toConsumableArray(options.runBeforeMainModule).concat([
entryPoint,
]);
for (const path of paths) {
if (modules.some((module) => module.path === path)) {
// 通過getRunModuleStatement函數(shù)生成 _r(moduleId)的代碼
// getRunModuleStatement默認(rèn)值詳情請見: node_modules/metro-config/src/defaults/index.js
const code = options.getRunModuleStatement(
options.createModuleId(path)
);
output.push({
path: `require-${path}`,
dependencies: new Map(),
getSource: () => Buffer.from(""),
inverseDependencies: new Set(),
output: [
{
type: "js/script/virtual",
data: {
code,
lineCount: countLines(code),
map: [],
},
},
],
});
}
}
}
// ...
return output;
}
至此 baseJSBundle我們已經(jīng)分析完成。
bundleToString
經(jīng)過前面一個(gè)步驟bundleToBundle我們分別獲取到了: preCode , postCode 和 modules 其對應(yīng)的分別是var 和 polyfills 部分的代碼 , require 部分的代碼 , _d 部分的代碼 而 bundleToString的作用如下:
- 先將 var 及 polyfill 部分的代碼使用\n 進(jìn)行字符串拼接;
- 然后將
_d部分的代碼使用moduleId進(jìn)行升序排列并使用字符串拼接的方式構(gòu)造_d部分的代碼; - 最后合如
_r部分的代碼
function bundleToString(bundle) {
let code = bundle.pre.length > 0 ? bundle.pre + "\n" : "";
const modules = [];
const sortedModules = bundle.modules
.slice() // The order of the modules needs to be deterministic in order for source
// maps to work properly.
.sort((a, b) => a[0] - b[0]);
for (const _ref of sortedModules) {
var _ref2 = _slicedToArray(_ref, 2);
const id = _ref2[0];
const moduleCode = _ref2[1];
if (moduleCode.length > 0) {
code += moduleCode + "\n";
}
modules.push([id, moduleCode.length]);
}
if (bundle.post.length > 0) {
code += bundle.post;
} else {
code = code.slice(0, -1);
}
return {
code,
metadata: {
pre: bundle.pre.length,
post: bundle.post.length,
modules,
},
};
}
總結(jié)
- react-native 使用 metro 打包之后的 bundle 大致分為四層
bundle 包大致分為四層:
- var 聲明層: 對當(dāng)前運(yùn)行環(huán)境, bundle 啟動(dòng)時(shí)間,以及進(jìn)程相關(guān)信息;
-
poyfill 層:
!(function(r){}), 定義了對define(__d)、require(__r)、clear(__c)的支持,以及 module(react-native 及第三方 dependences 依賴的 module) 的加載邏輯; -
模塊定義層:
__d定義的代碼塊,包括 RN 框架源碼 js 部分、自定義 js 代碼部分、圖片資源信息,供 require 引入使用 - require 層: r 定義的代碼塊,找到 d 定義的代碼塊 并執(zhí)行
-
react-native使用metro進(jìn)行打包主要分為三個(gè)步驟: 解析,轉(zhuǎn)化和生成;

- 解析和轉(zhuǎn)化部分: Metro Server 使用
IncrementalBundler進(jìn)行 js 代碼的解析和轉(zhuǎn)換
在 Metro 使用IncrementalBundler進(jìn)行解析轉(zhuǎn)換的主要作用是:
- 返回了以入口文件為入口的所有相關(guān)依賴文件的依賴圖譜和 babel 轉(zhuǎn)換后的代碼;
- 返回了var 定義部分及 polyfill 部分所有相關(guān)依賴文件的依賴圖譜和 babel 轉(zhuǎn)換后的代碼;
整體流程如圖所示:

通過上述的流程我們總結(jié)如下幾點(diǎn):
- 整個(gè) metro 進(jìn)行依賴分析和 babel 轉(zhuǎn)換主要通過了JestHasteMap 去做依賴分析;
- 在做依賴分析的通過,metro 會(huì)監(jiān)聽當(dāng)前目錄的文件變化,然后以最小變化生成最終依賴關(guān)系圖譜;
- 不管是入口文件解析還是 polyfill 文件的依賴解析都是使用了JestHasteMap ;
生成的對應(yīng)依賴關(guān)系圖譜格式如下:
// graph
[
{
dependencies: Map(404) { // 入口文件下每個(gè)文件所依賴其他文件的關(guān)系圖譜
'/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/index.js' => {
{
inverseDependencies: Set(1) {
'/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/index.js'
},
path: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/App.js',
dependencies: Map(8) {
'@babel/runtime/helpers/createClass' => {
absolutePath: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/node_modules/@babel/runtime/helpers/createClass.js',
data: {
name: '@babel/runtime/helpers/createClass',
data: { isAsync: false }
}
},
// ....
'react' => {
absolutePath: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/node_modules/react/index.js',
data: { name: 'react', data: { isAsync: false } }
},
'react-native' => {
absolutePath: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/node_modules/react-native/index.js',
data: { name: 'react-native', data: { isAsync: false } }
}
},
getSource: [Function: getSource],
output: [
{
data: {// 對應(yīng)文件轉(zhuǎn)換后的代碼
code: `__d(function(g,r,i,a,m,e,d){var t=r(d[0]);Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var n=t(r(d[1])),u=t(r(d[2])),l=t(r(d[3])),c=t(r(d[4])),f=t(r(d[5])),o=t(r(d[6])),s=r(d[7]);function y(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}var p=(function(t){(0,l.default)(R,t);var p,h,x=(p=R,h=y(),function(){var t,n=(0,f.default)(p);if(h){var u=(0,f.default)(this).constructor;t=Reflect.construct(n,arguments,u)}else t=n.apply(this,arguments);return(0,c.default)(this,t)});function R(){return(0,n.default)(this,R),x.apply(this,arguments)}return(0,u.default)(R,[{key:"render",value:function(){return o.default.createElement(o.default.Fragment,null,o.default.createElement(s.View,{style:v.body},o.default.createElement(s.Text,{style:v.text},"\\u4f60\\u597d\\uff0c\\u4e16\\u754c")))}}]),R})(o.default.Component);e.default=p;var v=s.StyleSheet.create({body:{backgroundColor:'white',flex:1,justifyContent:'center',alignItems:'center'},text:{textAlign:'center',color:'red'}})});`,
lineCount: 1,
map: [
[ 1, 177, 9, 0, '_react' ],
[ 1, 179, 9, 0, '_interopRequireDefault' ],
[ 1, 181, 9, 0, 'r' ],
[ 1, 183, 9, 0, 'd' ],
[ 1, 185, 9, 0 ],
[ 1, 190, 10, 0, '_reactNative' ],
// .....
],
functionMap: {
names: [ '<global>', 'App', 'render' ],
mappings: 'AAA;eCW;ECC;GDQ;CDC'
}
},
type: 'js/module'
}
]
}
},
'/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/App.js' => {
inverseDependencies: [Set],
path: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/App.js',
dependencies: [Map],
getSource: [Function: getSource],
output: [Array]
},
'/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/app.json' => {
inverseDependencies: [Set],
path: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/app.json',
dependencies: Map(0) {},
getSource: [Function: getSource],
output: [Array]
}
},
entryPoints: [ //入口文件
'/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/index.js'
],
importBundleNames: Set(0) {}
}
]
- metro 代碼生成部分使用
baseJSBundle得到代碼,并使用baseToString拼接最終Bundle代碼
在 baseJSBundle 中:
-
baseJSBundle整體調(diào)用了三次processModules分別用于解析出:preCode,postCode和modules其對應(yīng)的分別是var 和 polyfills 部分的代碼 , require 部分的代碼 ,_d部分的代碼 -
processModules經(jīng)過兩次filter過濾出所有類型為js/類型的數(shù)據(jù),第二次過濾使用用戶自定義filter函數(shù);過濾完成之后使用wrapModule轉(zhuǎn)換成_d(factory,moduleId,dependencies)的代碼
在baseToString中:
- 先將 var 及 polyfill 部分的代碼使用\n 進(jìn)行字符串拼接;
- 然后將
_d部分的代碼使用moduleId進(jìn)行升序排列并使用字符串拼接的方式構(gòu)造_d部分的代碼; - 最后合如
_r部分的代碼
原文地址: react-native bundle 到 bundle 生成到底發(fā)生了什么(metro 打包流程簡析)