React: webpack模块组织关系

现代前端开发离不开打包工具,以 webpack 为代表的打包工具已经成为日常开发必备之利器,拿 React 技术栈为例,我们 ES6 形式的源代码,需要经过 webpack 和 Babel 处理,才能生成发布版文件,在浏览器中运行。今天就结合 React 来梳理一下 webpack 打包时模块的组织结构,先给定下面一个简单的应用示例:

import React from 'react';
import ReactDOM from 'react-dom';

import {greet} from './utils';

const App = <h1>{greet('scott')}</h1>;

ReactDOM.render(App, document.getElementById('root'));

代码中的 utils 模块如下:

export function greet(name) {
  return `hello ${name}`;
}

如果编译该示例代码,由于要将第三方库一起打包,最终生成的目标代码会比较多,所以我们先在 webpack.config.prod.js 中将 React 和 ReactDOM 配置为 externals,不将它们编译到目标代码中,而是在运行时直接从外部取值。配置如下:

let appConfig = {
  entry: './src/index.js',
  externals: {
    'react': 'React',
    'react-dom': 'ReactDOM',
  },
  output: {
    path: './dist',
    filename: 'index.js'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  },
};

运行 webpack,生成的目标代码如下:

(function (modules) {
  // 存放已加载的模块
  var installedModules = {};

  // 加载函数
  function __webpack_require__(moduleId) {
    // 如果该模块已被加载 直接返回module.exports
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }

    var module = installedModules[moduleId] = {
      exports: {},
      id: moduleId,
      loaded: false
    };

    // 调用模块函数 加载相应的模块
    // 参数是(module, exports[, __webpack_require__])
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

    // 标记该模块已被加载
    module.loaded = true;

    // 最后也返回exports
    return module.exports;
  }

  // 暴露modules和installedModules对象
  __webpack_require__.m = modules;
  __webpack_require__.c = installedModules;

  // __webpack_public_path__
  __webpack_require__.p = "";

  // 加载主模块
  return __webpack_require__(0);
})
/* 以下是模块列表 作为参数被加载 */
([
  /* 0 主模块 */
  (function (module, exports, __webpack_require__) {
    'use strict';

    // 取索引为1的React模块
    var _react = __webpack_require__(1);

    var _react2 = _interopRequireDefault(_react);

    // 取索引为2的ReactDOM模块
    var _reactDom = __webpack_require__(2);

    var _reactDom2 = _interopRequireDefault(_reactDom);

    // 取索引为3的utils模块
    var _utils = __webpack_require__(3);

    // 模块取值 不包含__esModule:true的是默认导出
    function _interopRequireDefault(obj) {
      return obj && obj.__esModule ? obj : { default: obj };
    }

    // 开始渲染视图
    
    var App = _react2.default.createElement(
      'h1',
      null,
      (0, _utils.greet)('scott')
    );

    _reactDom2.default.render(App, document.getElementById('root'));
  }),
  /* 1 React模块 */
  (function (module, exports) {
    module.exports = React;
  }),
  /* 2 ReactDOM模块 */
  (function (module, exports) {
    module.exports = ReactDOM;
  }),
  /* 3 utils模块 */
  (function (module, exports) {
    "use strict";

    Object.defineProperty(exports, "__esModule", {
      value: true
    });
    
    exports.greet = greet;
    
    function greet(name) {
      return "hello " + name;
    }
  })
]);

上面就是基本的模块加载机制。其实,有了 externals 配置,我们也可以不在代码中引入 React 和 React-DOM,下面稍微修改一下代码:

import {greet} from './utils';

const App = <h1>{greet('scott')}</h1>;

ReactDOM.render(App, document.getElementById('root'));

转译后的代码如下:

(function (modules) {
  // ...
})
/* 以下是模块列表 作为参数被加载 */
([
  /* 0 主模块 */
  (function (module, exports, __webpack_require__) {
    'use strict';

    // 取索引为1的utils模块
    var _utils = __webpack_require__(1);

    // 开始渲染视图
    
    var App = React.createElement(
      'h1',
      null,
      (0, _utils.greet)('scott')
    );

    ReactDOM.render(App, document.getElementById('root'));
  }),
  /* 1 utils模块 */
  (function (module, exports) {
    "use strict";

    Object.defineProperty(exports, "__esModule", {
      value: true
    });
    
    exports.greet = greet;
    
    function greet(name) {
      return "hello " + name;
    }
  })
]);

可以看到,代码清晰了不少,主模块中直接调用 React.createElement() 来创建虚拟 DOM 对象,最后调用 ReactDOM.render() 方法来渲染真实的 DOM 结点。访问下面的入口文件,我们就可以看到程序运行的结果:

<!DOCTYPE html>
<html>
  <body>
    <div id="root"></div>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
    <script src="index.js"></script>
  </body>
</html>
posted @ 2018-07-07 23:13  liuhe688  阅读(420)  评论(0编辑  收藏  举报