react-native bundle 到 bundle 生成到底發生了什麼(metro 打包流程簡析)

本文涉及 react-native及 metro 版本

先來看一波本文的實例代碼:很簡單吧,一個你好,世界

// 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;運行上述命令之後,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.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 命令,但是具體的實現卻使用了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打包的具體實現
  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.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)進行打包,其實是使用傳入的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,
});

此處的_bundler是 IncrementalBundler 的實例,它的 buildGraph 函數完成了打包過程中前兩步 Resolution 和 Transformation 。 下面我們就來詳細查看一下 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.js

buildGraph(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.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是一個用於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.js
function _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 , postCode 和 modules 其對應的分別是var 和 polyfills 部分的代碼 , require 部分的代碼 , \_d 部分的代碼
  • processModules 經過兩次 filter 過濾出所有類型爲 js/類型的數據,第二次過濾使用用戶自定義 filter 函數;過濾完成之後使用 wrapModule 轉換成_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依賴關係圖譜和代碼,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.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)後面兩個參數生成
  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.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插入對應代碼
  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,
    },
  };
}

# 總結

  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 , 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 打包流程簡析)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章