webpack 详细教程:带你深度解读Webpack系列 进阶篇
webpack 详细教程:带你深度解读Webpack系列 进阶篇<!-- index.html --> <script src="./js/base.js"></script>这时候,我们 npm run dev,会发现有找不到该资源文件的报错信息。├── public │ ├── config.js │ ├── index.html │ ├── js │ │ ├── base.js │ │ └── other.js现在,我们在 index.html 中引入了 ./js/base.js。1.静态资源拷贝有些时候,我们需要使用已有的JS文件、CSS文件 (本地文件),但是不需要 webpack 编译。例如,我们在 public/index.html 中引入了 public 目录下的 js 或 css 文件,这个时候,如果直接打包,那么在构建出来之后,肯定是找不到对应的 js
三篇长文,带你解读 webpack,希望读完这三篇文章,你能够对 webpack 的各项配置有一个更为清晰的认识。
本文是第二篇,如果你还没有阅读我的上一个文章,建议阅读之后,再继续阅读本文。
本文会引入更多的 webpack 配置,如果文中有任何错误,欢迎再评论区指正,我会尽快修正,webpack 优化部分放在了下一篇。
推荐大家参考本文一步一步进行配置,不要总是想着找什么最佳配置,掌握之后,根据自己需求配置出来的,就是最佳配置。
1.静态资源拷贝
有些时候,我们需要使用已有的JS文件、CSS文件 (本地文件),但是不需要 webpack 编译。例如,我们在 public/index.html 中引入了 public 目录下的 js 或 css 文件,这个时候,如果直接打包,那么在构建出来之后,肯定是找不到对应的 js / css 了。
public 目录结构
├── public
│ ├── config.js
│ ├── index.html
│ ├── js
│ │ ├── base.js
│ │ └── other.js
现在,我们在 index.html 中引入了 ./js/base.js。
<!-- index.html -->
<script src="./js/base.js"></script>
这时候,我们 npm run dev,会发现有找不到该资源文件的报错信息。
对于这个问题,我们可以手动将其拷贝至构建目录,然后配置 CleanWebpackPlugin 时,注意不要清空对应的文件或文件夹即可,但是如若这个静态文件时不时的还会修改下,那么依赖于手动拷贝,是很容易出问题的。
不要过于相信自已的记性,依赖于手动拷贝的方式,大多数人应该都有过忘记拷贝的经历,你要是说你从来没忘记过。
幸运的是,webpack 为我们这些记性不好又爱偷懒的人提供了好用的插件 CopyWepackPlugin,它的作用就是将单个文件或整个目录复制到构建目录。
首先安装一下依赖:
npm install copy-webpack-plugin -D
修改配置(当前,需要做的是将 public/js 目录拷贝至 dist/js 目录):
//webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
//...
plugins: [
new CopyWebpackPlugin([
{
from: 'public/js/*.js'
to: path.resolve(__dirname 'dist' 'js')
flatten: true
}
//还可以继续配置其它要拷贝的文件
])
]
}
此时,重新执行 npm run dev,报错信息已经消失。
这里说一下 flatten 这个参数,设置为 true,那么它只会拷贝文件,而不会把文件夹路径都拷贝上,大家可以不设置 flatten 时,看下构建结果。
另外,如果我们要拷贝一下目录下的很多文件,但是想过滤掉某个或某些文件,那么 CopyWebpackPlugin 还为我们提供了 ignore 参数。
//webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
//...
plugins: [
new CopyWebpackPlugin([
{
from: 'public/js/*.js'
to: path.resolve(__dirname 'dist' 'js')
flatten: true
}
] {
ignore: ['other.js']
})
]
}
例如,这里我们忽略掉 js 目录下的 other.js 文件,使用 npm run build 构建,可以看到 dist/js 下不会出现 other.js 文件。CopywebpackPlugin 还提供了很多其它的参数,如果当前的配置不能满足你,可以查阅文档进一步修改配置。
2.ProvidePlugin
ProvidePlugin 在我看来,是为懒人准备的,不过也别过度使用,毕竟全局变量不是什么 "好东西"。ProvidePlugin 的作用就是不需要 import 或 require 就可以在项目中到处使用。
ProvidePlugin 是 webpack 的内置插件,使用方式如下:
new webpack.ProvidePlugin({
identifier1: 'module1'
identifier2: ['module2' 'property2']
});
默认寻找路径是当前文件夹 ./** 和 node_modules 当然啦,你可以指定全路径。
React 大家都知道的,使用的时候,要在每个文件中引入 React,不然立刻报错给你看。还有就是 jquery,lodash 这样的库,可能在多个文件中使用,但是懒得每次都引入,好嘛,一起来偷个懒,修改下 webpack 的配置:
const webpack = require('webpack');
module.exports = {
//...
plugins: [
new webpack.ProvidePlugin({
React: 'react'
Component: ['react' 'Component']
Vue: ['vue/dist/vue.esm.js' 'default']
$: 'jquery'
_map: ['lodash' 'map']
})
]
}
这样配置之后,你就可以在项目中随心所欲的使用$、_map了,并且写 React 组件时,也不需要 import React 和 Component 了,如果你想的话,你还可以把 React 的 Hooks 都配置在这里。
另外呢,Vue 的配置后面多了一个 default,这是因为 vue.esm.js 中使用的是 export default 导出的,对于这种,必须要指定 default。React 使用的是 module.exports 导出的,因此不要写 default。
另外,就是如果你项目启动了 eslint 的话,记得修改下 eslint 的配置文件,增加以下配置:
{
"globals": {
"React": true
"Vue": true
//....
}
}
当然啦,偷懒要有个度,你要是配一大堆全局变量,最终可能会给自已带来麻烦,对自已配置的全局变量一定要负责到底。
3.抽离CSS
CSS打包我们前面已经说过了,不过呢,有些时候,我们可能会有抽离CSS的需求,即将CSS文件单独打包,这可能是因为打包成一个JS文件太大,影响加载速度,也有可能是为了缓存(例如,只有JS部分有改动)还有可能就是 "我高兴":我想抽离就抽离,谁也管不着。
不管你是因为什么原因要抽离CSS,只要你有需求,我们就可以去实现。
首先,安装 loader:
npm install mini-css-extract-plugin -D
mini-css-extract-plugin 和 extract-text-webpack-plugin 相比:
- 异步加载
- 不会重复编译(性能更好)
- 更容易使用
- 只适用CSS
修改我们的配置文件:
//webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].css'
//个人习惯将css文件放在单独目录下
//publicPath:'../' //如果你的output的publicPath配置的是 './' 这种相对路径,那么如果将css文件放在单独目录下,记得在这里指定一下publicPath
})
]
module: {
rules: [
{
test: /\.(le|c)ss$/
use: [
MiniCssExtractPlugin.loader //替换之前的 style-loader
'css-loader' {
loader: 'postcss-loader'
options: {
plugins: function () {
return [
require('autoprefixer')({
"overrideBrowserslist": [
"defaults"
]
})
]
}
}
} 'less-loader'
]
exclude: /node_modules/
}
]
}
}
现在,我们重新编译:npm run build,目录结构如下所示:
.
├── dist
│ ├── assets
│ │ ├── alita_e09b5c.jpg
│ │ └── thor_e09b5c.jpeg
│ ├── css
│ │ ├── index.css
│ │ └── index.css.map
│ ├── bundle.fb6d0c.js
│ ├── bundle.fb6d0c.js.map
│ └── index.html
前面说了最好新建一个 .browserslistrc 文件,这样可以多个 loader 共享配置,所以,动手在根目录下新建文件 (.browserslistrc),内容如下(你可以根据自己项目需求,修改为其它的配置):
last 2 version
> 0.25%
not dead
修改 webpack.config.js:
//webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
//...
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].css'
})
]
module: {
rules: [
{
test: /\.(c|le)ss$/
use: [
MiniCssExtractPlugin.loader
'css-loader' {
loader: 'postcss-loader'
options: {
plugins: function () {
return [
require('autoprefixer')()
]
}
}
} 'less-loader'
]
exclude: /node_modules/
}
]
}
}
要测试自己的 .browserlistrc 有没有生效也很简单,直接将文件内容修改为 last 1 Chrome versions ,然后对比修改前后的构建出的结果,就能看出来啦。
将抽离出来的css文件进行压缩:
使用 mini-css-extract-plugin,CSS 文件默认不会被压缩,如果想要压缩,需要配置 optimization,首先安装 optimize-css-assets-webpack-plugin.
npm install optimize-css-assets-webpack-plugin -D
修改webpack配置:
//webpack.config.js
const OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
entry: './src/index.js'
//....
plugins: [
new OptimizeCssPlugin()
]
}
注意,这里将 OptimizeCssPlugin 直接配置在 plugins 里面,那么 js 和 css 都能够正常压缩,如果你将这个配置在 optimization ,那么需要在配置一下 js 的压缩(开发环境下不需要去做CSS的压缩,因此后面记得将其放到 webpack.config.prod.js 中哈)。
配置完之后,测试的时候发现,抽离之后,修改 css 文件时,第一次页面会刷新,但是第二次页面不会刷新不会刷新 —— 好嘛,我平时的业务中用不着抽离 css,这个问题搁置了好多天(准确来说是忘记了)。
3月8号再次修改这篇文章的时候,正好看到了 MiniCssExtractPlugin.loader 对应的 option 设置,我们再次修改下对应的 rule。
module.exports = {
rules: [
{
test: /\.(c|le)ss$/
use: [
{
loader: MiniCssExtractPlugin.loader
options: {
hmr: isDev
reloadAll: true
}
}
//...
]
exclude: /node_modules/
}
]
}
4.按需加载
很多时候我们不需要一次性加载所有的JS文件,而应该在不同阶段去加载所需要的代码。webpack 内置了强大的分割代码的功能可以实现按需加载。
比如,我们在点击了某个按钮之后,才需要使用对应的JS文件中的代码,需要使用 import() 语法:
document.getElementById('btn').onclick = function() {
import('./handle').then(fn => fn.default());
}
import() 语法,需要 @babel/plugin-syntax-dynamic-import 的插件支持,但是因为当前 @babel/preset-env 预设中已经包含了 @babel/plugin-syntax-dynamic-import,因此我们不需要再单独安装和配置。
直接 npm run build 进行构建,构建结果如下: