之前了解了webpack
的基本打包流程或者说原理,如果只是配置一个基本的webpack
打包配置,
打包后的文件会变得很大,当你项目部署后,用户打开对应的界面,也许会很长时间才加载完(当然和网络环境或硬件设备也有关系),尤其是单页面应用效果很明显,这样的用户体验自然是不好的,所以此文章主要是对webpack
打包优化的一个小小总结
首先搭建好一个基本的项目,目录结构
├── dist // 打包目录
│ ├── bundle.js // 打包后的js
│ └── index.html // 入口html文件
├── package-lock.json
├── package.json
├── public
│ └── index.html // html模板文件
├── src
│ ├── actions // redux actions文件
│ │ └── index.js
│ ├── app.js // webpack打包入口文件,也是项目的入口文件
│ ├── assets // scss
│ │ ├── about.scss
│ │ ├── title.scss
│ │ └── user.scss
│ ├── dev.js // 开发时的一些配置
│ ├── pages // page页面
│ │ ├── about.jsx
│ │ ├── index.jsx
│ │ ├── title.jsx
│ │ └── user.jsx
│ ├── reducers // redux中的reducer
│ │ └── index.js
│ └── store.js
└── webpack.config.js
webpack
基本配置如下
const path = require('path')
const HtmlWebPackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/app.js',
output: {
path: path.resolve(__dirname, './dist/'),
filename: 'bundle.js'
},
resolve: {
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx']
},
devServer: {
contentBase: path.join(__dirname, './src/'),
publicPath: '/',
host: '127.0.0.1',
port: 3000,
hot: true,
stats: {
colors: true
}
},
module: {
rules: [{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}, {
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}]
},
plugins: [
new HtmlWebPackPlugin({
template: 'public/index.html',
filename: 'index.html',
inject: true
})
]
}
优化:
1.基础优化
配置loader
,添加exclude
、include
缩小搜索范围,比如
{
test: /\.jsx?$/,
exclude: /node_modules/, // 过滤node_modules
use: {
loader: 'babel-loader'
}
}
2.使用DllPlugin
将一些变动较少或者根本不会变动的库先打包生成对应的.dll.js
,在webpack
打包的时候,将这些资源引入
首先新建一个webpack.config.dll.js
const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
module.exports = {
entry: {
react: ['react', 'react-dom']
},
output: {
filename: '[name].dll.js',
// 生成的文件目录
path: path.resolve(__dirname, 'dist'),
// 动态链接库名称
library: '_dll_[name]'
},
plugins: [
new DllPlugin({
name: '_dll_[name]',
path: path.join(__dirname, 'dist', '[name].manifest.json'),
context: __dirname,
})
]
}
然后在package.json
的scripts
中添加dll打包命令
webpack --config webpack.config.dll.js
然后运行此命令,可以看到会在dist
目录下面生成react.dll
,react.manifest.json
两个文件,这就是生成的dll
文件,然后webpack.config.js
里面添加配置,打包时引入这些的dll
文件。
首先安装插件add-asset-html-webpack-plugin
,引入DllReferencePlugin
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
插件里面配置(webpack4+)
new DllReferencePlugin({
context: __dirname,
manifest: require('./dist/react.manifest.json'),
}),
new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname, './dist/react.dll.js')
})
这样打包时页面就会引入react.dll.js
资源了,从而减少重复的资源打包。这种方式可以简单的理解为,抽出公共模块打包,然后引入
3.splitChunks
以前webpack
拆分模块还会用CommonsChunkPlugin
,在webpack4
后,可以直接用splitChunks
来代替完成这项工作,在optimization
里面设置了splitChunks
后,打包分割出来的文件是默认压缩过的
splitChunks: {
chunks: "async", // "initial" | "all" | "async",对哪种代码进行分割
minSize: 30000, // 超过minSize的包才做代码分割
minChunks: 1, // 一个包至少被用了多少次的时候才进行代码分割
maxAsyncRequests: 5, // 按需加载最多能加载多少个模块
maxInitialRequests: 3, // 对于entry里面的文件做代码分割最多能生成多少个js文件
automaticNameDelimiter: '~', // 文件生成时的连接符
name: true, // 为true的时候,打包出来的文件名由cacheGroups里面设置的为准
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/, // 匹配哪些需要分割的模块
priority: -10, // 优先级
filename: 'vendors.js'// 打包到一个叫vendors.js的文件
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
4.catch-loader
在一些性能开销较大的loader
之前添加此 loader
,以将结果缓存到磁盘里,不做过多介绍
module.exports = {
module: {
rules: [
{
test: /\.ext$/,
use: [
'cache-loader',
...loaders
],
include: path.resolve('src')
}
]
}
}
5.HappyPack
HappyPack
使用node
多线程进行构建来提升构建的速度,使用情况较少
const HappyPack = require('happypack')
const os = require('os')
const happyThreadPool = HappyPack.ThreadPool({
size: os.cpus().length
})
......
plugins: [
new HtmlWebPackPlugin({
template: 'public/index.html',
filename: 'index.html',
inject: true
}),
new HappyPack({
id: 'happyBabel',
loaders: [{
loader: 'babel-loader?cacheDirectory=true',
}],
threadPool: happyThreadPool,
verbose: true,
})
],
6.css
的压缩
对于scss
文件,一般来讲,依次配置sass-loader
、css-loader
、style-loader
来进行处理
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}
最后加载完后的页面css
会以内联样式的形式签入到页面中。在webpack4
中使用mini-css-extract-plugin
插件来提取、压缩css
。首先将style-loade
用MiniCssExtractPlugin.loader
代替,然后使用MiniCssExtractPlugin
插件,打包出对应的文件,并在页面中引入对应的css
文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
......
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
}
new MiniCssExtractPlugin({
filename: '[name].[hash:5].css',
chunkFilename: '[id].[hash:5].css'
})
7.图片的处理
同样的,先用file-loader
解析图片保证webpack能处理,然后可以用image-webpack-loader
压缩图片,或者说用url-loader
将图片转为base64
编码的形式,不多赘述
8.CDN加速
通常在打包中,把不变的一些静态文件放到CDN
上,可以直观地减小资源包大小,比如项目中用到了clipboard.js
,然后在模板html
中引入这个文件
<script src="https://cdn.bootcdn.net/ajax/libs/clipboard.js/2.0.6/clipboard.min.js" defer></script>
一般来讲,这种方式引入js静态文件,那么全局对象(如window)里面是会有对应的对象的,就可以直接引用对应的对象。当然还可以加上
externals: {
clipboard: 'clipboard'
},
这样就保证了打包时不打包clipboard
,优化打包后的文件体积
还有一种方式就是把所有的静态资源,如js
、css
都放在相应的CDN
上,在打包的config
中指定对应资源的CDN
域名,同样配置externals
。但其实这种方式有个很大的缺点就是,如果全部这样做的话,万一CDN
挂了,整个页面也就挂了
9.按需加载
以react
为例,使用react-loadable
来做按需加载,以前还有些其它方法这里就不赘述。主要是针对react-router
做一个处理,首先安装依赖react-loadable
,入口文件中封装一个loading
组件,用于页面加载时给一个提示,然后封装一个方法,用于异步加载这些组件
import Loadable from 'react-loadable'
const Loading = (props) => {
return <div>这是一个loading组件</div>
};
const asyncLoad = loader => Loadable({
loader,
loading: Loading
})
以下有三个页面,index
、about
、user
,调用asyncLoad
,引入这些文件
const Index = asyncLoad(() => import('./pages/index'))
const About = asyncLoad(() => import('./pages/about'))
const User = asyncLoad(() => import('./pages/user'))
再配置好路由,这样打包出来的js
文件就只会在对应页面或者说对应路由命中时加载了,从而提升页面的一个加载速度,当然也把打包出来的js文件进一步的分割,减小体积,当然需要注意的是,要在webpack.config
里面配置好chunFilename(按需加载的chunk名字)
,资源的publickPath
<Router>
<Switch>
<Route path="/" exact component={Index} />
<Route path="/about/" component={About} />
<Route path="/users/" component={User} />
</Switch>
</Router>
webpack.config.js
output: {
path: path.resolve(__dirname, './dist/'),
filename: '[name].[hash:5].bundle.js',
chunkFilename: '[name].[hash:5].bundle.js',
publicPath: '/'
},