Hibop.github.io icon indicating copy to clipboard operation
Hibop.github.io copied to clipboard

关于webpack学习之模块篇——五种模块化兼容方案学习

Open Hibop opened this issue 7 years ago • 5 comments

webpack作为前端全能打包工具,在模块化解决方案上很全能。学习这五种模块化解决方案可以加深我们队前端模块化理解,也可以加深我们对webpack的工作原理的理解。

模块化规范分类:

  • commonjs同步加载:代表nodejs,由于node后台异步操作IO很快,所以同步加载具有很好优势。 require/module.exports

  • ADM异步加载:requirejs

    define("defineModule", ["dependentModule"], function(dependentModule){
         //callback
    })
    require(["module-a", "module-b"], function(a, b) {

    }
  • es6 Module import&&export
export default class Component() {} // 等价export {Component as default}
export function a() {}
var x = 'xxx'
export {x} // 其实是{x: x}
setTimeout(() => x = 'YYY', 1000) // export之后,x还可以被修改
export var y = 100

// 错误语法
export 1;
var a = 100;
export a; // 不能解构{100: 100}

  1. import则不同,是编译时的(require是运行时的),必须放在文件开头;
  2. 理解上,require是赋值过程,import是解构过程
  3. 目前所有的引擎都还没有实现import,我们在node中使用babel支持ES6,也仅仅是将ES6转码为ES5再执行,import语法会被转码为require。
  4. esModule要搞清楚几个关键词:as、*、default、{}
  • 正常引入加括号: import {each,map} from 'jquery';
  • as: 别名import {fun as a} from './a';
  • *: 全量引入 import * as a from './a' ;
  • export default 引入时 import sth(不加括号解构) from './adda';
import a from './d';
// 等效于,或者说就是下面这种写法的简写,是同一个意思
import {default as a} from './d';

【天坑】 export default fn 如果在router中require引入时必须:require(./dir/src...).default, 否则模块报错 因此在实际使用过程中定好标准:webpack使用require模块引入,而项目业务代码统一es6

  • webpack 扩展异步加载:require.ensure();
require.ensure(
	['./common'], // 预加载,先加载请求,不执行。可以为空,为空时异步加载
	function(require){
	  var list = require('./common'); // 异步加载执行
	  list.show(); // 使用加载模块中函数
	},
	'dist/commonChunk' // 打包chunk自定义。否则common.[hash].js
);
  • webpack 扩展预加载:require.include()
require.ensure([], function(require){
  require.include('./preview'); //加载
  let p = require('./preview'); //执行
  p.getUrl(); //使用
}, 'pre');

模块化实现原理——IIFE(立即执行函数)

因为浏览器本身不支持模块化,那么webpack和require等模块化工具就用函数作用域来hack模块化的效果

整个打包生成的代码是一个IIFE(立即执行函数)。函数参数是我们写的各个模块组成的数组,只不过我们的代码,被webpack包装在了一个函数的内部,也就是说我们的模块,在这里就是一个函数。

在debug node代码时,你会发现一样的hack方式,node中的模块也是函数,跟模块相关的参数exports、require,或者其他参数__filename和__dirname等都是通过函数传值作为模块中的变量,模块与外部模块的访问就是通过这些参数进行的,当然这对开发者来说是透明的。

同样的方式,webpack也控制了模块的module、exports和require,那么我们就看看webpack是如何实现这些功能的。

webpack模块化代码生成分析:

  1. 打开webpack打包的代码,整体可以简化成下面的结构:
 (function (modules) {
// 1、模块缓存对象
var installedModules = {};
// 2、webpack实现的require
function __webpack_require__(moduleId) {
    // 3、判断是否已缓存模块
    if(installedModules[moduleId]) {
        return installedModules[moduleId].exports;
    }
    // 4、缓存模块
    var module = installedModules[moduleId] = {
        i: moduleId,
        loaded: false,
        exports: {}
    };
    // 5、调用模块函数,这里面call调用绑定的执行上下文是module.exports
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    // 6、标记模块为已加载
    module.loaded = true;
    // 7、返回module.exports
    return module.exports;
}
// 8、require第一个模块,并执行入口函数
return __webpack_require__(__webpack_require__.s = 0);
})
([
function (module, exports, __webpack_require__) {
    /* 模块index.js的代码,入口代码 */
},
function (module, exports, __webpack_require__) {
    /* 模块bar.js的代码 */
}
]);

整个打包生成的代码是一个IIFE(立即执行函数)。函数参数是我们写的各个模块组成的数组,只不过我们的代码,被webpack包装在了一个函数的内部,也就是说我们的模块,在这里就是一个函数。

初始化函数

在执行模块函数之前,IIFE函数体内实现需要函数参数的初始化:

  1. IIFE首先定义了installedModules ,这个变量被用来缓存已加载的模块。
  2. 定义了__webpack_require__ 这个函数,函数参数为模块的id。这个函数用来实现模块的require。
  3. webpack_require 函数首先会检查是否缓存了已加载的模块,如果有则直接返回缓存模块的exports。
  4. 如果没有缓存,也就是第一次加载,则首先初始化模块,并将模块进行缓存。
  5. 然后调用模块函数,也就是前面webpack对我们的模块的包装函数,将module、module.exports和__webpack_require__作为参数传入。注意这里做了一个动态绑定,将模块函数的调用对象绑定为module.exports,这是为了保证在模块中的this指向当前模块。
  6. 调用完成后,模块标记为已加载。
  7. 返回模块exports的内容。
  8. 利用前面定义的__webpack_require__ 函数,require第0个模块,也就是入口模块。

模块函数

function(module, exports, __webpack_require__) {
    "use strict";
    var bar = __webpack_require__(1);
    bar();
}

对于模块化函数一般会有三个参数:

  • module是当前缓存的模块,包含当前模块的信息和exports;
  • exports是module.exports的引用,暴露模块这也符合commonjs的规范,一般就是export.hello= 'hello'
  • webpack_require 则是require的实现。 在模块中,就可以对外使用module.exports或exports进行导出,使用__webpack_require__导入需要的模块,代码跟commonjs完全一样。

webpack的模块缓存:

在webpack中通过require加载模块,我们看到初始初始化时有installedModules,这是处理模块缓存问题 webpack管理着这些模块的缓存,如果一个模块被require多次,那么只会有一次加载过程,而返回的是缓存的内容,这也是commonjs的规范。

模块化规范历史

http://www.cnblogs.com/lvdabao/p/js-modules-develop.html

Hibop avatar Jan 21 '18 07:01 Hibop

附上大神的webpack模块化原理三连,搞懂这些,妈妈再也不用担心不理解模块化了

webpack模块化原理-commonjs: https://segmentfault.com/a/1190000010349749; webpack模块化原理-ES module: https://segmentfault.com/a/1190000010955254 webpack模块化原理-Code Splitting: https://segmentfault.com/a/1190000010349749

Hibop avatar Jan 21 '18 08:01 Hibop

webpack之build: dll(生产环境依赖包构建)

  • 关于webpack.DllPlugin({})使用: https://github.com/superpig/blog/issues/6

  • 一个react全家桶强依赖vender包括: ['lodash', 'whatwg-fetch', 'react', 'react-dom', 'react-router', 'redux', 'react-redux', 'redux-thunk', 'redux-promise-middleware', 'immutable', 'redux-immutable', 'react-intl', 'core-js', 'tslib'] 这些包需要用DllPlugin先build出来可以充分利用浏览器缓存;

  • [antd, codemirror, chartjs] 等包并不是每个页面都需要,或者有些只是用其中一部分,所以我们可以做按需加载来 加载这些三方件

Hibop avatar Feb 08 '18 13:02 Hibop

Webpack 入门指迷 : https://segmentfault.com/a/1190000002551952

Hibop avatar Feb 26 '18 03:02 Hibop