本文波及 react-nativemetro 版本

  • 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);

看完下面的代码不知你是否疑难?

  1. var 定义层和 polyfill 的代码是在什么机会生成的?
  2. 咱们晓得_d()有三个参数,别离是对应 factory 函数,以后 moduleId 以及 module 依赖关系

    • metro 应用什么去做整个工程的依赖剖析?
    • moduleId 如何生成?
  3. 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)中,outputoutputBundle类型,这部分代码在 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.jsbuild办法中:

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,});

此处的_bundlerIncrementalBundler 的实例,它的 buildGraph 函数实现了打包过程中前两步 ResolutionTransformation 。 上面咱们就来具体查看一下 Metro 解析,转换过程。

# metro 构建 bundle: 解析和转换

在下面一节咱们晓得 metro 应用IncrementalBundler进行 js 代码的解析和转换,在 Metro 应用IncrementalBundler进行解析转换的次要作用是:

  • 返回了以入口文件为入口的所有相干依赖文件的依赖图谱和 babel 转换后的代码
  • 返回了var 定义局部及 polyfill 局部所有相干依赖文件的依赖图谱和 babel 转换后的代码

整体流程如图所示:

通过上述的流程咱们总结如下几点:

  1. 整个 metro 进行依赖剖析和 babel 转换次要通过了JestHasteMap 去做依赖剖析;
  2. 在做依赖剖析的通过,metro 会监听当前目录的文件变动,而后以最小变动生成最终依赖关系图谱;
  3. 不论是入口文件解析还是 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.loadDeltaCalculator之后,生成的依赖图谱格局如下:

{  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 的数据结构:其次要包含如下几个信息:

  1. 文件相干的依赖关系
  2. 指定 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 , postCodemodules 其对应的别离是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 , postCodemodules 其对应的别离是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,    },  };}

# 总结

  1. 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 定义的代码块 并执行
  1. react-native应用 metro 进行打包次要分为三个步骤: 解析,转化和生成;
  2. 解析和转化局部: Metro Server 应用IncrementalBundler进行 js 代码的解析和转换

在 Metro 应用IncrementalBundler进行解析转换的次要作用是:

  • 返回了以入口文件为入口的所有相干依赖文件的依赖图谱和 babel 转换后的代码
  • 返回了var 定义局部及 polyfill 局部所有相干依赖文件的依赖图谱和 babel 转换后的代码

整体流程如图所示:

通过上述的流程咱们总结如下几点:

  1. 整个 metro 进行依赖剖析和 babel 转换次要通过了JestHasteMap 去做依赖剖析;
  2. 在做依赖剖析的通过,metro 会监听当前目录的文件变动,而后以最小变动生成最终依赖关系图谱;
  3. 不论是入口文件解析还是 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) {}}]
  1. metro 代码生成局部应用 baseJSBundle 失去代码,并应用 baseToString 拼接最终 Bundle 代码

baseJSBundle 中:

  • baseJSBundle整体调用了三次 processModules别离用于解析出: preCode , postCodemodules 其对应的别离是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 打包流程简析)