构建工具-----webpack


Webpack

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

  • code-splitting 、tree-shaking 、scope hoisting
  • Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
  • Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
  • Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。webpack使用 code splitting 后的产物,也就是按需加载的分块,装载了不同的 module
  • Loader:模块转换器,用于把模块原内容按照需求转换成新内容。如何转化处理某一类型的文件。比如说分析转换scss为css,或者把下一代的JS文件(ES6,ES7)转换为现代浏览器兼容的JS文件。
  • Plugin:扩展插件,在 Webpack 构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要的事情。用来拓展Webpack功能。hot-module-replacement 热模块更新、HappyPack 开启多线程,并行执行、html-webpack-plugin 可以在项目文件夹下自动生成index.html。
  • Output:输出结果,在 Webpack 经过一系列处理并得出最终想要的代码后输出结果。

出处:关于webpack4的14个知识点,童叟无欺

运行过程

初始化配置参数 -> 绑定事件钩子回调 -> 确定Entry逐一遍历 -> 使用loader编译文件 -> 输出文件

Webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。Webpack 通过 Tapable 来组织这条复杂的生产线。 Webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 Webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。

​ ———-吴浩麟《深入浅出webpack》

Tapable的原理其实就是我们在前端进阶过程中都会经历的EventEmit,通过发布者-订阅者模式实现,它的部分核心代码可以概括成下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SyncHook{
constructor(){
this.hooks = [];
}

// 订阅事件
tap(name, fn){
this.hooks.push(fn);
}

// 发布
call(){
this.hooks.forEach(hook => hook(...arguments));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Event {
constructor() {
// 存储事件的数据结构
// 为了查找迅速,使用了对象(字典)
this._cache = {};
}
// 绑定
on(type, callback) {
// 为了按类查找方便和节省空间,
// 将同一类型事件放到一个数组中
// 这里的数组是队列,遵循先进先出
// 即先绑定的事件先触发
let fns = (this._cache[type] = this._cache[type] || []);
if (fns.indexOf(callback) === -1) {
fns.push(callback);
}
return this;
}
// 触发
trigger(type, data) {
let fns = this._cache[type];
if (Array.isArray(fns)) {
fns.forEach((fn) => {
fn(data);
});
}
return this;
}
// 解绑
off(type, callback) {
let fns = this._cache[type];
if (Array.isArray(fns)) {
if (callback) {
let index = fns.indexOf(callback);
if (index !== -1) {
fns.splice(index, 1);
}
} else {
//全部清空
this._cache[type] = null
return this;
}
}
}
}
// 测试用例
const event = new Event();
event.on('test', (a) => {
console.log(a);
});
event.trigger('test', 'hello world');
event.off('test');
event.trigger('test', 'hello world');

Loader

loaderwebpack的核心概念之一,它的基本工作流是将一个文件以字符串的形式读入,对其进行语法分析及转换,然后交由下一环节进行处理,所有载入的模块最终都会经过moduleFactory处理,转成javascript可以识别和运行的代码,从而完成模块的集成。

loader支持链式调用,所以开发上需要严格遵循“单一职责”原则,即每个loader只负责自己需要负责的事情:将输入信息进行处理,并输出为下一个loader可识别的格式。

  • 基本原理

    loader只是一个普通的funciton,它会传入匹配到的文件内容(String),你只需要对这些字符串做些处理就好了

  • 例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    module.exports = function(source){
    var tpl="";
    source.split(/\r?\n/).forEach(function(line){
    line=line.trim();
    if(!line.length){
    return;
    }
    //对line进行处理...
    tpl+=line;
    });
    return "var tpl=\'" + tpl + "\'\nmodule.exports = tpl";
    }

Plugin

  • 本质:一个带有apply方法的class。

  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class BasicPlugin{
    // 在构造函数中获取用户给该插件传入的配置
    constructor(options){
    //用户自定义配置
    this.options = options
    console.log(this.options)
    }

    // Webpack 会调用 BasicPlugin 实例的 apply 方法给插件实例传入 compiler 对象
    apply(compiler){
    compiler.plugin('compilation',function(compilation) {
    })
    }
    }

    // 导出 Plugin
    module.exports = BasicPlugin;

    执行过程:

    1. Webpack 启动后,在读取配置的过程中会先执行 new BasicPlugin(options) 初始化一个 BasicPlugin 获得其实例。
    2. 在初始化 compiler 对象后,再调用 basicPlugin.apply(compiler) 给插件实例传入 compiler 对象。
    3. 插件实例在获取到 compiler 对象后,就可以通过 compiler.plugin(事件名称, 回调函数) 监听到 Webpack 广播出来的事件。并且可以通过 compiler 对象去操作 Webpack。
  • Compiler 和 Compilation

    • Compiler 对象包含了 Webpack 环境所有的的配置信息,包含 options,loaders,plugins 这些信息,这个对象在 Webpack 启动时候被实例化,它是全局唯一的,可以简单地把它理解为 Webpack 实例;
    • Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。当 Webpack 以开发模式运行时,每当检测到一个文件变化,一次新的 Compilation 将被创建。Compilation 对象也提供了很多事件回调供插件做扩展。通过 Compilation 也能读取到 Compiler 对象。
    • Compiler 和 Compilation 的区别在于:Compiler 代表了整个 Webpack 从启动到关闭的生命周期,而 Compilation 只是代表了一次新的编译。