导航
导航
文章目录
  1. 版本
  2. webpack优化
    1. 1、抽取css(MiniCssExtractPlugin)
    2. 2、压缩CSS(OptimizeCssAssetsWebpackPlugin)
    3. 3、压缩JS
    4. 4、代码分割(SplitChunksPlugin)
    5. 5、配置全局变量
    6. 6、CSS Tree Shaking
    7. 7、JS Tree Shaking
    8. 8、resolve(解析)
    9. 9、模块热替换HMR
    10. 10、BundleAnalyzerPlugin
  3. webpack分离配置文件
    1. 1、提取公共配置
    2. 2、配置开发环境
    3. 3、配置发布环境
    4. 4、修改script命令行

动手搭建react开发环境二

版本

  • webpack 4
  • Babel 7

本篇主要使用针对上篇的webpack配置进行优化

webpack优化

1、抽取css(MiniCssExtractPlugin)

为每个引入 CSS 的 JS 文件创建一个 CSS 文件,提高首页加载速度

  • 把 style-loader 替换成 MiniCssExtractPlugin.loader
  • 新增 plugins
1
yarn add mini-css-extract-plugin -D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
//...同上
module: {
rules: [
{
test: /\.(sc|sa|c)ss$/,
use: [
// 'style-loader',
MiniCssExtractPlugin.loader,
// ...同上
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[hash].css',
chunkFilename: '[id].[hash].css',
}),
]
}

2、压缩CSS(OptimizeCssAssetsWebpackPlugin)

1
yarn add optimize-css-assets-webpack-plugin -D
1
2
3
4
5
6
7
8
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = {
//...同上
plugins: [
new OptimizeCssAssetsWebpackPlugin(),
]
}

3、压缩JS

webpack 4只要在生产模式下, 代码就会自动压缩

1
mode: 'production',

4、代码分割(SplitChunksPlugin)

代码分割,单独打包,可以有效避免所有页面只生成一个js文件,首屏加载很慢的情况。

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
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'all', // include all types of chunks
// minSize: 30000, // 引入的库大于30kb时才会做代码分割
// minChunks: 1, // 一个模块至少被用了1次才会被分割
// maxAsyncRequests: 5, // 同时异步加载的模块数最多是5个,如果超过5个则不做代码分割
// maxInitialRequests: 3, // 入口文件进行加载时,引入的库最多分割出3个js文件
// automaticNameDelimiter: '~', // 生成文件名的文件链接符
// name: true, // 开启自定义名称效果
// cacheGroups: { // 判断分割出的代码放到那里去
// vendors: { // 配合chunks:‘all’使用,表示如果引入的库是在node-modules中,那就会把这个库分割出来并起名为vendors.js
// test: /[\/]node_modules[\/]/,
// priority: -10,
// filename: 'vendors.js'
// },
// default: { // 为非node-modules库中分割出的代码设置默认存放名称
// priority: -20,
// reuseExistingChunk: true, // 避免被重复打包分割
// filename: 'common.js'
// }
// }
}
}
};

5、配置全局变量

1
2
3
4
5
6
7
8
9
10
const webpack = require("webpack");

module.exports = {
//...同上
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),
}),
]
}

🔥Tip1 问题:当用 DefinePlugin 来配置全局变量时,只给依赖中注入了环境变量,也就是src文件夹下面的和依赖的模块。当我们在webpack配置文件中去取 process.env.NODE_ENV 依然是 undefined。

解决:在package.json命令中注入

1
2
3
"scripts": {
"start": "webpack-dev-server NODE_ENV=development --config ./build/webpack.config.js",
}

6、CSS Tree Shaking

去除项目代码中用不到的 CSS 样式,仅保留被使用的样式代码

🔥Tip2 问题:当使用 CSS Tree Shaking 的时候,需要把 css-modules 关闭,不然 css 会被全部清除掉。

1
yarn add glob-all purify-css purifycss-webpack -D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const PurifyCSS = require("purifycss-webpack");
const glob = require("glob-all");

module.exports = {
//...同上
plugins: [
new PurifyCSS({
paths: glob.sync([
// 要做CSS Tree Shaking的路径文件
path.resolve(__dirname, "../public/*.html"), // 请注意,我们同样需要对 html 文件进行 tree shaking
path.resolve(__dirname, "../src/*.js")
])
})
]
}

7、JS Tree Shaking

清除到代码中无用的js代码,只支持import方式引入,不支持commonjs的方式引入

webpack 4只要在生产模式下, tree shaking就会生效。

8、resolve(解析)

能设置模块如何被解析。

  • extension: 指定extension之后可以不用在require或是import的时候加文件扩展名,会依次尝试添加扩展名进行匹配
  • alias: 配置别名可以加快webpack查找模块的速度
1
2
3
4
5
6
7
8
9
module.exports = {
//...同上
resolve: {
extensions: ['.js', '.jsx'],
alias: {
'@': path.join(__dirname, '../src'),
},
},
}

9、模块热替换HMR

模块热替换也称为HMR,代码更新时只会更新被修改部分都显示。有如下有点

  • 针对于样式调试更加方便
  • 只会更新被修改代码的那部分显示,提升开发效率
  • 保留在完全重新加载页面时丢失的应用程序状态。

这里我们采用Node.js的方式实现

1
2
3
4
yarn add express webpack-dev-middleware webpack-hot-middleware react-hot-loader cross-env -D

cd build
touch dev-server.js
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
// dev-server.js
const path = require('path');
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require("webpack-hot-middleware")
const config = require('./webpack.config.js');

const complier = webpack(config); // 编译器,编译器执行一次就会重新打包一下代码
const app = express(); // 生成一个实例
const DIST_DIR = path.resolve(__dirname, '../', 'dist'); // 设置静态访问文件路径
const port = parseInt(process.env.PORT, 10) || 8586;
const host = process.env.HOST || 'localhost';

const devMiddleware = webpackDevMiddleware(complier, {
quiet: true,
noInfo: true,
stats: 'minimal'
})
const hotMiddleware = webpackHotMiddleware(complier, {
log: false,
heartbeat: 2000
})

app.use(devMiddleware)
app.use(hotMiddleware)
// 设置访问静态文件的路径
app.use(express.static(DIST_DIR))

app.listen(port, () => {
console.log(`App running at: http://${host}:${port}`);
}) //监听端口

修改webpack.config.js

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
// webpack.config.js
const isDev = process.env.NODE_ENV === 'development';

module.exports = {
entry: {
main: [
'webpack-hot-middleware/client?noInfo=true&reload=true',
'./src/index.js'
]
},
module: {
rules: [
{
test: /\.(sc|sa|c)ss$/,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[local]_[hash:base64:5]',
},
sourceMap: !isDev && true, // 开发时刷新会导致闪屏(样式加载慢一步)
},
},
'postcss-loader', // 使用 postcss 为 css 加上浏览器前缀
'sass-loader', // 编译scss
],
},
],
},
plugins: [
new webpack.NamedModulesPlugin(), // 用于启动HMR时可以显示模块的相对路径
new webpack.HotModuleReplacementPlugin(), // 开启HMR(热替换功能,替换更新部分,不重载页面!) 相当于在命令行加 --hot
]
}

修改入口文件index.js

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
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import { BrowserRouter } from 'react-router-dom';
import Router from './router';

function render() {
ReactDOM.render(
<AppContainer>
<BrowserRouter>
<Router />
</BrowserRouter>
</AppContainer>,
document.getElementById('root')
);
}

/* 初始化 */
render();

/* 热更新 */
if (module.hot) {
module.hot.accept('./router/index.js', () => {
render();
});
}

修改script命令行

1
"start": "cross-env NODE_ENV=development node ./build/dev-server.js",

ok,当我们修改代码时,页面就不需要刷新了,而是直接更新变化的部分。

10、BundleAnalyzerPlugin

使用交互式可缩放树形图可视化webpack输出文件的大小,可以方便我们针对代码依赖的大小进行优化。

1
yarn add webpack-bundle-analyzer -D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
//...同上
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'server',
analyzerHost: '127.0.0.1',
analyzerPort: 8889,
reportFilename: 'report.html',
defaultSizes: 'parsed',
openAnalyzer: true,
generateStatsFile: false,
statsFilename: 'stats.json',
statsOptions: null,
logLevel: 'info'
})
]
}
1
2
3
4
5
6
// package.json
{
"script": {
"analyz": "cross-env NODE_ENV=production npm_config_report=true npm run build"
}
}

webpack分离配置文件

针对开发环境和发布环境配置对应的webpack,公共的部分提取出来,再使用 webpack-merge 来将不同环境下的配置合并起来

1
2
3
4
5
6
cd build
touch webpack.base.conf.js
touch webpack.dev.conf.js
touch webpack.prd.conf.js

yarn add webpack-merge -D

1、提取公共配置

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.base.conf.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const isDev = process.env.NODE_ENV === 'development';

module.exports = {
entry: ['./src/index.js'],
output: {
// 输出目录
path: path.resolve(__dirname, '../dist'),
},
module: {
// ...同webpack.config.js的modules
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html', // 最终创建的文件名
template: path.join(__dirname, '../public/index.html'), // 指定模板路径
}),
],
resolve: {
extensions: ['.js', '.jsx'],
alias: {
'@': path.join(__dirname, '../src'),
},
},
optimization: {
splitChunks: {
// 代码分割按需加载、提取公共代码
chunks: 'all', // 所有的 chunks 代码公共的部分分离出来成为一个单独的文件
},
},
performance: false, // 关闭性能提示
};

2、配置开发环境

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
39
40
41
42
// webpack.dev.conf.js
const path = require("path");
const webpack = require("webpack");
const merge = require('webpack-merge');
const commonConfig = require('./webpack.base.conf.js');

module.exports = merge(commonConfig, {
mode: "development",
devtool: 'cheap-module-eval-soure-map',
entry: {
//实现刷新浏览器webpack-hot-middleware/client?noInfo=true&reload=true 是必填的
main: [
'webpack-hot-middleware/client?noInfo=true&reload=true',
'./src/index.js'
]
},
output: {
// 输出目录
path: path.resolve(__dirname, "../dist"),
// 文件名称
filename: "bundle.[name].[hash].js",
chunkFilename: '[name].[hash].js'
},
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
// new webpack.DefinePlugin({
// 'process.env.NODE_ENV': JSON.stringify('development'),
// }),
],
devServer: {
hot: true,
contentBase: path.resolve(__dirname, "../dist"),
host: "localhost",
port: 8586,
historyApiFallback: true, // 该选项的作用所有的404都连接到index.html
proxy: {
// 代理到后端的服务地址
// "/api": "http://localhost:3000"
}
}
});

3、配置发布环境

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
39
// webpack.prd.conf.js
const path = require('path');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.base.conf.js');

module.exports = merge(commonConfig, {
mode: 'production',
devtool: 'cheap-module-source-map',
output: {
publicPath: '/', // 打包路径
// 输出目录
path: path.resolve(__dirname, '../dist'),
// 文件名称
filename: 'bundle.[name].[hash].js',
chunkFilename: '[name].[hash].js',
},
optimization: {
usedExports: true,
splitChunks: {
chunks: 'all', // 所有的 chunks 代码公共的部分分离出来成为一个单独的文件
cacheGroups: {
// 公共代码打包分组配置
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
},
},
},
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[hash].css',
chunkFilename: '[id].[hash].css',
}),
// new webpack.DefinePlugin({
// 'process.env.NODE_ENV': JSON.stringify('production'),
// }),
],
});

4、修改script命令行

1
2
"start": "cross-env NODE_ENV=development node ./build/dev-server.js",
"build": "cross-env NODE_ENV=production webpack --config ./build/webpack.prod.conf.js",

写到这里,一个基本的React开发环境也就搭起来了,接下来就可以针对代码或者开发效率进行优化。