1、webpack的模块化原理
webpack 本身维护了一套模块系统,这套模块系统兼容了所有前端历史进程下的模块规范,包括 amd commonjs es6 等
(function(modules) {
function __webpack_require__(moduleId) {
var module = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
return module.exports;
}
return __webpack_require__(0);
})([(function (module, __webpack_exports__, __webpack_require__) {
// 模块的逻辑代码
...
})]
原理的关键有两点:
1、所有的模块都被封装成function (module, webpack_exports, webpack_require){}的形式,
2、webpack_require函数,该函数内部有一个module对象,该对象有两个重要属性:i代表模块id,exports代表要暴露给外面的对象。每次调用webpack_require时都会传入一个数字作为模块id,这样不同模块就能被区分,另外该函数将module.exports作为返回值。每个模块的导出值都会被记录在module.exports对象里,其他依赖该模块的模块就能取到对应数据
2、webpack2是如何支持import和require两种引入模块的方式
首先webpack是基于node的,node的模块规范是commonjs,该规范就是使用require和module.exports来引入和导出模块的;其次,es6 module是静态的依赖,所以可以在运行前进行代码转换。import的本质其实就是require,webpack在进行静态分析时会将其转为require。这也说明了在webpack中可以混用import和require,因为二者本质上都是require
3、如何使用 webpack 的 tree-shaking 技术
webpack 的 tree-shaking 基于ES6的模块静态依赖机制,babel也是可以将ES6模块转换为commenjs模块的,但是你一旦这样做了就会失去tree-shaking技术。所以在使用babel转换ES6时一般会如下配置,即只使用bable的ES6语法转换能力,不使用它的模块处理,而是使用webpack2自己的模块处理。
presets: [['babel-preset-es2015', {modules: false}]]
需要说明的是,即使在 引入模块时使用了 es6 ,但是引入的那个模块却是使用 commonjs 进行输出,这也无法使用tree-shaking。
而第三方库大多是遵循 commonjs 规范的,这也造成了引入第三方库无法减少不必要的引入。
所以对于未来来说第三方库要同时发布 commonjs 格式和 es6 格式的模块。es6 模块的入口由 package.json 的字段 module 指定。而 commonjs 则还是在 main 字段指定。
4、import及export转换为require和module.exports的规则是啥样的
- es6 的 export default 都会被转换成 exports.default
- export 的所有输出都会添加到module.exports对象上
- 使用require去引用ES6模块的export default输出时,注意用require('**').default
- 导出
// ES6
export default 123;
export const a = 123;
const b = 3;
const c = 4;
export { b, c };
// 转换后
exports.default = 123;
exports.a = 123;
exports.b = 3;
exports.c = 4;
exports.__esModule = true;
babel 转换 es6 的模块输出逻辑非常简单,即将所有输出都赋值给 exports,并带上一个标志 __esModule 表明这是个由 es6 转换来的 commonjs 输出。
- 导入
// 1、普通导入
import a from './a.js' //引入一个 es6 模块中的 default 输出
// babel会这么转
function _interopRequireDefault(obj) {
return obj && obj.__esModule
? obj
: { 'default': obj };
}
var _a = require('./a.js');
var _a2 = _interopRequireDefault(_a);
var a = _a2['default'];
// 2、通配符引入
import * as a from './a.js' //将 es6 模块的所有命名输出以及defalut输出打包成一个对象赋值给a变量
// Babel内部这么转
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;
}
}
// 3、具名引入
import { a } from './a.js' // 将 es6 模块的a引入
//babel这样转
require('./a.js').a
5、plugin && loader
- compiler & compilation 对象
compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并在所有可操作的设置中被配置,包括原始配置,loader 和插件。当在 webpack 环境中应用一个插件时,插件将收到一个编译器对象的引用。可以使用它来访问 webpack 的主环境。
compilation 对象代表了一次单一的版本构建和生成资源。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,一次新的编译将被创建,从而生成一组新的编译资源。一个编译对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。编译对象也提供了很多关键点回调供插件做自定义处理时选择使用。
compiler 是 webpack 环境的代表,compilation 则是 webpack 构建内容的代表,它包含了每个构建环节及输出环节所对应的方法,存放着所有 module、chunk、asset 以及用来生成最后打包文件的 template 的信息。
- 事件钩子
事件钩子其实就是类似 MVVM 框架的生命周期函数,在特定阶段能做特殊的逻辑处理。了解一些常见的事件钩子是写 webpack 插件的前置条件
- 插件模板
function MyPlugin(options) {}
this.opt = options
//1.函数原型上的 apply 方法会注入 compiler 对象
MyPlugin.prototype.apply = function(compiler) {
// 2.compiler 对象上挂载了相应的 webpack 事件钩子
compiler.plugin('emit', (compilation, callback) => { // 3.事件钩子的回调函数里能拿到编译后的 compilation 对象
...
})
}
module.exports = MyPlugin
- Loader
1、loader 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!
2、css-loader 解析 @import 和 url()路径中指定的css内容
3、style-loader 会把原来的 CSS 代码插入页面中的一个 style 标签中
- Loader 模版
// loaderUtils 可以获取 webpack.config.js 中的配置
var loaderUtils = require('loader-utils');
module.exports = function(source) {
console.log("start process code...");
var options = loaderUtils.getOptions(this) || {};
if(options !== {}) {
var replaceMap = options["replaceMap"];
for(var key in replaceMap) {
console.log(source);
source = source.replace(key, replaceMap[key]);
console.log(source);
}
}
return source;
};