这里记录了自己在开发一个 React 项目时使用 Webpack 优化项目的过程,欢迎大家围观点赞或吐槽。
学习 React 时候,写了个个人博客站点。使用 webpack 作为打包工具,在这之前学习 webpack 时候,知道 webpack 有插件可以做资源压缩、抽离,以达到减小资源的体积,便于缓存资源的目的,但是开始这个项目时候并没有想立即使用 webpack 的插件带来的便利,主要是想先写完再来优化,也便于优化前后有个对比,便于深入的了解插件的作用。
写完后项目打包后的main.js
文件体积是 3.38 MiB,我部署使用的腾讯云 1 M 带宽的服务器,访问速度很慢。
看看此时 webpack.config.js 的配置:
const path = require('path');
var webpack = require('webpack');
const config = {
entry: ['babel-polyfill','./src/app.js'],
output: {
path: path.resolve(__dirname, 'build'),
filename: 'main.js', // 打包输出,单文件输出
publicPath: '/',
},
mode: 'production',
module: {
rules: [
{
test: /\.js$/,
include: /(src)/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader'],
},
{
test: /\.scss$/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
}, {
loader: 'sass-loader',
},],
},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
],
devServer: {
contentBase: path.join(__dirname, "build"),
compress: true,
port: 9000,
open: true,
inline: true,
},
};
module.exports = config;
npm run build
结果: Asset Size Chunks Chunk Names
main.js 3.38 MiB main [emitted] main
3 M 的单文件实在是太大了。适当减少请求次数,减少单次请求的文件大小,这是做前端优化的重要手段。如何缩小这个单页面应用体积,或者适当拆分资源(利用浏览器可以同时下载多个资源的特性)来优化访问速度。
js压缩
去掉注释,减少空格可以减少无用字符占用的文件体积。webpack 插件 UglifyjsWebpackPlugin
官方对插件的介绍是用来缩小你 javascript 文件,对于我这个博客而言就是 main.js 文件。
webpack.config.js 配置如下:
const path = require('path');
var webpack = require('webpack');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const config = {
entry: ['babel-polyfill','./src/app.js'],
output: {
path: path.resolve(__dirname, 'build'),
filename: 'main.js',
publicPath: '/',
},
mode: 'production',
module: {
rules: [
{
test: /\.js$/,
include: /(src)/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader'],
},
{
test: /\.scss$/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
}, {
loader: 'sass-loader',
},],
},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new UglifyJsPlugin({
uglifyOptions: {
ie8: false,
mangle: true,
output: { comments: false },
compress: {
warnings: false,
drop_console: true,
drop_debugger: true,
unused: false,
},
},
sourceMap: true,
cache: true,
}),
],
devServer: {
contentBase: path.join(__dirname, "build"),
compress: true,
port: 9000,
open: true,
inline: true,
},
};
module.exports = config;
npm run build
输出: Asset Size Chunks Chunk Names
main.js 3.1 MiB main [emitted] main
可见资源减少了 0.28 MIB。
gzip 压缩
上面的 UglifyjsWebpackPlugin
插件带来的压缩效果可能并不能满足我们的要求。我们熟悉有一种打包压缩方式,将文件压缩为 zip 包,这种压缩效果显著,通常可以将文件成倍压缩,那么这种压缩方式能否在这里使用呢,答案是可以的。CompressionWebpackPlugin
插件就提供了这种功能,我们来引入看看效果。
webpack.config.js
配置如下:
const path = require('path');
var webpack = require('webpack');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CompressionPlugin = require("compression-webpack-plugin");
const config = {
entry: ['babel-polyfill','./src/app.js'],
output: {
path: path.resolve(__dirname, 'build'),
filename: 'main.js',
publicPath: '/',
},
mode: 'production',
module: {
rules: [
{
test: /\.js$/,
include: /(src)/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader'],
},
{
test: /\.scss$/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
}, {
loader: 'sass-loader',
},],
},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new UglifyJsPlugin({
uglifyOptions: {
ie8: false,
mangle: true,
output: { comments: false },
compress: {
warnings: false,
drop_console: true,
drop_debugger: true,
unused: false,
},
},
sourceMap: true,
cache: true,
}),
new CompressionPlugin(),
],
devServer: {
contentBase: path.join(__dirname, "build"),
compress: true,
port: 9000,
open: true,
inline: true,
},
};
module.exports = config;
npm run build
结果:....
Asset Size Chunks Chunk Names
main.js 3.1 MiB main [emitted] main
main.js.gz 544 KiB [emitted]
....
nginx_http_gzip_static_module
模块可以支持请求压缩包,nginx 配置如下:...
server {
gzip on;
gzip_static on;
gzip_min_length 1000;
gzip_buffers 4 8k;
gzip_types text/plain application/xml text/css text/js text/xml application/x-javascript text/javascript application/json application/xml+rss image/jpeg image/png image/g
gzip_vary on;
listen 80;
location / {
...
}
}
...
这样浏览器端下载的资源就由原来的 3.38M 降到了 554K。
main.js 是个大杂烩 — css 提取
已经将文件压缩成 gzip 文件,从减少文件体积方面好像已经无计可施。但回头看看 main.js 文件,不难发现他是个即有 js 又有 css 的大杂烩,要是能把 css 抽离出来,是不是可以进一步减少单文件体积,虽然会多出一个 css 文件,多了次请求,但正好利用了浏览器的并发下载,从缓存资源角度来讲也是有利的。webpack 插件 extract-text-webpack-plugin
可以用来提取 css。但是需要注意的是 extract-text-webpack-plugin
只能用在 webpack 4 以下,webpack4 及以上版本需要使用 mini-css-extract-plugin
webpack.config.js
配置如下:
const path = require('path');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CompressionPlugin = require("compression-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const config = {
entry: ['babel-polyfill','./src/app.js'],
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].[hash:8].js',
publicPath: '/',
},
mode: 'production',
module: {
rules: [
{
test: /\.js$/,
include: /(src)/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader',
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html',
}),
new webpack.HotModuleReplacementPlugin(),
new UglifyJsPlugin({
uglifyOptions: {
ie8: false,
mangle: true,
output: { comments: false, },
compress: {
warnings: false,
drop_console: true,
drop_debugger: true,
unused: false,
},
},
sourceMap: true,
cache: true,
}),
new CompressionPlugin(),
new MiniCssExtractPlugin({
filename: "[name].[hash:8].css",
chunkFilename: "[id].[hash:8].css",
}),
],
devServer: {
contentBase: path.join(__dirname, "build"),
compress: true,
port: 9000,
open: true,
inline: true,
},
};
module.exports = config;
npm run build:
Built at: 2018-06-21 08:03:57
Asset Size Chunks Chunk Names
main.b2c90941.css 317 KiB main [emitted] main
main.b2c90941.js 2.69 MiB main [emitted] main
index.html 263 bytes [emitted]
index.html.gz 196 bytes [emitted]
main.b2c90941.css.gz 33.4 KiB [emitted]
main.b2c90941.js.gz 501 KiB [emitted]
可见 css 被取了出来,这里的提取效果没有想想中的理想,js 的体积缩小的并不多,原本 css 也不多。
html 文件自动生成: 抽离后文件自动在 html 页面引入
上一步抽离了 css,在打包的使用启用了 chunk hash,这样当 js 或者 css 文件有改动后运行 npm run build
每次生成的 js 和 css 的打包文件名是不同的,这样就有两个问题需要解决: 1、每次 build 后需要在 index.html 页面修改 css 和 js 文件的名称,2、多次修改后 build,会产生需要没用的 js、css 文件。针对这两个问题,大牛早已经给出了解决方案。
html-webpack-plugin
插件可以让我们指定生成 index.html 使用的模版文件,build 后会自动生成 index.html 文件,并将 css 和 js 文件自动引入到 html 文件。
clean-webpack-plugin
插件则可以帮我们在 build 的开始阶段,自动删除指定的目录或者文件。
webpack.config.js
配置如下:
const path = require('path');
var webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CompressionPlugin = require("compression-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const config = {
entry: ['babel-polyfill','./src/app.js'],
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].[hash:8].js',
publicPath: '/',
},
mode: 'production',
module: {
rules: [
{
test: /\.js$/,
include: /(src)/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader',
],
},
],
},
plugins: [
// build 开始阶段需要删除的文件
new CleanWebpackPlugin(['build/*'], {
watch: true,
}),
// 指定生成 html 文件使用的模版文件
new HtmlWebpackPlugin({
template: 'index.html',
}),
new webpack.HotModuleReplacementPlugin(),
new UglifyJsPlugin({
uglifyOptions: {
ie8: false,
mangle: true,
output: { comments: false },
compress: {
warnings: false,
drop_console: true,
drop_debugger: true,
unused: false,
},
},
sourceMap: true,
cache: true,
}),
new CompressionPlugin(),
new MiniCssExtractPlugin({
filename: "[name].[hash:8].css",
chunkFilename: "[id].[hash:8].css",
}),
],
devServer: {
contentBase: path.join(__dirname, "build"),
compress: true,
port: 9000,
open: true,
inline: true,
},
};
module.exports = config;
npm run build
:...
// 删除指定的文件
clean-webpack-plugin: /Users/wewin/reactLearn/redux-blog/build/* has been removed.
...
Asset Size Chunks Chunk Names
main.ad06a35d.css 317 KiB main [emitted] main
main.ad06a35d.js 2.69 MiB main [emitted] main
index.html 265 bytes [emitted]
index.html.gz 196 bytes [emitted]
main.ad06a35d.css.gz 33.4 KiB [emitted]
main.ad06a35d.js.gz 501 KiB [emitted]
...
自动生成的 index.html 文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>redux blog</title>
<link href="/main.ad06a35d.css" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="/main.ad06a35d.js"></script>
</body>
</html>
js 和 css 文件被自动引入到了 html 页面,上次 build 生成的文件,会自动被删除。
被打包的文件里到底有些什么 --- BundleAnalyzerPlugin
上面对文件的压缩,css 的提取都起到了减少 js 提交的作用,但是经过上面两个步骤后,最后打包的 main.js 仍有 501 KiB,想要进一步减少文件体积,我们就要清楚 main.js 文件里到底有些什么,是什么导致了文件如此庞大。BundleAnalyzerPlugin
插件可以帮我们分析出文件的组成,可以以文件的或者网页的形式展示给我们。配置和使用这里不做具体的说明。
公共 JavaScript 模块抽离
将公共的 JavaScript 模块抽离,避免重复的引入,可以有效的减少 js 文件体积。webpack 4 可以使用 SplitChunksPlugin
插件来提取共同的 js,在 webpack 4 以下版本可以使用 CommonsChunkPlugin
插件。
webpackge.config.js
const path = require('path');
var webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CompressionPlugin = require("compression-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const config = {
entry: ['babel-polyfill', './src/app.js'],
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].[hash:8].js',
publicPath: '/',
},
mode: 'production',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader',
],
},
],
},
plugins: [
new CleanWebpackPlugin(['dist', 'build/*'], {
watch: true,
}),
new HtmlWebpackPlugin({
template: 'index.html',
}),
new webpack.HotModuleReplacementPlugin(),
new UglifyJsPlugin({
uglifyOptions: {
ie8: false,
mangle: true,
output: { comments: false },
compress: {
warnings: false,
drop_console: true,
drop_debugger: true,
unused: false,
}
},
sourceMap: true,
cache: true,
}),
new CompressionPlugin(),
new MiniCssExtractPlugin({
filename: "[name].[hash:8].css",
chunkFilename: "[id].[hash:8].css",
}),
],
optimization: {
splitChunks: {
chunks: 'initial',
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 2,
maxInitialRequests: 2,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /\/node_modules\//,
priority: -10,
},
'react-vendor': {
test: (module, chunks) => /react/.test(module.context),
priority: 1,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
}
}
}
},
devServer: {
contentBase: path.join(__dirname, "build"),
compress: true,
port: 9000,
open: true,
inline: true,
},
};
module.exports = config;
npm run build
: Asset Size Chunks Chunk Names
main.3413403b.js 8.33 KiB main [emitted] main
react-vendor~main.3413403b.css 318 KiB react-vendor~main [emitted] react-vendor~main
react-vendor~main.3413403b.js 2.68 MiB react-vendor~main [emitted] react-vendor~main
index.html 355 bytes [emitted]
main.3413403b.js.gz 3.2 KiB [emitted]
index.html.gz 214 bytes [emitted]
react-vendor~main.3413403b.css.gz 33.5 KiB [emitted]
react-vendor~main.3413403b.js.gz 498 KiB [emitted]
这里对 splitChunks 的配置基本上使用的基本都是默认配置,splitChunks 的使用可以参考官网。提取 js 不但可以缩小文件体积,对 React React-dom 这种基础依赖的提取更有利于缓存。
这里主要是记录下自己在减少打包后的文件体积使用到的 webpack 的几个插件,希望对有相同需求的朋友有所帮助。
欢迎指正!
原文作者:其言
本文来源: 掘金 如需转载请联系原作者