webpack使用心得总结

去除无用的样式

安装: npm i purgecss-webpack-plugin glob -D

1
2
3
4
5
6
7
8
9
10
11
12
13
// 去除无用的样式
const glob = require('glob');
const PurgecssWebpackPlugin = require('purgecss-webpack-plugin');
plugins: [
new HtmlWebpaclPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin(),
// 去除无用的样式
new PurgecssWebpackPlugin({
paths: glob.sync('./src/**/*', {nodir: true})
})
]

下面我们来简单分析分析:

glob是用来查找文件的

1
2
3
4
glob.sync('./src/**/*', {nodir: true}
// 同步查找src目录下的任意文件夹下的任意文件
// 返回一个数组,如['真实路径/src/css/style.css','真实路径/src/index.js',...]
// {nodir: true}表示不包含文件夹,加快查找速度

purgecss-webpack-plugin是去除无用的css

1
2
3
4
5
new PurgecssWebpackPlugin({
// paths表示指定要去解析的文件名数组路径
// Purgecss会去解析这些文件然后把无用的样式移除
paths: glob.sync('./src/**/*', {nodir: true})
})

动态添加CDN

在html文件中引入cdn文件,在webpack配置externals,这样就不会打包引入的cdn的库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// index.html文件

<body>
<div id="root"></div>
<!-- 引入jquery的cdn -->
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</body>

// webpack.config.js文件

module.exports = {
externals: {
'jquery': '$'
}
}

这样写完后,在js文件中我们就可以不用再导入jquery也能直接使用$操作符了

由于每次都需要在index.html模板中手动引入需要的cdn文件,然后还要在webpack里配置,有点繁琐了

So,html-webpack-externals-plugin这样的插件就应运而生了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 动态添加CDN
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');

module.exports = {
plugins: [
new HtmlWebpackExternalsPlugin({
externals: [
{ // 引入的模块
module: 'jquery',
// cdn的地址
entry: 'https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js',
// 挂载到了window上的名称
// window.jQuery就可以全局使用
global: 'jQuery'
}
]
})
]
};

DllPlugin动态链接库

很多时候我们在开发时无论是用React还是Vue,我们都不希望这个开发的主力框架每次都被打包一遍,这样也是费时费力的事情

所以,出现了DllPlugin这种插件,它纯属webpack内置的,放心大胆的用

作用:

  1. 在第一次打包的时候就把打包用到的开发框架直接打包好,然后会生成一个manifest.json文件
  2. 再打包的的时候,只要有import React from ‘react’这样的引用,它就会先去所谓的缓存文件里找,找到了就直接用,也不用再进行对react打包了
  3. 如果没找到的话,再对框架打包一遍也无伤大雅

创建动态链接库
在根目录下创建一个webpack.dll.js文件,用来打包出dll文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const path = require('path');
// 引入webpack
const webpack = require('webpack');

module.exports = {
entry: ['react', 'react-dom'],
output: {
filename: 'react.dll.js',
path: path.resolve('dll'),
library: 'react' // 打包后被引用的变量名
},
plugins: [
// 动态链接库
new webpack.DllPlugin({
name: 'react',
path: path.resolve('dll', 'manifest.json')
})
]
};

代码写完了,npm run dll,之后会出现一个dll的文件夹,里面会包含你打包出来的文件

引用动态链接库

1
2
3
4
5
6
7
8
9
10
11
12
13
const path = require('path');
// 引入webpack
const webpack = require('webpack');

module.exports = {
plugins: [
// 引用对应的动态链接库的manifest.json文件
// 这样以后再引入react的时候就会优先在json文件里去寻找
new webpack.DllReferencePlugin({
manifest: path.resolve('dll', 'manifest.json')
})
]
};

写到这里还不算完,还需要在src目录下的index.html模板中引入一下

1
<script src="../dll/react.dll.js"></script>

之所以,会新建一个dll目录,因为在npm start开发环境编译的时候,dist目录的内容都在内存中了,是找不到react.dll.js文件的

动态引入js
通过add-asset-html-webpack-plugin插件就可以完成这样的需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// webpack.config.js文件

const webpack = require('webpack');
// 添加资源到html文件
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');

module.exports = {
plugins: [
// 引用打包好的react,不会打包到bundle里
new webpack.DllReferencePlugin({
manifest: path.resolve('dll', 'manifest.json')
}),
// 直接将打包好的react.dll.js添加到html模板
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve('dll', 'react.dll.js')
})
]
};

懒加载

说到懒加载必然是一种很好的优化网页或应用的方式,那么在webpack中也是通过ES6的import()语法来引入的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React, { Component, Fragment } from "react";
import moment from "moment";
import "moment/locale/zh-cn";

// 设置中文
// moment.locale("zh-cn");

export default class List extends Component {
btnClick = () => {
import("./test").then((data) => {
console.log(data);
data.a();
});
};
render() {
console.log($);
let time = moment().format("YYYY-MM-DD HH:mm:ss");
return (
<Fragment>
<div>{time}</div>
<div>111</div>
<button onClick={this.btnClick}>222</button>
</Fragment>
);
}
}

抽取公共代码

比如有两个js文件,一个是index.js另一个是lrc.js,它们都引用了著名的实用工具库lodash,代码如下

1
2
3
4
5
6
7
8
9
// index.js文件

import _ from 'lodash';
console.log(_.xor([2, 1], [2, 3]));


// lrc.js文件
import _ from 'lodash';
console.log(_.flatten([1,[3, 4, 5, [2]]]));

抽取第三方模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// webpack.config.js文件

module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: 'initial',
minSize: 0,
minChunks: 2,
test: /node_modules/,
priority: 1
}
}
}
}
};

抽取公共模块

1
2
3
4
5
6
7
// index.js文件
import { flatten } from './common';
console.log('index',flatten([1,[33, 4, 5, [34]]]));

// lrc.js文件
import {flatten} from './common';
console.log(flatten([1,[33, 4, 5, [34]]]));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// webpack.config.js文件

module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
utils: {
chunks: 'initial',
minSize: 0,
minChunks: 2
}
}
}
}
};

IgnorePlugin

作用: 忽略打包第三方模块指定的目录

为什么要忽略呢? 通过下面的栗子来看一下

相信很多人应该或多或少的都听过moment这个时间库,不知道也没关系,我来演示一波

先安装moment: npm i moment -S

1
2
3
4
5
6
7
8
9
10
// index.js文件

// 导入moment
import moment from 'moment';

// 设置中文
moment.locale('zh-cn');
let time = moment().endOf('day').fromNow();

window.root.innerHTML += time;

页面上展示的一点毛病都没有,不过如果看一下打包的情况就会发现有瑕疵了

设置了中文,却把整个语言包都打包进去了,这样很不好

这是神马原因呢,其实是因为moment被导入的时候,附赠了整个locale语言包,这种买一赠一的行为就不用提现在代码世界了,吃不消了

我们需要用中文包,但是不想打包全部语言包,就让IgnorePlugin出马了

1
2
3
4
5
6
7
8
9
10
// webpack.config.js文件

const webpack = require('webpack');

module.exports = {
plugins: [
// 忽略moment目录下的locale文件夹
new webpack.IgnorePlugin(/\.\/locale/, /moment/)
]
};

配置改写后,再回到index.js中单独导入中文语言包就好了

1
2
3
4
5
6
7
8
9
10
// index.js文件

// 利用IgnorePlugin把只需要的语言包导入使用就可以了,省去了一下子打包整个语言包
import moment from 'moment';
// 单独导入中文语言包
import 'moment/locale/zh-cn';

let time = moment().endOf('day').fromNow();

window.root.innerHTML += time;

noParse

noParse的作用是不去解析你所使用的第三方库中的依赖库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
module: {
// 不去解析jquery或lodash中的依赖库
noParse: /jquery|lodash/,
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader'
}
}
]
}
}

在工作中,忽略大型的库可以提高构建性能,可以从构建时间上看出来速度的提升,如上面代码中提到的jquery和lodash

resolve

从这个英文就能看出来,它就是配置模块如何解析用的,配置太多也没必要一一介绍了,还是直接说重点写出常用的配置吧

resolve常用配置

  1. modules
    1. 指定解析第三方包的目录位置
  2. alias
    1. 指定import导入时的别名,简化引入
  3. extensions
    1. 自动解析确定好的扩展名
    2. 默认会把js和json当做扩展名
1
2
3
4
5
6
7
8
9
10
11
12
const { resolve } = require('path');

module.exports = {
resolve: {
modules: [resolve('node_modules')],
alias: {
Utils: resolve(__dirname, 'src/utils/'),
'@': resolve(__dirname, 'src')
},
extensions: ['.js', '.css', '.json']
}
}

include和exclude

  1. include: 包含指定目录下的文件解析
  2. exclude: 排除指定目录不进行解析

二者使用一个即可了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader'
},
exculde: /node_modules/, // 二选一
include: path.resolve('src') // 二选一
}
]
}
}

happypack

webpack在Node环境下运行所以也是单线程操作,一件一件的去处理事情。
于是乎,就有了happypack的用武之地了,它的作用就是可以实现多进程打包操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// webpack.config.js文件

const Happypack = require('happypack');

module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: 'Happypack/loader?id=js'
},
{
test: /\.css$/,
use: 'Happypack/loader?id=css'
}
]
},
plugins: [
new Happypack({
id: 'js',
loaders: [
{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
}
}
]
}),
new Happypack({
id: 'css',
loaders: ['style-loader', 'css-loader']
})
]
}

由于HappyPack 对file-loader、url-loader 支持的不友好,所以不建议对该loader使用。

HappyPack 参数

  1. id: String 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件.
  2. loaders: Array 用法和 webpack Loader 配置中一样.
  3. threads: Number 代表开启几个子进程去处理这一类型的文件,默认是3个,类型必须是整数。
  4. verbose: Boolean 是否允许 HappyPack 输出日志,默认是 true。
  5. threadPool: HappyThreadPool 代表共享进程池,即多个 HappyPack 实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多。
  6. verboseWhenProfiling: Boolean 开启webpack –profile ,仍然希望HappyPack产生输出。
  7. debug: Boolean 启用debug 用于故障排查。默认 false。
觉得不错的话可以打赏哦