Appearance
Plugin工作机制
旨在增强webpack自动化能力,Loader专注实现资源模块加载,实现项目整体打包。Plugin解决除了资源加载以外其他自动化工作。相比于Loader,Plugin拥有更宽的能范围。
例如:
- 在打包之前自动清除dist目录
- 拷贝静态文件至输出目录
- 压缩输出代码
有了Plugin的webpack实现了大多数前端工程化的工作。
webpack插件工作原理
webpack插件就是通过在生命周期的钩子中挂载函数实现扩展的:
在webpack工作的过程中有很多的环节,为了便于插件的扩展,webpack给每一个环节都埋下了一个钩子,这样我们可以往不同的节点上挂载不同的任务,就可以轻松去扩展webpack的能力。
webpack钩子有哪些?
webpack常用插件
clean-webpack-plugin
自动清除输出目录插件
原生是同名文件覆盖,其他文件不清理,并不是很合理,合理的是每次打包之前自动清理dist目录。
- 安装插件
npm i clean-webpack-plugin --save-dev
- 在webpack.config.js中写
js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
// 专门用来配置插件的地方,是一个数组,添加一个插件就是添加一个元素
// 类型实例放入数组中
plugins: [
new CleanWebpackPlugin()
]
- 命令行运行
npm run build
可以看到output文件是被清理过后生成的
html-webpack-plugin
这个插件的作用在于自动生成HTML文件
自动生成使用bundle.js的HTML
之前我们是通过硬编码的方式把html放在项目根目录下面的。这种方式有两个问题:
- 我们在发布的时候需要发布根目录下面的index.html文件和dist目录下面所有的打包结果
- 上线之后我们要确保路径和名称没有问题,而且如果文件名要修改的话需要手动去修改。
最好的办法是通过Webpack输出HTML文件,这样我们只需要发布dist文件即可,而且也不需要考虑文件名称和引用路径的问题。
- 安装
npm i html-webpack-plugin --save-dev
- 在webpack.config.js中引用
js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
...,
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin()
]
}
- 命令行中执行
npm run build
,可以看到在output目录下面生成了一个index.html文件 - 如果引用路径有错误,是之前我们设置的publicPath,因为当时的index.html在根目录下面,如果把index.html放在output目录下面,那么就不需要再设置publickPath了,这个时候需要删除。并且把根目录下面的index.html文件删除即可。
给HTML添加自定义配置
- 页面的标题是需要修改的
- 我们还需要去自定义原数据标签和一些基础的DOM结构
对于简单的我们通过html-webpack-plugin的属性来实现
js
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'webpack plugin sample',
meta: {
viewport: 'width=device-width'
}
})
]
对于我们需要大量的修改的话,最好是生成一个模板,让html-webpack-plugin根据模板生成一个页面:
- 在src文件夹下添加一个index.ejs模板
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webpack</title>
</head>
<body>
<div class="container">
<!-- 这个变量是内部变量,我们也可以自己添加自定义变量 -->
<h1><%= htmlWebpackPlugin.options.title %></h1>
<h2><%= htmlWebpackPlugin.options.hello %></h2>
</div>
</body>
</html>
- 在webpack.config.js里面配置模板
js
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'webpack plugin sample',
hello: 'hello~',
meta: {
viewport: 'width=device-width'
},
//在这里设置template模板的路径是src下的index.html文件
template: './src/index.ejs'
})
]
- 命令行运行
npm run build
,可以看到output目录下的index.html被解析出来
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webpack</title>
<meta name="viewport" content="width=device-width"></head>
<body>
<div class="container">
<!-- 这个变量是内部提供的变量,我们也可以自己添加自定义变量 -->
<h1>webpack plugin sample</h1>
<h2>hello~</h2>
</div>
<script src="bundle.js"></script></body>
</html>
同时输出多个页面文件
我们可以在webpack.config.js中再通过new HtmlWebpackPlugin创建新的实例对象
js
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'webpack plugin sample',
hello: 'hello~',
meta: {
viewport: 'width=device-width'
},
template: './src/index.ejs'
}),
new HtmlWebpackPlugin({
filename: 'about.html',
title: 'About'
})
]
就可看到在output目录下新生成了一个about.html文件
copy-webpack-plugin
我们在项目中还有一些不需要构建的静态文件,他们最终也要发布到线上,例如:public目录
- 安装
npm i copy-webpack-plugin --save-dev
- 在webpack中引用
js
const CopyWebpackPlugin = require('copy-webpack-plugin')
new CopyWebpackPlugin({
// 传入数组,用于我们需要copy的文件路径
// 可以是一个通配符,也可以是一个目录或者文件的相对路径
// 'public/**'这个是将public目录下的所有按照目录结构copy到output中
// 下面是把public目录整体打散copy到output目录下
patterns: [{
from: 'public',
// to是到的文件位置,如果不写默认是output目录
// 如果写的话和上面的output属性一样,要写绝对路径
// to: path.join(__dirname, 'output/public')
}]
})
- 在命令行中运行
npm run build
可以看到在output目录中copy了我们需要的文件
PS: 一般在上线前去使用,在平时的打包过程中不会使用这个,因为我们在平时打包比较频繁,如果复制的文件比较多,开销就比较大,打包速度就会降低
其他插件可以去搜索
动手实践:开发一个webpack插件
开发一个删除bundle.js中的无用注释的webpack插件
PS: webpack要求插件必须是一个函数,或者是一个包含apply方法的对象。一般我们会定义一个类,在类中定义一个apply方法,在使用的时候就是通过类型创建一个实例。
- 在webpack.config.js中创建一个类MyPlugin
js
class MyPlugin {
/**
* apply方法会在启动时自动被调用
* @param {object} compiler
* compiler对象参数,是webpack工作中最核心的对象
* compiler里面包含了此次构建的所有配置信息,我们也是通过这个对象去注册钩子函数
*
* 这个插件目的:是为了清除webpack打包中没有必要有的注释,这样去除了注释之后就更加容易阅读
*
* 步骤:需要在bundle.js内容明确之后再进行清除
*/
apply (compiler) {
console.log('MyPlugin 启动')
/**
* emit 这个方法是webpack即将要往输出目录输出之前执行,
* 通过compiler.hooks.emit访问到那个钩子
* 使用tap方法注册一个钩子,两个参数
* 第一个参数是插件名称:MyPlugin
* 第二个参数是执行的函数,并且要接收一个compilation的对象参数,compilation可以理解为此次打包的上下文,所有打包的结果都会放到这个对象中
*/
compiler.hooks.emit.tap('MyPlugin', compilation => {
// 通过assets属性获取即将写入文件目录的资源文件信息,我们需要遍历这个对象,键是每个文件的名称,值的source方法可以访问内容
for (const name in compilation.assets) {
// 键是每个文件的名称
// console.log(name)
// 值的source方法可以访问内容
// console.log(compilation.assets[name].source())
// 匹配后缀是.js文件的文件名
if (name.endsWith('.js')) {
// 获取文件对应的内容
const contents = compilation.assets[name].source()
// 将所有的注释都替换掉
const withoutComments = contents.replace(/\/\*\**\*\//g, '')
// 把现在的变量替换到原来的source方法下,并且要更新一下文件大小size
compilation.assets[name] = {
source: () => withoutComments,
size: () => withoutComments.length
}
}
}
})
}
}
- 在plugins中引用
js
plugins: [
...,
new MyPlugin()
]
- 在命令行中执行
npm run build
就可以看到output中的bundle.js的无用注释被删除了。