webpack简介
基本概念
-
Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。 -
Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。 -
Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。 -
Loader:loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。 -
Plugin:插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务,扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情
vue2.0以后使用vue.config.js,对应plugins放到configureWebpack中
module.exports = {
pages: pages,
configureWebpack: {
// 环境模式:
// 开发环境,打包时代码不压缩,默认开启调试模式
mode: 'development',
// 生产环境,打包发布到线上的,默认开启代码压缩,代码混淆,未开启代码调试
// mode: 'production'
// 打包时的入口文件路径,即从哪个路径进行打包,webpack以js文件为入口文件
entry: './src/main.js',
// 打包时的出口路径,即打包好后的文件路径
output: {
// 输出的目录,与webpack.config.js对比,如不生成在当前目录,输出目录需给全路径
// path: path.join(__dirname, 'dist'),
// __dirname为node对象,即当前目录
path: path.join(__dirname, 'dist'),
// js文件打包好后的文件名及位置,[name]指向entry中的key值,此时是main
filename: 'js/[name].bundle.js'
},
module: {
rules: [
{ test: /.txt$/, use: 'raw-loader' }
]
},
plugins: [new CustomPlugin({pages})]
}
工作流程
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
-
初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
-
开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
-
确定入口:根据配置中的 entry 找出所有的入口文件;
-
编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
-
完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
-
输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
-
输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
comiler vs compilation 对象
Compiler 负责监听文件和启动编译 它可以读取到 webpack 的 config 信息,整个 Webpack 从启动到关闭的生命周期,一般只有一个 Compiler 实例,整个生命周期里暴露了很多方法,常见的 run,make,compile,finish,seal,emit 等,我们写的插件就是作用在这些暴露方法的 hook 上
Compilation 负责构建编译。 每一次编译(文件只要发生变化,)就会生成一个 Compilation 实例,Compilation 可以读取到当前的模块资源,编译生成资源,变化的文件,以及依赖跟踪等状态信息。同时也提供很多事件回调给插件进行拓展
什么是plugin?
plugin是插件的意思,通常用于对现有的架构进行扩展。webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化,文件压缩等等。
loader和plugin的区别:
loader主要用于转换某些类型的模块,是一个加载器;
plugin是插件,对webpack本身进行扩展,是一个扩展器;
plugin的使用步骤:
通过npm 安装需要使用的plugins(有些内置的不需要再安装);
在webpack.config.js中的plugins中配置插件;
那如何自定义plugin
webpack 插件是一个具有 apply 属性的 JavaScript 对象。apply 属性会被 webpack compiler 调用,并且 compiler 对象可在整个编译生命周期访问
实现一个html转换ejs的插件
- 第一步创建CustomPlugin,利用Compiler Hooks中钩子函数emit
-
emit: 生成资源到output目录之前。 -
entryOption: 在 webpack 选项中的entry配置项 处理过之后,执行插件。 -
afterPlugins: 设置完初始插件之后,执行插件。 -
compilation: 编译创建之后,生成文件之前,执行插件。 -
done: 编译完成。
在 compiler.hooks 下指定事件钩子函数,便会触发钩子时,执行回调函数。
Webpack 继承自Tapable 类,Tapable提供三种触发钩子的方法,可以使用这些方法,注入自定义的构建步骤,这些步骤将在整个编译过程中不同时机触发:能否使用同步异步触发,取决于钩子函数是否支持同步和异步
-
tap:以同步方式触发钩子; -
tapAsync:以异步方式触发钩子; -
tapPromise:以异步方式触发钩子,返回 Promise;
-
compiler.hooks.emit.tap利用tap触发钩子,传递插件名称,传递的方法中利用compilation获取资源 -
compilation.assets是存放当前所有即将输出的资源,获取到对应的页面html -
通过拿到的资源名称,和内容
compilation.assets[filename].source(),写入到ejs文件中,在这个过程也可以实现其他的预置公共内容 -
vue.config.js 中对应配置使用
plugins: [new CustomPlugin({pages})] -
build后会对应生成ejs文件
const pluginName = 'custom-plugin';
class CustomPlugin {
constructor (options) {
this.options = Object.assign({}, options)
}
apply (compiler) {
compiler.hooks.emit.tap(pluginName, compilation => {
let templateNames = []
for (let key in this.options.pages) {
// 获取模版名称
let filename = this.options.pages[key].filename || `${key}.html`
if (compilation.assets[filename]) {
templateNames.push(filename)
// 获取对应文件内容
let htmlStr = compilation.assets[filename].source()
let htmlName = filename.split('.')[0]
// 写入新文件
await this.writeEjsFile(compilation, htmlName, htmlStr)
}
}
if (templateNames.length === 0) {
console.log(`${pluginName}----文件模版名不正确,${pluginName}停止`)
}
// 输出部署路径log
this.printLog(compilation)
})
}
// 输出ejs文件
async writeEjsFile (compilation, htmlName, htmlStr) {
let url = `./${htmlName}/${htmlName}.${env}.ejs`
await this.writeFile(compilation, url, htmlStr)
}
// 写文件
writeFile (compilation, url, data) {
compilation.assets[url] = {
source: function() {
return data;
},
size: function() {
return data.length;
}
}
}