什么是插件(plugin)?
plugin 用于扩展webpack的功能。
当然loader也是变相的扩展了webpack ,但是它只专注于转化文件这一个领域。而plugin的功能更加的丰富,而不仅局限于资源的加载。在 webpack 的构建流程中,plugin 用于处理更多其他的一些构建任务。可以这么理解,模块代码转换的工作由 loader 来处理,除此之外的其他任何工作都可以交由 plugin 来完成。
如何使用插件
1. 安装需要的插件
例如:
安装HtmlWebpackPlugin
npm install --save-dev html-webpack-plugin
2. 配置对应的插件
webpack.config.js
// 导入对应的插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [new HtmlWebpackPlugin()]
}
webpack常用的plugin
- 官网介绍plugins
- 第三方插件awesome-webpack
- HotModuleReplacementPlugin –开启全局的模块热替换(HMR);
- NamedModulesPlugin –当模块热替换(HMR)时在浏览器控制台输出对用户更友好的模块名字信息;
- CommonsChunkPlugin –提取chunk公共部分;
- ExtractTextPlugin –独立生成css文件,以外链的形式加载;
- UglifyJsPlugin –压缩js;
- HtmlWebpackPlugin –使用模版生成html。
1. HtmlWebpackPlugin
HtmlWebpackPlugin
HtmlWebpackPlugin会在打包结束之后自动创建一个index.html, 并将打包好的JS自动引入到这个文件中
- 安装HtmlWebpackPlugin
npm install --save-dev html-webpack-plugin
- 配置HtmlWebpackPlugin
webpack.config.js
// 导入HtmlWebpackPlugin插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [new HtmlWebpackPlugin()]
}
- HtmlWebpackPlugin高级使用
默认情况下HtmlWebpackPlugin生成的html文件是一个空的文件, 如果想指定生成文件中的内容可以通过配置模板的方式来实现
例如:
默认情况下生成html文件并没有压缩, 如果想让html文件压缩可以设置module.exports = { plugins: [new HtmlWebpackPlugin({ // 指定打包的模板, 如果不指定会自动生成一个空的 template: "index.html" })] }
minify
属性module.exports = { plugins: [new HtmlWebpackPlugin({ minify: { // 告诉htmlplugin打包之后的html文件需要压缩 collapseWhitespace: true, } })] }
更多高级配置请查看 插件文档
2. CleanWebpackPlugin
webpack-clean-plugin会在打包之前将我们指定的文件夹清空
应用场景:每次打包前将dist目录清空, 然后再存放新打包的内容, 避免新老混淆问题
参考文档
- 安装clean-webpack-plugin
npm install --save-dev clean-webpack-plugin
-
配置clean-webpack-plugin
webpack.config.js
// 导入CleanWebpackPlugin插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [new CleanWebpackPlugin()]
}
3. CopyWebpackPlugin
在打包项目的时候除了JS/CSS/图片/字体图标等需要打包以外, 可能还有一些相关的文档也需要打包
文档内容是固定不变的, 我们只需要将对应的文件拷贝到打包目录中即可, 那么这个时候我们就可以使用copy-plugin来实现文件的拷贝
参考文档
- 安装copy-webpack-plugin
npm install --save-dev copy-webpack-plugin
-
配置copy-webpack-plugin
webpack.config.js
// 导入CleanWebpackPlugin插件
const CopyWebpackPlugin = require('CopyWebpackPlugin');
module.exports = {
plugins: [
new CopyWebpackPlugin([
// 从哪个文件拷贝到哪个文件
{from:"doc", to:"./doc"}
])
]
}
4. MiniCssExtractPlugin
mini-css-extract-plugin是一个专门用于将打包的CSS内容提取到单独文件的插件
前面我们通过style-loader打包的CSS都是直接插入到head中的
- 安装mini-css-extract-plugin
npm install --save-dev mini-css-extract-plugin
-
配置mini-css-extract-plugin
webpack.config.js
// 导入 MiniCssExtractPlugin 插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: './css/[name].css',
})
]
}
-
替换style-loader
webpack.config.js
module.exports = {
plugins: [new MiniCssExtractPlugin()],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
};
因为style-loader的作用是将webpack处理之后的内容插入到HTML的HEAD代码中, 现在我们是需要打包到一个单独的文件中
【注意】
如果相关文件资源无法显示, 需要根据打包后的结构手动设置公开路径
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
},
},
'css-loader',
],
},
],
},
};
-
webpack打包图片路径问题
webpack打包之后给我们的都是相对路径,但是正是因为是相对路径, 所以会导致在html中使用的图片能够正常运行, 在css中的图片不能正常运行
例如: 在以下目录中
打包之后的路径是 images/animal.jpg
那么在html中, 会去html文件所在路径下找images,正好能找到所以不报错
但是在css中, 会去css文件所在路径下找images, 找不到所以报错
|---bundle
|---css
|---index.css
|---js
|---index.js
|---images
|---animal.jpg
|---index.html
解决方案
在开发阶段将publicPath设置为dev-server服务器地址
在上线阶段将publicPath设置为线上服务器地址
5. 压缩CSS代码( OptimizeCSSAssetsPlugin 和 TerserJSPlugin )
压缩用mini-css-extract-plugin插件打包的css代码
参考文档
参考文档
- 安装OptimizeCSSAssetsPlugin
npm install --save-dev optimize-css-assets-webpack-plugin
-
配置OptimizeCSSAssetsPlugin
webpack.config.js
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
// optimization: 配置webpack的优化项
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})],
},
}
【注意】
由于配置了webpack的optimization.minimizer项目会覆盖默认的JS压缩选项, 所以JS代码也需要通过插件自己压缩
- 安装TerserJSPlugin
npm install --save-dev terser-webpack-plugin
-
配置TerserJSPlugin
webpack.config.js
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
module.exports = {
// optimization: 配置webpack的优化项
optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
},
// mode: 指定打包的模式,
// production: 上线(生产)模式, 会对打包的JS代码进行压缩
mode: "production",
}
6. 监听打包文件变化( watch 和 devServer)
-
什么是watch
可以监听打包文件变化,当它们修改后会重新编译打包
参考文档 -
配置watch
webpack.config.js
module.exports = {
// 监听打包文件变化,当它们修改后会重新编译打包
watch: true,
watchOptions: {
aggregateTimeout: 300, // 防抖, 和函数防抖一样, 改变过程中不重新打包, 只有改变完成指定时间后才打包
poll: 1000, // 每隔多少时间(毫秒)检查一次变动
ignored: /node_modules/ // 排除一些巨大的文件夹, 不需要监控的文件夹
},
}
-
什么是webpack-dev-server
webpack-dev-server和watch一样可以监听文件变化
webpack-dev-server可以将我们打包好的程序运行在一个服务器环境下, 可以解决"开发阶段"的跨域问题
参考文档 - 安装webpack-dev-server
npm install webpack-dev-server --save-dev
-
配置webpack-dev-server
webpack.config.js
module.exports = {
devServer: {
// 告诉devServer要把哪个目录运行在服务器环境下
contentBase: "./bundle",
// 编译打包之后是否需要自动在浏览器中打开, 默认为false.
// 如果为false则需要自己在浏览器地址栏输入localhost:8080
open: true,
// 端口号, 默认是8080
port: 9090
},
}
打包指令由原来的
npx webpack
更改为npx webpack-dev-server
-
watch 和 devServer的区别
watch在修改文件后需要手动刷新浏览器, devServer会自动刷新
watch是只能在本地环境中的, devServer是可以在服务器环境中的 -
利用webpack-dev-server代理解决跨域问题
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能
所谓同源是指: 协议,域名,端口都相同,就是同源, 否则就是跨域
例如:
http://127.0.0.1:8080
http://127.0.0.1:8080 // 同源
http://127.0.0.1:8080
http://127.0.0.1:9090 // 跨域
webpack.config.js
module.exports = {
devServer: {
contentBase: "./bundle",
open: true,
port: 9090,
proxy: [{
context: ["/user", "/login"],
/*
target: 当我们在代码中发送请求到/user的时候, devServer就会自动将我们请求的地址替换为
http://127.0.0.1:3000/user
*/
target: "http://127.0.0.1:3000",
changeOrigin: true, // 域名跨域
secure: false, // https跨域
pathRewrite:{"": "/api"} // 路径重写, 将路径中的api替换为空
}]
}
}
【注意】
devServer只能解决开发阶段的跨域问题, 并不能解决项目上线之后的跨域问题
因为项目上线之后是将打包好的文件上传到服务器, 而打包好的文件中并没有devServer, 所以项目上线之后要想解决跨域问题还是需要依赖后端开发人员
通过webpack-dev-server自动打包并没有真正的放到指定的目录中, 因为读写磁盘是非常耗时和消耗性能的, 所以为了提升性能webpack-dev-server将转换好的内容直接放到了内存中
常用配置附录
- target:要使用url模块解析的url字符串
- forward:要使用url模块解析的url字符串
- agent:要传递给http(s).request的对象(请参阅Node的https代理和http代理对象)
- ssl:要传递给https.createServer()的对象
- ws:true / false,是否代理websockets
- xfwd:true / false,添加x-forward标头
- secure:true / false,是否验证SSL Certs
- toProxy:true / false,传递绝对URL作为路径(对代理代理很有用)
- prependPath:true / false,默认值:true - 指定是否要将目标的路径添加到代理路径
- ignorePath:true / false,默认值:false - 指定是否要忽略传入请求的代理路径(注意:如果需要,您必须附加/手动)。
- localAddress:要为传出连接绑定的本地接口字符串
- changeOrigin:true / false,默认值:false - 将主机标头的原点更改为目标URL
7. HotModuleReplacementPlugin
通过webpack-dev-server可以实现实时监听打包内容的变化, 每次打包之后都会自动刷新网页, 但是正是因为每当内容被修改时都会自动刷新网页, 所以给我们带来了很多不便, 这时就需要通过HMR插件来优化调试开发
HMR(HotModuleReplacementPlugin)热更新插件, 在内容发生改变的时候时时的更新修改的内容但是不会重新刷新网站
-
HMR使用:
HotModuleReplacementPlugin是一个内置插件, 所以不需要任何安装直接引入webpack模块即可使用
const webpack = require("webpack");
module.exports = {
devServer: {
hot: true, // 开启热更新, 只要开启了热更新就不会自动刷新网页了
hotOnly: true // 哪怕不支持热更新也不要刷新网页
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
}
【注意】
如果是通过style-loader来处理CSS, 那么经过前面两步就已经实现了热更新
如果是通过MiniCssExtractPlugin.loader来处理CSS, 那么还需要额外配置MiniCssExtractPlugin.loader
loader: MiniCssExtractPlugin.loader,
options:{
hmr: true
}
-
JS模块如何实现热更新
对于css模块而言, 在css-loader中已经帮我们实现了热更新, 只要css代码被修改就会立即更新
但是对于js模块而言, 系统默认并没有给我们实现热更新, 所以修改了js模块代码并不会立即更新
所以我们需要手动监听模块变化
index.js
// 判断当前有没有开启热更新
if (module.hot){
// 告诉热更新需要监听哪一个JS模块的变化
module.hot.accept("./test.js", function () {
// 手动编写模块变化后的业务逻辑
});
}
8. babel
在开发中为了兼容一些低级版本的浏览器, 我们需要将ES678高级语法转换为ES5低级语法, 否则在低级版本浏览器中我们的程序无法正确执行。默认情况下webpack是不会将我们的代码转换成ES5低级语法的, 如果需要转换我们需要使用babel来转换
参考文档
- 安装转换到ES5的相关包
npm install --save-dev babel-loader @babel/core @babel/preset-env
- 配置babel
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/, // 不做处理的目录
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
}
]
}
-
presets优化
在实际开发中默认情况下babel会将所有高于ES5版本的代码都转换为ES5代码,但是有时候可能我们需要兼容的浏览器已经实现了更高版本的代码, 那么这个时候我们就不需要转换。因为如果浏览器本身已经实现了, 我们再去转换就会增加代码的体积,就会影响到网页的性能。
所以我们通过配置presets的方式来告诉webpack我们需要兼容哪些浏览器,然后babel就会根据我们的配置自动调整转换方案, 如果需要兼容的浏览器已经实现了, 就不转换了
参考文档
"presets": [["@babel/preset-env",{
targets: {
"chrome": "58",
"ie": "10"
},
}]],
利用babel实现低版本语法
对于有对应关系的语法而言, 经过我们上面的配置就已经能够实现自动转换了,但是对于没有对应关系的语法而言, 经过我们上节面的配置还不能实现自动转换。
什么叫有对应关系, 什么叫做没有对应关系?
有对应关系就是指ES5中有对应的概念, 例如: 箭头函数对应普通函数, let对应var, 这个就叫做有对应关系
没有对应关系就是指E5中根本就没有对应的语法, 例如Promise, includes等方法是ES678新增的,ES5中根本就没有对应的实现, 这个时候就需要再增加一些额外配置, 让babel自己帮我们实现对应的语法
参考文档
- 安装实现包
npm install --save @babel/polyfill
-
在用到没有对应关系的文件中导入polyfill包
index.js
import "@babel/polyfill";
Promise.resolve().then(function () {
console.log("babel");
});
【注意】
如果导入了polyfill,那么无论我们有没有用到没有对应关系的语法都会打包到文件中。但是这样会增加打包后文件的大小, 我们希望的是只将用到的不存在对应关系的语法打包到文件中,那么就需要在webpack.config.js中再配置一下。
"presets": [["@babel/preset-env",{
...
// 只打包没有对应关系的语法
useBuiltIns: "usage"
}]],
如果设置了useBuiltIns: "usage
属性, 那么polyfill会自动引入, 不需要我们再手动导入
babel 的第二种配置方式
直接导入polyfill的方式只适用于一般项目开发, 但是如果是在编写一些第三方模块的时候这种方式会出现一些问题。因为这种方式是通过全局变量的方式来注入代码, 会污染全局环境. 所以我们再来看一下polyfill的第二种配置方式
参考文档
- 安装相关模块
npm install --save @babel/polyfill
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
- 配置相关信息
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false,
"version": "7.0.0-beta.0"
}
]
]
}
【注意】
"corejs": false, 还是全局注入,还是会污染全局环境
"corejs": 2, 则不会污染全局环境, 所以还需要安装corejs2
npm install --save @babel/runtime-corejs2
babel-使用技巧
- 查看错误信息
- 根据错误信息查询 文档
- 根据文档缺什么安装配置什么
9. webpack-merge
为了便于维护配置文件, 我们会将开发阶段、上线阶段、公共配置分为三个文件。
webpack-merge模块就是用来实现重复代码的抽离和合并进一步优化配置文件。
- 安装webpack-merge
npm install --save-dev webpack-merge
- 将重复代码抽取到 webpack.config.common.js中
-
在开发阶段配置文件和上线阶段配置文件中导入common.js, 利用merge合并即可
例如:
webpack.config.common.js
webpack.config.dev.js
webpack.config.prod.js
网友评论