所谓的优化,也就是引入一些插件,使得代码的体积变小。
1. 删除没有意义的样式
在css文件中有一些没有用到的样式,希望在打包的过程中 删除掉它们。
安装两个插件 cnpm install purgecss-webpack-plugin glob --save-dev
glob
库:主要功能是查找匹配的文件
// 查找src文件夹下的所有文件夹的所有文件。
// {nodir: true} 不包含文件夹
/*
src-- index.js
-- index.css
-- index.html
-- a
-- index.js
*/
const glob = require('glob')
let paths = glob.sync("./src/**/*",{nodir: true})
/*
[ './src/a/index.js',
'./src/index.css',
'./src/index.html',
'./src/index.js' ]
*/
const glob = require('glob')
// 只要作用是删除没有意义的css。但不能在style标签中删除,只能配合 mini-css-extract-plugin 插件使用,先抽离css再去优化。
const PurgecssWebpackPlugin = require('purgecss-webpack-plugin')
plugins: [
new PurgecssWebpackPlugin({
paths: glob.sync("./src/**/*",{nodir: true})
// glob.sync(["./src/**/*","!x.js"],{nodir: true}) 忽略x.js文件
})
]
2. 图片优化
安装loader cnpm install image-webpack-loader --save-dev
。
image-webpack-loader
要配置在 file-loader
之后
module: [
{
test: /\.(jpe?g | png | gif)/,
use: [
{
loader: 'file-loader'
},
{
loader: 'image-webpack-loader',
// options 配置格式固定。
options: {
// 对关于一些图片清晰度的设置。
mozjpeg: {
progressive: true,
quality: 65
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75
}
}
}
]
}
]
3. CDN引入文件加载
这里在index.html中引入了jq的cdn版,可直接使用$,但为了防止因为直接使用 $ 而产生 来源的疑惑,我们需要手动import jquery, 但是的话,这样 webpack 会在 俩个文件中各打包一次jq。所以 我们还需要进行配置。
// index.html
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
// index.js
import $ from 'jquery'; // => 表明来源,但不打包
console.log($)
// webpack.config.js
module.exports = {
externals: {
'jquery': '$' // 不去打包代码里的jquery (脱离webpack打包,不被打入bundle中)
}
}
如果引入的 cnd文件 有点多的话,一个一个的引太麻烦,可以使用 插件 add-asset-html-cdn-webpack-plugin
。注意 它是 基于 html-webpack-plugin
的。
但是:我用的时候这个插件不能用,不知道为啥,可能是版本的原因,毕竟是1.0版本。
我们改用webpack-cdn-plugin
代替它,配置也很简单。
// webpack.config.js
let AddCdnPlugin = require('add-asset-html-cdn-webpack-plugin');
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
}),
// 添加cnd的插件
/*
new AddCdnPlugin(true,{
'jquery': 'https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js'
})
*/
new WebpackCdnPlugin({
modules: [
{
name: 'jquery',
path: 'https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js'
}
]
})
]
4. Tree-Shaking
‘摇晃树’,作用是 去除掉 js 中多余的代码。并且只支持es6,webpack内置的。
es6 import
导入语法为静态导入,require
为动态导入,所谓动态导入,就是可以在一些判断语句中可以导入。 if(){ require('...') }
。
tree-shaking 只能在 生产环境下使用。
删除’副作用’代码:在package.json 中添加 sideEffects: false
,只要引入的没有用到,就被删除。但是,如果只单纯写 false
,那么引入的 import './index.css'
也会被 tree-shaking掉。
// webpack.config.js
optimization: {
usedExports: true // 使用了哪个模块你说一下。
}
// calc.js
export const add = (a,b) => {
return a + b + 'add'
}
export const re = (a,b) => {
return a - b + 're'
}
// test.js
function test(){
return '123'
}
console.log(test())
export default test
// index.js
import {add} from './ache';
// 在 引入的 test 文件 中 内部 代码自己执行,导入的 test 应该是没用的代码,不过webpack依然认为它是有用的。我们需要手动处理这样的代码。
import test from './test' // 副作用代码,可能开发的时候是无意义的。应该被删。
import './index.css' // package.json 里 "sideEffects":["**/*.css",...],声明他不是副作用代码。
console.log(add())
5. Scope hoisting
作用域提升,减少作用域。它也是webpack内置的,不需要配。
因为每个模块导出的都是一个函数。
6. DLL打包
在打包的过程中,例如 react,react-dom 每一次webpack都会重新进行打包,我们希望,可以将 react,react-dom 这类固定文件提前打包好,当下一次打包的时候,直接去打包其他的代码即可,react,react-dom 可以直接拿出来用。
webpack默认打包之后的代码形式是这样的:
// module.exports = 'hello world'
(function () {
return 'hello world'
})()
// 代码是一个自执行函数
// 当我们在 output 配置中 配置 library:xxx,则会把导出的结果赋值给 xxx。
// let xxx = (function(){...})()
// 除了 library 之外,还有 libraryTarget ,根据变量使用的环境来选择你需要的模式,(var,commonjs,commonjs2,...)
以 raect react-dom 为例 ,生成一个只有 react,react-dom 的文件。
// 新建一个webpack.xxx.js (这里以webpack.dll.js为例)
// webapck.dll.js
const path = require('path');
module.exports = {
mode: 'development',
entry: ['react','react-dom'],
output: {
library: 'react', // 打包后接受自执行函数的名字叫ache
// libraryTarget: 'commonjs2',
filename: 'react.dll.js',
path: path.resolve(__dirname,'dll')
}
}
// package.json
"ache": "webpack --config ./webpack.dll.js"
然后,配置DLLPlugin
插件,生成manifest.json
文件。我们叫这个文件为缓存列表,通过这个缓存列表可以找到对应需要的 模块( react, react-dom )
const DLLPlugin = request('webpack').DLLPlugin
const path = require('path');
const DLLPlugin = require('webpack').DllPlugin
module.exports = {
mode: 'development',
entry: ['react','react-dom'],
output: {
library: 'react', // 打包后接受自执行函数的名字叫ache
// libraryTarget: 'commonjs2',
filename: 'react.dll.js',
path: path.resolve(__dirname,'dll')
},
plugins: [
new DLLPlugin({
name: 'react', // 暴露函数的名字。这里的name必须要与dll.js中函数名保持一致。
path: path.resolve(__dirname,'dll/manifest.json') // manifest.json 文件的绝对路径
})
]
}
// 我本地使用了 import React 语法,需要先去 manifest.json查找,找到后会加载对应的库的名字,可能会引用某个模块,会去dll.js文件查找。
然后,引入DLL引用插件,让在打包的时候去找minifest.json文件。
// webpack.config.js
const DLLReferencePlugin= require('webpack').DLLReferencePlugin;
plugins:[
new DLLReferencePlugin({
// 我打包 react ,会先去manifest.json文件中查找,找到后再去找真实的文件(dll.js)。
manifest: path.resolve(__dirname,'dll/manifest.json')
}),
...
]
通过上面以及配置差不多了,但是通过 manifest.json,会去找 name 对应 dll.js文件中的 变量,发现 dll.js文件包 没有被引入,找不到 react。
手动引太麻烦,这时还得需要一个插件:add-asset-html-webpack-plugin
。
cnpm install add-asset-html-webpack-plugin --save-dev
;
这个插件 会将 dll.js 文件拷贝到 dist 目录下,然会在自动引入 index.html 中。
// webpack.config.js
const AddAssetHtmlPlugin= require('add-asset-html-webpack-plugin')
plugins: [
new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname,'./dll/react.dll.js') // 将该文件拷贝到dist目录下
}),
...
]
一般来说,DLL插件是用在开发环境下的。
7.动态加载
用来实现动态分离文件
import
语法引入。
核心原理是 使用jsonp动态导入需要引入的文件。
import 可以实现代码分割。
// index.js
button.addEventListener('click',() => {
// 动态导入,类比 路由懒加载 import语法
import('./ache.js').then(data => {
console.log(data.add(11,22))
})
})
给动态引入的文件增加名字
output: {
filename: 'xxx' // 同步打包的名字
chunkFilename: '[name].main.js' // 异步打包的名字 name 默认是0,1,2...递增的。
}
import(/* webpackChunkName: "video" */'./video.js').then(data => { // 会把 "video" 传入到 上面 [name] 中。
console.log(data)
})
// 打包后 video.main.js
8. 打包文件分析工具
8.1 多入口
// webpack.config.js
{ // entry 有三种写法:字符串,数组,对象
entry: {
'a': './a.js',
'b': './b.js'
}
}
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
chunks: ['a'] //要引入打包后的 'a' 代码块。
}),
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'login.html',
chunks: ['b'], // 如果是 ['b','a'],我们希望 login.html中是按照 这个数组的顺序引进的,但是webpack是按照entry的顺序引入的,所以 我们要配置 chunkSortMode。
chunkSortMode: 'manual' // 手动按照我的顺序来执行
})
]
8.2 分析依赖
按装 cnpm install webpack-bundle-analyzer --save-dev
可视化分析工具。
多用在生产环境下,并且可以帮助我们将第三方包进行分离。
会自动生成一个8888端口。
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
plugins: [
env !== 'development' && new BundleAnalyzerPlugin()
].filter(Boolean)
9. 第三方模块的抽离
当遇到类似于 react react-dom 这样较大的库时,需要抽离打包。可以使用下面属性。
dllPlugin
不要和 splitchunks
共同使用。
// webpack.config.js
optimization: {
splitChunks: {
// chunk: initial 只操作同步的 ;all所有的;async异步的。
chunks: 'async', // 默认支持异步代码分割 import()
minSize: 30000, // 文件超过30K 我就回抽离它
maxSize: 0,
minChunks: 1, // 最少模板引用一次才抽离
maxAsyncRequests: 30, // 最多5个请求
maxInitialRequests: 30, // 最多首屏加载30个请求
automaticNameDelimiter: '~', // xxx~a~b
enforceSizeThreshold: 50000,
automaticNameMaxLength: 30, // 最长名字大小
cacheGroups: { // 缓存组, cacheGroups内配置优先级高于外部配置
// react: {
// test: /[\\/]node_modules[\\/]\/react|react-dom/, // 要抽离的模块
// priority: 1 // 优先级
// },
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: { // common~a~b
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
// a.js
import ('jquary') // 异步
import React from 'react' // 同步
10. 费时分析
测试当前打包的速度
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smw = new SpeedMeasureWebpackPlugin () //在外面new
// 将我们的打包代码包起来
module.exports = env => {
return smv.wrap({
mode: env,
entry: ...
output: ...
})
}