首先,说明两种不同的模块规范:
- commonjs规范:node应用是由模块组成的,每一个文件都是一个模块,拥有自己的变量、作用域和方法。每个模块内部都会包含一个对象module,这个对象的exports属性是对外的接口,每次加载这个模块都是加载这个属性中的内容,require方法用于加载某个模块。
- ES6模块规范:ES6模块规范是我们现在常用的,它是通过import进行模块导入、通过export进行模块导出。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
webpack模块化原理
直接上demo:
// webpack.config.js
const path = require('path');
module.exports = {
entry: './a.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
}
};
// a.js
import a from './c';
export default 'a.js';
console.log(a);
// c.js
export default 333;
// 打包之后的代码
(function(modules) {
function __webpack_require__(moduleId) {
var module = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module, module.exports, __webpack_require__);
return module.exports;
}
return __webpack_require__(0);
})([
// 入参数组
(function (module, __webpack_exports__, __webpack_require__) {
// 引用 模块 1
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__c__ = __webpack_require__(1);
/* harmony default export */ __webpack_exports__["default"] = ('a.js');
console.log(__WEBPACK_IMPORTED_MODULE_0__c__["a" /* default */]);
}),
(function (module, __webpack_exports__, __webpack_require__) {
// 输出本模块的数据
"use strict";
/* harmony default export */ __webpack_exports__["a"] = (333);
})
]);
打包之后的代码就是webpack运行时的代码,包括webpack模块的实现。
我们打包之后的代码其实是一个自执行函数。
(function(modules) {
})([]);
自执行函数的入参是各个文件通过babel转码之后组成的code模块数组。
重点在于__webpack_require__函数的实现(webpack自己实现的require函数),这个函数就是webpack自己实现的require函数。该函数本质上就是执行一个模块的代码,然后将相应的导出变量挂载在module.exports对象上。这样就可以在其他模块通过__webpack_require__引入。
注:webpack在最后生成文件的时候会将代码中的require方法替换成__webpack_require__,否则会报错:require is not defined.
在自执行函数执行的最后,会将主文件的输出模块module.exports返回出来,便于日后我们可以require这个包的内容。
最后:这样打包之后的js文件,并不能被其他的文件引用,因为它只作用于当前作用域,这个js文件不能被其他模块通过import或者require的方式引用。
webpack编译后的文件如何被其他模块引用?
webpack通过output.libraryTarget属性来设置打包输出的包的引用方式。
打包输出方式可以参见我的另一篇博客
output.libraryTarget :commonjs2;
会将打包过的代码赋值给module.exports。
这样,就可以在别的模块利用require引用这个模块。
babel的作用
babel在webpack中用来转换es6语法,那它是怎么转换es6语法的呢?
1.处理导出模块
export default 123;
export const a = 123;
const b = 3;
const c = 4;
export { b, c };
babel会将这些都转换成commonjs的exports写法:
exports.default = 123;
exports.a = 123;
exports.b = 3;
exports.c = 4;
exports.__esModule = true;
exports.__esModule = true;表示是由ES6转换来的。
2.处理默认的导入模块
import a from './a.js';
function _interopRequireDefault(obj) {
return obj && obj.__esModule
? obj
: { 'default': obj };
}
var _a = require('assert');
var _a2 = _interopRequireDefault(_a);
var a = _a2['default'];
最后的 a 变量就是 require 的值的 default 属性。如果原先就是commonjs规范的模块,那么就是那个模块的导出对象。
3.引入 * 通配符
import * as a from './a.js'
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj;
}
else {
var newObj = {}; // (A)
if (obj != null) {
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key))
newObj[key] = obj[key];
}
}
newObj.default = obj;
return newObj;
}
}
4.import { a } from './a.js’
直接转换成 require(’./a.js’).a 即可。
总结:由于babel会帮我们将ES6的模块转换为commonjs,所以我们在引入的时候可以混合使用ES6和commonjs规范。
为什么有时候需要使用require(’’).default来引用模块?
babel在对模块进行转换的时候,会把es6的export default转换为exports.default属性,即使这个模块只有一个输出。所以引用的时候我们需要加上default属性来引用这个模块的默认导出。
再来一波模块化预习复习(预习)
- 前端模块化方案有哪些?
commonjs、AMD、CMD、ESModule等。
- Commonjs
Commonjs是node服务端使用的规范。用同步的方式去加载模块,由于是在服务端,各个模块文件都存储在本地磁盘,同步的方式读取很快,不会出现问题。
2.AMD
AMD规范采用异步的方式去加载模块。模块的加载不会影响到后面代码的实现。所有依赖这个模块的代码,都放在一个回调函数中,当模块加载完毕后才会执行回调。
3.CMD
与AMD类似,CMD推崇后置依赖,在用到某个模块时,才去加载它,而AMD是提前加载所有的模块。
4.ES6 Module
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
- webpack是如何实现模块化的?
在webpack打包时,babel会将ES6模块转换成commonjs规范,但是我们前面不是说commonjs不能用在浏览器端吗?为什么这里可以?这是因为webpack自己实现了一个require函数,用来加载commonjs模块的内容,使得最后的打包代码可以在浏览器中使用。