关于webpack学习之模块篇——五种模块化兼容方案学习
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}
- import则不同,是编译时的(require是运行时的),必须放在文件开头;
- 理解上,require是赋值过程,import是解构过程
- 目前所有的引擎都还没有实现import,我们在node中使用babel支持ES6,也仅仅是将ES6转码为ES5再执行,import语法会被转码为require。
- 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模块化代码生成分析:
- 打开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函数体内实现需要函数参数的初始化:
- IIFE首先定义了installedModules ,这个变量被用来缓存已加载的模块。
- 定义了__webpack_require__ 这个函数,函数参数为模块的id。这个函数用来实现模块的require。
- webpack_require 函数首先会检查是否缓存了已加载的模块,如果有则直接返回缓存模块的exports。
- 如果没有缓存,也就是第一次加载,则首先初始化模块,并将模块进行缓存。
- 然后调用模块函数,也就是前面webpack对我们的模块的包装函数,将module、module.exports和__webpack_require__作为参数传入。注意这里做了一个动态绑定,将模块函数的调用对象绑定为module.exports,这是为了保证在模块中的this指向当前模块。
- 调用完成后,模块标记为已加载。
- 返回模块exports的内容。
- 利用前面定义的__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的规范。
模块化规范历史
附上大神的webpack模块化原理三连,搞懂这些,妈妈再也不用担心不理解模块化了
webpack模块化原理-commonjs: https://segmentfault.com/a/1190000010349749; webpack模块化原理-ES module: https://segmentfault.com/a/1190000010955254 webpack模块化原理-Code Splitting: https://segmentfault.com/a/1190000010349749
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] 等包并不是每个页面都需要,或者有些只是用其中一部分,所以我们可以做按需加载来 加载这些三方件
Webpack 入门指迷 : https://segmentfault.com/a/1190000002551952