一、webpack 性能优化之缓存
-
创建空文件夹,通过
npm init
命令初始化package.json
文件,通过npm install webpack webpack-cli -g
命令全局下载webpack
和webpack-cli
,通过npm install webpack webpack-cli -D
命令本地下载webpack
和webpack-cli
,通过npm i html-webpack-plugin mini-css-extract-plugin optimize-css-assets-webpack-plugin -D
命令下载html-webpack-plugin
、mini-css-extract-plugin
和optimize-css-assets-webpack-plugin
,通过npm i eslint-loader eslint -D
命令下载eslint-loader
和eslint
,通过npm i eslint-config-airbnb-base eslint-plugin-import eslint -D
命令下载eslint-config-airbnb-base
、eslint-plugin-import
和eslint
,eslint-config-airbnb-base
依赖于eslint-plugin-import
和eslint
,通过npm i postcss-loeader postcss-preset-env -D
命令下载postcss-loeader
和postcss-preset-env
,通过npm i css-loader less-loader -D
命令下载css-loader
和less-loader
,通过npm i url-loader file-loader html-loader -D
命令下载url-loader
、file-loader
和html-loader
,通过npm i babel-loader @babel/core @babel/preset-env -D
命令下载babel-loader
、@babel/core
和@babel/preset-env
,通过npm i @babel/polyfill core-js -D
命令下载@babel/polyfill
和core-js
。 -
在里面创建
src
文件夹,里面创建css
和js
文件夹,index.html
和server.js
。在css
中创建index.css
,在js
中创建index.js
,代码如下所示:
- index.css
html, body { margin: 0; padding: 0; height: 100%; background-color: deeppink; }
- index.js
import '../css/index.css'; function sum(...args) { return args.reduce((p, c) => p + c, 0); } // eslint-disable-next-line console.log(sum(1, 2, 3, 4));
- index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>webpack</title> </head> <body> <h1>hello cache</h1> </body> </html>
- server.js
const express = require('express'); const app = express(); app.use(express.static('build', { maxAge: 1000 * 3600 })); app.listen(3000);
- 创建
webpack.config.js
文件,通过require
引入path
、html-webpack-plugin
、mini-css-extract-plugin
和optimize-css-assets-webpack-plugin
。通过process.env.NODE_ENV
定义nodejs
环境变量:决定使用browserslist
的哪个环境。定义一个复用loader
,在下面匹配.css
和.less
文件的时候,需要重复使用。通过MiniCssExtractPlugin.loader
提取css
单独文件,使用css-loader
,使用postcss-loader
做css
的兼容性处理,同时使用postcss-preset-env
做css
的按需加载的兼容性处理,options
是postcss-loader
的配置项,ident
是固定写法,plugins
是使用postcss-preset-env
这个插件。entry
是入口文件,output
是出口文件,filename
是输出的文件名,path
是文件的输出路径。module
是loader
的配置,rules
是详细的loader
配置。在rules
中使用oneOf
,以下的loader
只会匹配一个,不能有两个配置处理同一种类型文件,如果有需要提取出来。第一个规则是匹配以.css
结尾的文件,通过use
使用commonCssLoader
这个抽出复用的loader
。第二个规则是匹配以.less
结尾的文件,通过use
使用commonCssLoader
这个抽出复用的loader
和less-loader
。第三个规则是匹配以.js
结尾的文件,通过exclude
不包括node_modules
,通过enforce
优先执行,通过loader
使用eslint-loader
,通过options
设置自动修复eslint
的错误。第四个规则是匹配以.js
结尾的文件,通过exclude
不包括node_modules
,通过loader
使用babel-loader
,通过options
进行配置,presets
为预设,指示babel
做怎么样的兼容性处理,使用babel/preset-env
这个预设,cacheDirectory
是开启babel
缓存,第二次构建时,会读取之前的缓存。通过useBuiltIns
按需加载,通过corejs
指定core-js
版本,通过targets
指定兼容性做到哪个版本浏览器。js
兼容性处理需要使用到babel-loader
、@babel/core
和@babel/preset-env
。如果是基本js
兼容性处理,需要使用@babel/preset-env
,但是只能转换基本语法,如promise
高级语法不能转换。如果使用全部js
兼容性处理,需要使用@babel/polyfill
,但是只要解决部分兼容性问题,但是将所有兼容性代码全部引入,体积太大了,所有就需要做兼容性处理的就做做兼容性处理的就做,使用core-js
。第五个规则是匹配以.jpg
、png
和gif
格式的图片资源,通过loader
使用url-loader
,url-loader
依赖于file-loader
。进行options
配置,设置limit
为8 * 1024
,图片大小小于8kb
,就会被base64
处理,给图片做优化。这样做的优点是减少请求数量(减轻服务器压力),缺点是图片体积会更大(文件请求速度更慢)。设置esModule
为false
,由于url-loader
默认使用es6
模块化解析,而html-loader
引入图片是commonjs
,解析时会出问题为[object Module]
,所以关闭url-loader
的es6
模块化,使用commonjs
解析。设置name
为[hash:10].[ext]
,给图片进行重命名,因为图片值为hash
,[hash:10]
取图片的hash
的前10
位,[ext]
取文件原来扩展名。第三个规则是匹配以.html
结尾的文件,使用loader
为html-loader
,处理html
文件的img
图片(负责引入img
,从而能被url-loader
进行处理)。第六个规则是匹配以.html
结尾的文件,通过loader
使用html-loader
。第七个规则是匹配其它资源,通过loader
使用file-loader
,通过options
进行输出路径配置,输出文件夹为media
。plugins
里面是一些插件配置,通过MiniCssExtractPlugin
插件提取css
以及对输出的css
文件进行重命名,通过OptimizeCssAssetsWebpackPlugin
对css
文件进行压缩,通过HtmlWebpackPlugin
复制里面的文件,并自动引入打包输出的所有资源(JS/CSS
),进行minify
配置,通过collapseWhitespace
移除空格,通过removeComments
移除注释,压缩 html 文件。设置mode
为production
,在生产环境下webpack
会自动压缩js
代码,代码如下所示:
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
process.env.NODE_ENV = 'production'
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
require('postcss-preset-env')()
]
}
}
]
module.exports = {
entry: 'src/js/index.js',
output: {
filename: 'js/built.[contenthash:10].js',
path: resolve(__dirname, 'build')
},
module: {
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
],
cacheDirectory: true
}
},
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:].[ext]',
outputPath: 'imgs',
esModule: false
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
test: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.[contenthash:10].css'
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
mode: 'production',
devtool: 'source-map'
}
-
缓存是分为两种,
babel
缓存和文件资源缓存。babel
缓存是需要在options
中设置cacheDirectory
为true
,让第二次打包构建速度更快。文件资源缓存是让代码上线运行缓存更好使用,通过对输出的文件进行设置不同的hash
值。 -
文件资源缓存的分类,如下所示:
类型 | 类型描述 | 类型问题 |
---|---|---|
hash | 每次wepack构建时会生成一个唯一的hash值 | 因为js和css同时使用一个hash值,如果重新打包,会导致所有缓存失效,但是却只改动一个文件 |
chunkhash | 根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样 | js和css的hash值还是一样的,因为css是在js中被引入的,所以同属于一个chunk |
contenthash | 根据文件的内容生成hash值。不同文件hash值一定不一样 | 效果最好 |
- 在命令行输入
webpack
命令,资源就会进行打包,以及相应的压缩代码,同时通过node server.js
命令启动服务器服务,观察缓存的变化。
二、webpack 性能优化之 tree-shaking
- 在上面缓存的项目中,修改
js
文件夹中的index.js
,同时新增test.js
,删除server.js
文件,代码如下所示:
- index.js
import '../css/index.css'; import { mul } from './test.js' function sum(...args) { return args.reduce((p, c) => p + c, 0); } // eslint-disable-next-line console.log(mul(2, 3)); // eslint-disable-next-line console.log(sum(1, 2, 3, 4));
- test.js
export function mul(x, y) { return x * y; } export function count(x, y) { return x - y; }
tree shaking
的本质是去除无用代码,摇树优化。无用代码消除在广泛存在于传统的编程语言编译器中,编译器可以判断出某些代码根本不影响输出,然后消除这些代码,这个称之为DCE
(dead code elimination
),tree shaking
是DCE
的一种新的实现。tree shaking
的前提是必须使用ES6
模块化和开启production
环境,作用是减少代码体积。在package.json
中配置,通过"sideEffects": false
是所有代码都没有副作用,都可以进行tree shaking
。但是问题是可能会把css / @babel/polyfill
这些副作用文件干掉,这些还是有需要的。所有,可以通过"sideEffects": ["*.css", "*.less"]
,tree shaking
就不会对这些文件进行处理,没有副作用的代码,代码如下所示:
"sideEffects": [
"*.css",
"*.less"
]
- 在命令行输入
webpack
命令,资源就会进行打包,以及相应的压缩代码和去除无用的代码。
三、webpack 性能优化之 code-split
-
创建空文件夹,通过
npm init
命令初始化package.json
文件,通过npm install webpack webpack-cli -g
命令全局下载webpack
和webpack-cli
,通过npm install webpack webpack-cli -D
命令本地下载webpack
和webpack-cli
,通过npm i html-webpack-plugin -D
命令下载html-webpack-plugin
。 -
在里面创建
js
文件夹,里面创建index.js
和test.js
,然后创建index.html
,代码如下所示:
- index.js
function sum(...args) { return args.reduce((p, c) => p + c, 0); } // eslint-disable-next-line console.log(sum(1, 2, 3, 4));
- test.js
export function mul(x, y) { return x * y; } export function count(x, y) { return x - y; }
- index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>webpack</title> </head> <body> <h1>hello cache</h1> </body> </html>
- 创建
webpack.config.js
文件,通过require
引入path
和html-webpack-plugin
。entry
是入口文件,入口文件分为单入口文件和多入口文件。如果是单入口,输出只有一个bundle
,单页面。如果是多入口,多个bundle
,多页面。output
是出口文件,filename
是输出的文件名,path
是文件的输出路径。[name]
是取文件名,[contenthash:10]
是取内容的hash
值前10
位。plugins
里面是一些插件配置,通过HtmlWebpackPlugin
复制里面的文件,并自动引入打包输出的所有资源(JS/CSS
),进行minify
配置,通过collapseWhitespace
移除空格,通过removeComments
移除注释,压缩html
文件,设置production
,在生产环境下webpack
会自动压缩js
代码,代码如下所示:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
main: './js/index.js',
test: './js/test.js'
},
output: {
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
mode: 'production'
}
- 第二种方式,修改
index.js
和test.js
,代码如下所示:
- index.js
import $ from 'jquery'; function sum(...args) { return args.reduce((p, c) => p + c, 0); } // eslint-disable-next-line console.log(sum(1, 2, 3, 4)); // eslint-disable-next-line console.log($);
- test.js
import $ from 'jquery'; // eslint-disable-next-line console.log($); export function mul(x, y) { return x * y; } export function count(x, y) { return x - y; }
- 修改
webpack.config.js
文件,添加optimization
,在splitChunks
中设置chunks
为all
。optimization
可以将node_modules
中代码单独打包一个chunk
最终输出,自动分析多入口chunk
中,有没有公共的文件,如果有会打包成单独一个chunk
,代码如下所示:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: 'src/js/index.js',
output: {
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
optimization: {
splitChunks: {
chunks: 'all'
}
},
mode: 'production'
}
- 第三种方式,修改
index.js
和test.js
,代码如下所示:
- index.js
function sum(...args) { return args.reduce((p, c) => p + c, 0); } import(/* webpackChunkName: 'test' */'./test') .then(({ mul, count }) => { // eslint-disable-next-line console.log(mul(2, 5)); }) .catch(() => { // eslint-disable-next-line console.log('文件加载失败~'); }); // eslint-disable-next-line console.log(sum(1, 2, 3, 4));
- test.js
// eslint-disable-next-line console.log($); export function mul(x, y) { return x * y; } export function count(x, y) { return x - y; }
- 通过
js
代码,让某个文件被单独打包成一个chunk
,import
动态导入语法:能将某个文件单独打包,核心代码如下:
import(/* webpackChunkName: 'test' */'./test')
.then(({ mul, count }) => {
// eslint-disable-next-line
console.log(mul(2, 5));
})
.catch(() => {
// eslint-disable-next-line
console.log('文件加载失败~');
});
- 在命令行输入
webpack
命令,资源就会进行打包,以及相应的压缩代码,以及代码分割。
四、webpack 性能优化之预加载和懒加载
- 在上面的
code-split
的项目中,修改index.js
和test.js
文件,代码如下所示:
- index.js
console.log('index.js文件被加载了~'); document.getElementById('btn').onclick = function() { import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => { console.log(mul(4, 5)); }); };
- test.js
console.log('test.js文件被加载了~'); // eslint-disable-next-line console.log($); export function mul(x, y) { return x * y; } export function count(x, y) { return x - y; }
webpack.config.js
文件不变,懒加载是当文件需要使用时才加载,预加载是会在使用之前,提前加载js
文件,正常加载是并行加载,同一时间加载多个文件。需要注意的是,预加载prefetch
,等其他资源加载完毕,浏览器空闲了,再偷偷加载资源。webpackChunkName
是懒加载,webpackPrefetch
是预加载,核心代码如下:
document.getElementById('btn').onclick = function() {
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
};