基本概念
本质上,webpack是JavaScript的静态模块打包工具。在webpack处理程序的时候,它会递归的构建一张依赖关系图,这张关系图为程序所需的全部模块,然后将它们打包成一个或者多个bundle。在学习webpack前必须掌握以下几个核心概念:
- entry:入口,webpack执行构建的第一步;
- output:出口,webpack所创建bundles的输出位置,默认为 ./dist;
- plugins:插件,打包过程中所依赖的插件,通过 new 来实例化创建;
- loader:模块转换器,webpack是JavaScript的模块打包工具,它只认识JavaScript,所以需要通过loader对其他类型的文件进行转换,在执行webpack操作。
运行流程
Webpack运行流程是一个串行的流程,从开始到结束会依次执行以下流程:
- 获取配置参数:从配置文件和shell读取配置参数,实例化插件;
- 开始编译:将上一步获取的参数用来初始化compiler对象,加载插件(依次调用插件的apply方法),执行compiler对象run方法;
- 确定入口:从配置文件的 entry 获取全部的入口文件;
- 开始编译:根据入口文件获取全部依赖的 loader 并依次执行对文件进行转换;
- 完成编译:执行完上一步之后,得到各个插件之间的依赖关系;
- 输出资源:根据依赖关系生成一个个包含多个模块的 chunk,生成文件输出列表;
- 输出文件:根据配置文件中的 output 生成文件系统,完成构建。
tapable介绍
webpack本质上是一种事件流的机制,它的工作流程是通过将一个个插件串联起来,而实现这一切的就是tapable。tapable库中有很多的类,他们提供不同的事件流执行机制,简称“钩子”。
钩子名称 | 执行方式 | 说明 |
---|---|---|
SyncHook | 同步串行 | 不关心监听函数的返回值,按照事件注册顺序依次执行 |
SyncBailHook | 同步串行 | 只要有一个监听函数返回不为空,则跳过之后剩下的所有监听函数 |
SyncWaterfallHook | 同步串行 | 前一个监听函数的返回值会传递给下一个监听函数 |
SyncLoopHook, | 同步循环 | 执行监听函数,当返回值为true时循环执行监听函数,直到返回值为undefined则退出循环执行下一个 |
AsyncParallelHook | 异步并行 | 哪个函数先执行完就先执行哪个函数,不关心监听函数的返回值 |
AsyncParallelBailHook | 异步并行 | 只要有一个监听函数返回不为空,则跳过之后剩下的所有监听函数直至 callAsync ,调用它的回调函数 |
AsyncSeriesHook | 异步串行 | 不关心监听函数的返回值,但是必须执行回调函数,等所有函数执行完毕之后调用 callAsync 的回调函数 |
AsyncSeriesBailHook | 异步串行 | 异步执行监听函数,当有一个函数返回不为空时,则跳过后面的所有监听函数,直接调用 callAsync 的回调函数 |
AsyncSeriesWaterfallHook | 异步串行 | 上一个监听函数的返回值可传递给下一个监听函数 |
compiler & compilation
compiler 对象包含了webpack的全部配置信息(options、loader、plugins等等),它在webpack启动的时候就被实例化,且是全局唯一的,可以简单理解为webpack的实例化
compilation 对象包含当前模块的全部资源信息,webpack运行时每检测到一个文件发生变化就会重新生成一个compilation,它提供了很多回调函数供插件扩展。
一些重要的钩子函数
事件钩子 | 说明 | 参数 | 类型 |
---|---|---|---|
entry-option | 初始化option | - | SyncBailHook |
run | 开始读取records之前,钩入compiler | compiler | AsyncSeriesHook |
compile | 一个新的编译(compilation)创建之后,钩入compiler | compilation | SyncHook |
compilation | compilation创建之后,开始执行插件 | compilation | SyncHook |
make | 递归分析依赖,加载依赖模块 | compilation | AsyncParallelHook |
after-compile | 编译过程结束 | compilation | AsyncSeriesHook |
emit | 生成的资源写入到output之前 | compilation | AsyncSeriesHook |
after-emit | 生成的资源写入到output之后 | compilation | AsyncSeriesHook |
done | 完成编译 | stats | AsyncSeriesHook |
failed | 编译失败 | error | SyncHook |
事件钩子 | 说明 | 参数 | 类型 |
---|---|---|---|
normal-module-loader | 普通模块loader,一个接一个的加载模块图中所有模块的函数 | loaderContext、module | SyncHook |
seal | 编译(compilation)停止接收新的模块 | - | SyncHook |
optimize | 优化开始 | - | SyncHook |
optimize-modules | 优化模块 | modules | SyncBailHook |
optimize-chunks | 优化chunk | chunks | SyncBailHook |
additional-assets | 为编译创建时附加资源 | - | AsyncSeriesHook |
optimize-chunk-assets | 优化所有的chunk资源,资源会被存储在compilation.assets | chunks | AsyncSeriesHook |
optimize-assets | 优化存储在compilation.assets中的所有资源 | assets | AsyncSeriesHook |
实现一个简单的loader
本质上来说loader是一个node模块。它可以加载资源文件,并对这些文件进行解析等操作,它支持链式调用、依次调用。
loader的配置一般在 webpack.base.confi.js
的 module
中。
module: {
rules: [
{
test: '/\.txt$/',
loader: 'my-loader'
}
...
]
}
下面是一个简单loader例子:
// my-loader.js
module.exports = function(source) {
source = source.split('').join('-');
return `module.exports = '${source}'`
}
//在vue项目中引入
<template>
<div id="app">
<div>{{txt}}</div>
</div>
</template>
<script>
import txt from './assets/mytxt.txt'
export default {
name: 'App',
data() {
return {
txt: txt
}
},
...
}
</script>
实现一个简单的plugin
一个插件由以下构成
- 一个具名 JavaScript 函数。
- 在它的原型上定义
apply
方法。 - 指定一个触及到 webpack 本身的 事件钩子(compiler)。
- 操作 webpack 内部的实例特定数据。
- 在实现功能后调用 webpack 提供的 callback。
plugin的配置一般在 webpack.dev.conf.js
的 plugins
中。
const hcxPlugin = require('./hcx.webpack.js');
plugins: [
new hcxPlugin({data: 'hcx'}),
...
]
插件代码:
function hcxPlugin() {}
hcxPlugin.prototype.apply = function(compiler) {
compiler.plugin('emit', function(compilation, callback) {
compilation.chunks.forEach(function(chunk) {
chunk.modules.forEach(function(module) {
})
chunk.files.forEach(function(filename) {
var source = compilation.assets[filename].source();
// do something
...
})
})
callback();
})
}
module.exports = hcxPlugin;