本文波及 react-native
及 metro
版本
react-native@0.63.2
metro@0.58.0
先来看一波本文的实例代码:很简略吧,一个你好,世界
// App.jsimport 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
;运行上述命令之后,rn 会默认应用metro
作为打包工具,生成bundle
包。
生成的 bundle 包大抵分为四层:
- var 申明层: 对以后运行环境, bundle 启动工夫,以及过程相干信息;
- polyfill 层:
!(function(r){})
, 定义了对define(__d)
、require(__r)
、clear(__c)
的反对,以及 module(react-native 及第三方 dependences 依赖的 module) 的加载逻辑; - 模块定义层: \_\_d 定义的代码块,包含 RN 框架源码 js 局部、自定义 js 代码局部、图片资源信息,供 require 引入应用
- require 层: r 定义的代码块,找到 d 定义的代码块 并执行
格局如下:
// 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
的代码是在什么机会生成的?咱们晓得
_d()
有三个参数,别离是对应factory
函数,以后moduleId
以及module
依赖关系metro
应用什么去做整个工程的依赖剖析?moduleId
如何生成?
metro
如何打包?
日常开发中咱们可能并么有在意,整个 rn 打包逻辑;当初就让笔者带您走入 rn 打包的世界!
# 二、metro 打包流程
通过翻阅源码和 Metro 官网,咱们晓得 metro 打包的整个流程大抵分为:
命令参数解析
metro 打包服务启动
打包 js 和资源文件
- 解析,转化和生成
进行打包服务
# 1. 命令参数解析
首先咱们来看看 react-native bundle
的实现以及参数如何解析;因为 bundle 是 react-native 的一个子命令,那么咱们寻找的思路能够从 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.jsrun() -> 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 命令,然而具体的实现却应用了buildBundle.js
.
// node_modules/react-native/node_modules/@react-native-community/cli/build/commands/bundle/bundle.jsvar _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打包的具体实现 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 启动
在node_modules/react-native/node_modules/@react-native-community/cli/build/commands/bundle/buildBundle.js
文件中默认导出的 buildBundle
办法才是整个react-native bundle
执行的入口。在入口中次要做了如下几件事件:
- 合并 metro 默认配置和自定义配置,并设置 maxWorkers,resetCache
- 依据解析失去参数,构建 requestOptions,传递给打包函数
- 实例化 metro Server
- 启动 metro 构建 bundle
- 解决资源文件,解析
- 敞开 Metro Server
// node_modules/react-native/node_modules/@react-native-community/cli/build/commands/bundle/buildBundle.js// metro打包服务,也是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的默认配置var _loadMetroConfig = _interopRequireDefault( require("../../tools/loadMetroConfig"));async function buildBundle(args, ctx, output = _bundle().default) { // 合并metro默认配置和自定义配置,并设置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"; // 依据命令行的入参 --sourcemap-output 构建 sourceMapUrl let sourceMapUrl = args.sourcemapOutput; if (sourceMapUrl && !args.sourcemapUseAbsolutePath) { sourceMapUrl = _path().default.basename(sourceMapUrl); } // 依据解析失去参数,构建requestOptions,传递给打包函数 const requestOpts = { entryFile: args.entryFile, sourceMapUrl, dev: args.dev, minify: args.minify !== undefined ? args.minify : !args.dev, platform: args.platform, }; // 实例化metro 服务 const server = new (_Server()).default(config); try { // 启动打包, what? 作者不是说的是Server打包吗?为什么是output? 答:上面会解说 const bundle = await output.build(server, requestOpts); // 将打包生成的bundle保留到对应的目录 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 打包服务 server.end(); }}var _default = buildBundle;exports.default = _default;
从上述代码能够看到具体的打包实现都在output.build(server, requestOpts)
中,output是
outputBundle类型,这部分代码在
Metro JS\` 中,具体的门路为:node\_modules/metro/src/shared/output/bundle.js
// node_modules/metro/src/shared/output/bundle.jsfunction 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)
进行打包,其实是应用传入的packagerClient.build
进行打包。而packagerClient
是咱们刚传入的Server
。而Server
就是上面咱们要剖析打包流程。其源码地位为:node_modules/metro/src/Server.js
# metro 构建 bundle: 流程入口
通过下面的剖析,咱们曾经通晓整个react-native bundle
打包服务的启动在node_modules/metro/src/Server.js
的build
办法中:
class Server { // 构建函数,初始化属性 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*() { // 将传递进来的参数,依照模块进行拆分,一遍更好的治理;其拆分的格局如下: // { // 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)和转换(Transformation) const _ref13 = yield _this2._bundler.buildGraph( entryFile, transformOptions, { onProgress, shallow: graphOptions.shallow, } ), prepend = _ref13.prepend, graph = _ref13.graph; // 获取构建入口文件门路 const entryPoint = path.resolve(_this2._config.projectRoot, entryFile); // 初始化构建参数,此处的参数来源于: 命令行 && 自定义metro配置metro.config.js && 默认的metro配置 const bundleOptions = { asyncRequireModulePath: _this2._config.transformer.asyncRequireModulePath, processModuleFilter: _this2._config.serializer.processModuleFilter, createModuleId: _this2._createModuleId, // 外面自定义/默认的createModuleIdFactory给每个module生成id; 其默认生成规定详情请见: node_modules/metro/src/lib/createModuleIdFactory.js getRunModuleStatement: _this2._config.serializer.getRunModuleStatement, // 给办法签名 // 默认值为 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) ), // 指定在主模块前运行的模块, 默认值: 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; // 是否应用自定义生成,如果是,则调用自定义生成的函数,获取最终代码 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 { // 此处笔者将其拆分成两个步骤,比拟容易剖析 // 将解析及转化之后的数据,生成如下格式化的数据 // { // pre: string, // var定义局部及poyfill局部的代码 // post: string, // require局部代码 // modules: [[number, string]], // 模块定义局部,第一个参数为number,第二个参数为具体的代码 // } var base = baseJSBundle(entryPoint, prepend, graph, bundleOptions); // 将js module进行排序并进行字符串拼接生成最终的代码 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, }; })(); }}
在这个 build 函数中,首先执行了 buildGraph,而
this._bundler
的初始化产生在 Server 的 constructor
中。
this._bundler = new IncrementalBundler(config, { watch: options ? options.watch : undefined,});
此处的_bundler
是 IncrementalBundler
的实例,它的 buildGraph
函数实现了打包过程中前两步 Resolution
和 Transformation
。 上面咱们就来具体查看一下 Metro 解析,转换过程。
# metro 构建 bundle: 解析和转换
在下面一节咱们晓得 metro 应用IncrementalBundler
进行 js 代码的解析和转换,在 Metro 应用IncrementalBundler
进行解析转换的次要作用是:
- 返回了以入口文件为入口的所有相干依赖文件的依赖图谱和 babel 转换后的代码;
- 返回了var 定义局部及 polyfill 局部所有相干依赖文件的依赖图谱和 babel 转换后的代码;
整体流程如图所示:
通过上述的流程咱们总结如下几点:
- 整个 metro 进行依赖剖析和 babel 转换次要通过了JestHasteMap 去做依赖剖析;
- 在做依赖剖析的通过,metro 会监听当前目录的文件变动,而后以最小变动生成最终依赖关系图谱;
- 不论是入口文件解析还是 polyfill 文件的依赖解析都是应用了JestHasteMap ;
上面,咱们来剖析其具体过程如下:
// node_modules/metro/src/IncrementalBundler.jsbuildGraph(entryFile, transformOptions) { var _this2 = this; let otherOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : { onProgress: null, shallow: false }; return _asyncToGenerator(function*() { // 外围构建在buildGraphForEntries中,通过入口文件进行依赖解析,失去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) ); // 调用 DeltaBundler.buildGraph const graph = yield _this._deltaBundler.buildGraph(absoluteEntryFiles, { // ... 一些其余的参数 }); // .... 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(); // 监听文件变动,如果文件存在变动则更新文件之间的依赖 const deltaCalculator = new DeltaCalculator( entryPoints, depGraph, options ); // 计算模块之间的变动,包含模块的减少删除和批改,如果有变动则第一工夫更新 yield deltaCalculator.getDelta({ reset: true, shallow: options.shallow }); // 依据返回的依赖图谱以及文件变化检测之后的后果,返回如下格局的的模块依赖信息。(残缺格式化前面会给出) // { // 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进行依赖剖析,生成依赖图谱 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进行依赖剖析// node_modules/metro/src/node-haste/DependencyGraph.jsstatic _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是一个用于node.js动态资源的依赖项管理系统。它提供了为节点模块解析和Facebook的haste模块零碎动态解析JavaScript模块依赖性的性能。// 因为haste map创立是同步的,且大多数工作被I / O阻塞,因而采纳了电脑的多内核进行并行操作。
通过DependencyGraph.load
和DeltaCalculator
之后,生成的依赖图谱格局如下:
{ dependencies: Map(404) { // 每一个模块的依赖信息等 '/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会通过是否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.jsfunction _getPrependedScripts() { _getPrependedScripts = _asyncToGenerator(function*( config, options, bundler, deltaBundler ) { // 获取所有的polyfills,包含默认的和自定义的polyfill // 默认的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 剖析 如下四个文件及自定义polyfill的依赖关系图谱 // 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定义局部和 通过 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" )}`;}
此处还有一个局部作者没有具体进行讲述,那就是应用JestHasteMap 进行文件依赖解析具体局部;后续笔者会独自出一篇文章进行解说,对于查阅。
至此,metro 对入口文件及 polyfills 依赖剖析及代码生成以及讲述结束,回过头再看一下此章节的结尾局部,不知您是否已恍然大悟。讲述了 Metro 的解析和转换,上面局部将讲述 Metro 如果通过转换后的文件依赖图谱生成最终的 bundle 代码。
# metro 构建 bundle: 生成
回到最开始的 Server 服务启动代码局部,咱们发现通过buildGraph
之后失去了prepend: var及polyfill局部的代码和依赖关系
以及graph: 入口文件的依赖关系及代码
;在没有提供自定义生成的状况下 metro 应用了baseJSBundle
将依赖关系图谱和每个模块的代码通过一系列的操作最终应用 bundleToString
转换成最终的代码。
// metro打包外围:解析(Resolution)和转换(Transformation) const _ref13 = yield _this2._bundler.buildGraph( entryFile, transformOptions, { onProgress, shallow: graphOptions.shallow, } ), prepend = _ref13.prepend, graph = _ref13.graph; // .... // 此处笔者将其拆分成两个步骤,比拟容易剖析 // 将解析及转化之后的数据,生成如下格式化的数据 // { // pre: string, // var定义局部及poyfill局部的代码 // post: string, // require局部代码 // modules: [[number, string]], // 模块定义局部,第一个参数为number,第二个参数为具体的代码 // } var base = baseJSBundle(entryPoint, prepend, graph, bundleOptions); // 将js module进行排序并进行字符串拼接生成最终的代码 bundleCode = bundleToString(base).code;
在关注baseJSBundle
之前,咱们先来回顾一下,graph 和 prepend 的数据结构:其次要包含如下几个信息:
- 文件相干的依赖关系
- 指定 module 通过 babel 之后的代码
// graph[{ dependencies: Map(404) { // 入口文件下每个文件所依赖其余文件的关系图谱 '/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: {// 对应文件转换后的代码 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
上面咱们咱们重点关注一下baseJSBundle
是如何解决上述的数据结构的:
baseJSBundle
整体调用了三次processModules
别离用于解析出:preCode
,postCode
和modules
其对应的别离是var 和 polyfills 局部的代码 , require 局部的代码 , \_d 局部的代码processModules
通过两次filter
过滤出所有类型为js/
类型的数据,第二次过滤应用用户自定义filter
函数;过滤实现之后应用wrapModule
转换成_d(factory,moduleId,dependencies)
的代码- baseJSBundle
// node_modules/metro/src/DeltaBundler/Serializers/baseJSBundle.jsfunction 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依赖关系图谱和代码,filter+join成对应的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)的代码,调用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`局部的代码数组 _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
通过两次 filter
过滤出所有类型为 js/
类型的数据,第二次过滤应用用户自定义 filter
函数;过滤实现之后应用 wrapModule 转换成_d(factory,moduleId,dependencies)
的代码
// node_modules/metro/src/DeltaBundler/Serializers/helpers/processModules.jsfunction 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.jsfunction 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)前面两个参数生成 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)); } // 进行代码转换,因为在获取到的依赖图谱中只有_d(factory),须要加上用moduleId和依赖关系 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.jsfunction 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插入对应代码 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的默认值为: /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函数生成 _r(moduleId)的代码 // getRunModuleStatement默认值详情请见: 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
咱们曾经剖析实现。
# bundleToString
通过后面一个步骤bundleToBundle
咱们别离获取到了: preCode
, postCode
和 modules
其对应的别离是var 和 polyfills 局部的代码 , require 局部的代码 , \_d 局部的代码 而 bundleToString
的作用如下:
- 先将 var 及 polyfill 局部的代码应用\n 进行字符串拼接;
- 而后将
_d
局部的代码应用moduleId
进行升序排列并应用字符串拼接的形式结构_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, }, };}
# 总结
- react-native 应用 metro 打包之后的 bundle 大抵分为四层
bundle 包大抵分为四层:
- var 申明层: 对以后运行环境, bundle 启动工夫,以及过程相干信息;
- poyfill 层:
!(function(r){})
, 定义了对define(__d)
、require(__r)
、clear(__c)
的反对,以及 module(react-native 及第三方 dependences 依赖的 module) 的加载逻辑; - 模块定义层:
__d
定义的代码块,包含 RN 框架源码 js 局部、自定义 js 代码局部、图片资源信息,供 require 引入应用 - require 层: r 定义的代码块,找到 d 定义的代码块 并执行
react-native
应用metro
进行打包次要分为三个步骤: 解析,转化和生成;- 解析和转化局部: Metro Server 应用
IncrementalBundler
进行 js 代码的解析和转换
在 Metro 应用IncrementalBundler
进行解析转换的次要作用是:
- 返回了以入口文件为入口的所有相干依赖文件的依赖图谱和 babel 转换后的代码;
- 返回了var 定义局部及 polyfill 局部所有相干依赖文件的依赖图谱和 babel 转换后的代码;
整体流程如图所示:
通过上述的流程咱们总结如下几点:
- 整个 metro 进行依赖剖析和 babel 转换次要通过了JestHasteMap 去做依赖剖析;
- 在做依赖剖析的通过,metro 会监听当前目录的文件变动,而后以最小变动生成最终依赖关系图谱;
- 不论是入口文件解析还是 polyfill 文件的依赖解析都是应用了JestHasteMap ;
生成的对应依赖关系图谱格局如下:
// graph[{ dependencies: Map(404) { // 入口文件下每个文件所依赖其余文件的关系图谱 '/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: {// 对应文件转换后的代码 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
整体调用了三次processModules
别离用于解析出:preCode
,postCode
和modules
其对应的别离是var 和 polyfills 局部的代码 , require 局部的代码 ,_d
局部的代码processModules
通过两次filter
过滤出所有类型为js/
类型的数据,第二次过滤应用用户自定义filter
函数;过滤实现之后应用wrapModule
转换成_d(factory,moduleId,dependencies)
的代码
在baseToString
中:
- 先将 var 及 polyfill 局部的代码应用\n 进行字符串拼接;
- 而后将
_d
局部的代码应用moduleId
进行升序排列并应用字符串拼接的形式结构_d
局部的代码; - 最初合如
_r
局部的代码
原文地址: react-native bundle 到 bundle 生成到底产生了什么(metro 打包流程简析)