当有设置时,指定的目录将用来缓存 loader 的执行结果。之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程(recompilation process)
babel-loader?cacheDirectory
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use :['babel-loader?cacheDirectory=true'],
exclude: /node_modules/
}
]
}
例子一: moment
https://webpack.js.org/plugins/ignore-plugin/
因为moment使用以下代码导入:
require('./locale/' + name);
所以匹配到 ./locale
就不引入
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/
});
这意味着“任何’./locale’以结尾结尾的目录中匹配的require语句’moment’都将被忽略。
业务代码:
import moment from 'moment';
//设置语言
//手动引入所需要的语言包
import 'moment/locale/zh-cn';
import React from 'react';
moment.locale('zh-cn');
const MomentDemo = () => {
return (
<div>
{
moment().format('LLLL')}
</div>
)
}
export default MomentDemo;
例子二:echarts 未优化+800kb:
// import ReactECharts from 'echarts-for-react';内部引用了echart/index.js(这个包会将所有的库引入,如折线图、柱状图等,但是我们这边只需要折线图)
import ReactECharts from 'echarts-for-react';
import React from 'react';
const option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line'
}]
};
const EChartDemo = () => {
return (
<ReactECharts
option={
option}
/>
)
}
export default EChartDemo;
配置方式优化+630kb
// webpack.config.js
new webpack.IgnorePlugin({
resourceRegExp:/^\.\/lib\/chart\/(.)*/,
contextRegExp:/echarts$/,
}),
// app
import ReactECharts from 'echarts-for-react';
import React from 'react';
// 引入需要的折线图库
import 'echarts/lib/chart/line';
const option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line'
}]
};
const EChartDemo = () => {
return (
<ReactECharts
option={
option}
/>
)
}
export default EChartDemo;
代码层次优化+340kb:
// import ReactECharts from 'echarts-for-react'; // 引用这个包会引入所有echart内容
// 引入核心的ReacEchart组件,不引入默认ReactEchart防止引入所有的echart模块
import ReactECharts from 'echarts-for-react/lib/core';
// 引入需要的折线图库
import 'echarts/lib/chart/line';
// 引入核心echart组件,引入默认echart会导致止引入所有的echart模块
import echarts from 'echarts/lib/echarts';
import React from 'react';
const option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line'
}]
};
const EChartDemo = () => {
// 这里需要注入echart组件
return (
<ReactECharts
echarts={
echarts}
option={
option}
/>
)
}
export default EChartDemo;
因为echart默认引用方式里面直接写死了全量的,
// index.js
require("./lib/chart/line");
require("./lib/chart/bar");
require("./lib/chart/pie");
require("./lib/chart/scatter");
只能改成'echarts/lib/echarts'
并注入'echarts-for-react/lib/core'
总结:
无论是配置按需加载还是根据第三方包内部实现入手,都需要去看对应代码结构。从代码入手的优化难度比较大,但是收益也是最高的。
有些第三方包已经构建完了代码,那就直接使用构建完的代码,不直接对源码进行编译。
如:antd,直接使用antd/dist/antd.min.js
即可,不需要额外打包。
个人不建议使用这个功能,如果有类似JQ引用第三方的功能,建议使用CDN加速。
而且在有提供ES6+版本代码的情况下,直接引用ES6+的代码,因为现在构建速度大都不是问题还能利用Tree Shaking
去掉无用代码。
https://github.com/amireh/happypack#readme
// @file: webpack.config.js
const HappyPack = require('happypack');
{
module:{
rules: [{
test: /.js$/,
// 1) replace your original list of loaders with "happypack/loader":
// loaders: [ 'babel-loader' ],
// 1) 替换原先的loader为 happypack/loader
use: 'happypack/loader',
include: [ /* ... */ ],
exclude: [ /* ... */ ]
}
]
},
plugins :[
// 2) create the plugin:
// 2) 创建插件
new HappyPack({
// 3) re-add the loaders you replaced above in #1:
// 3) 重新添加刚才被替换的loader
loaders: [ 'babel-loader' ]
})
]
}
项目比较小的时候启用多线程反而会拖慢打包速度,因为多线程之前开启、销毁、通信都需要额外的性能支出
webpack有默认的代码压缩功能,如果项目比较大、文件比较多,则引入多线程压缩能节省时间。
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
{
plugins:[
// window.ENV = "production"
new webpack.DefinePlugin({
ENV:JSON.stringify('production')}),
// 打包之前清空文件夹
new CleanWebpackPlugin(),
// 压缩css
new MiniCssExtractPlugin({
filename:'css/[name]_[contenthash:8].css'
}),
new HappyPack({
loaders: [ 'babel-loader?cacheDirectory=true' ]
}),
new ParallelUglifyPlugin({
})
]
}
项目比较小的时候启用多线程反而会拖慢打包速度,因为多线程之前开启、销毁、通信都需要额外的性能支出
生产环境的代码优化比如何打包更值得关注,以下“默认”代表mode == production的时候默认开启。
把代码体积整小,文件小加载快。
// webpack.config.js
{
mode:'production' // webpack5 根据mode自动会对代码进行压缩
}
摇晃树,把没有根的叶子摇下来。相对于代码,把没有引用的代码去掉。ES6静态依赖,在构建代码的时候能发现没有引用的模块。
webpack自带功能,无需配置,但是要对代码引用处理
// util.js
export const log = function(){
console.log(arguments);
}
// 没有引用到
export const warn = function(){
console.warn(arguments);
}
// main
import {
log } from './util.js'
log('123')
// util.js
export const log = function(){
console.log(arguments);
}
// 没有引用,所以被去掉
// export const warn = function(){
// console.warn(arguments);
// }
// main
import {
log } from './util.js'
log('123')
注意:此功能仅对ES import有效,所以我们引用尽量直接引用ES6+版本的功能
如何优化?加入以下配置:
// webpack.base.config.js
{
resolve:{
mainFields: ['jsnext:main', 'module','browser', 'main']
}
}
解释:
使用import xxx from 'XXX'
优先引用其package.json里面配置的路径,如果匹配不到再找下一个,main肯定会有。
// redux package.json
{
"jsnext:main": "es/index.js",
"license": "MIT",
"main": "./lib/index.js",
"module": "es/index.js",
}
import redux from 'redux';
// 'jsnext:main' 直接命中
import redux from 'redux/es/index.js'
This plugin will enable the same concatenation behavior in webpack. By default this plugin is already enabled in production mode and disabled otherwise. If you need to override the production mode optimization, set the optimization.concatenateModules option to false.
// util.js
export const doLog = function(){
console.log(...arguments)
}
export const doWarn = function(){
console.warn(...arguments)
}
import {
doLog } from './util'
doLog(123)
doLog(456)
如果没有scope-hosting,则是:
// index、util被拆分成两个闭包,需要引入才能使用
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./util */ "./src/util.js");
(0,_util__WEBPACK_IMPORTED_MODULE_0__.doLog)(123)
;(0,_util__WEBPACK_IMPORTED_MODULE_0__.doLog)(456)
开启production后为:
// 这些方法被打包成一个文件、提升为同一个作用域的代码
!function(){
"use strict";const n=function(){
console.log(...arguments)};n(123),n(456)}();
把文件拆分成第三方依赖包、公共代码、业务代码三部分。
// webpack.config.js
{
plugins:[
new HttpWebpackPlugin({
template:PATH.HTML_PATH,
filename:'index.html',
// 这个chunks会按照依赖的关系自动注入,无需刻意排序
chunks:['index','vendor','common']
})
],
optimization:{
//https://webpack.docschina.org/plugins/split-chunks-plugin/#optimization-splitchunks
splitChunks: {
chunks: 'all',
cacheGroups: {
default: {
name:'common',
minSize: 0, // 太小的公共文件也没必要拆分,这里做演示填了0
minChunks: 2,// 最小几次引用
priority: -20 // 决定分块的优先级顺序
},
vendor: {
name:'vendor',
test: /node_modules/,
priority: -10
}
}
}
}
}
好处:
{
output: {
path: PATH.PROD_OUT_PATH,//打包后的文件存放的地方
// 指定存放 JavaScript 文件的 CDN 目录 URL
publicPath: 'http://cdn.xxx.com/',
filename: "lib/[name]_[contenthash:8].js"//打包后输出文件的文件名
}
}
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>indextitle>
<link href="http://cdn.xxx.com/css/common_e40efa98.css" rel="stylesheet" />
head>
<body>
<div id="root">div>
<script src="http://cdn.xxx.com/lib/vendor_8140b223.js">script>
<script src="http://cdn.xxx.com/lib/common_fa02369b.js">script>
<script src="http://cdn.xxx.com/lib/index_0daada23.js">script>
body>
html>
把JS一股脑都丢给CDN服务商去,应用服务器只需要提供HTML文件的访问。节省服务器带宽、提高并发量。带宽以及网络优化交给CDN,他们是专业的。
推荐七牛
{
output: {
path: PATH.PROD_OUT_PATH,//打包后的文件存放的地方
filename: "lib/[name]_[contenthash:8].js"//打包后输出文件的文件名
}
}
好处
解决了浏览器缓存被需要又不被需要的死锁。(缓存存在节省带宽,缓存存在代码更新后无法及时更新)
小贴士
建议新的静态文件提取出来放到原来的目录里面,新旧代码共存。
原因如下:
项目上线后更新,删除所有静态文件后替换为新的文件,这时候如果index.html
被缓存则我们的访问结果是旧的
<script src="http://cdn.xxx.com/lib/vendor_version1.js">script>
这时如果js缓存失效则页面就会出错。
新旧代码共存的时候,即使页面被缓存,也可以命中旧的JS文件,不至于出错。
一个页面有时候不一定用到所有功能,所以当在有需要的时候按需加载则能提高首屏加载的速度。
import('../common/log-util').then(code => {
code.default.log(123)
})
使用import()
,在production打包环境下会自动为log-util
生成一个带哈希值后缀的文件,这时候可以在需要的时候才加载这个代码。
小图片转成base64放到JS文件减少网络请求次数。大图片则直接引用,有利于浏览器缓存。
// webpack.config.js
{
module: {
rules: [
{
// 支持:import '{filepath}'
test: /\.(png|jpg|gif|svg)$/,
use: {
loader:'url-loader',
options: {
// 超出10kb 文件的方式引入,小于10kb使用blob的方式
// in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).This can impact web performance.
// 为了最佳性能,图片请使用小于244kb,超过可以使用cdn
limit: 1024 * 10,
outputPath: 'images'
}
}
}
]
},
}
构建出来有什么问题我们再去解决,webpack-bundle-analyzer
提供可视化的分析工具。
npm install webpack-bundle-analyzer -D
// webpack.prod.analyze.js
const webpack = require("webpack");
const {
merge } = require("webpack-merge");
const {
BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const prodConfig = require('./webpack.prod');
// 智能合并webpack common配置
module.exports = merge(prodConfig,{
mode:'production',
plugins:[
// window.ENV = "production"
new webpack.DefinePlugin({
NODE_ENV:JSON.stringify('production')}),
new webpack.DefinePlugin({
npm_config_report:true}),
// 性能分析,仅在npm run analyze
new BundleAnalyzerPlugin()
]
});
{
"scripts": {
"analyze": "webpack --config script/webpack.prod.analyze.js"
}
}
npm run analyze
浏览器会自动打开分析的页面,可以根据页面上文件大小做针对性配置调整。
// webpack.config.dev.js
{
plugins:[
new webpack.DefinePlugin({
ENV:JSON.stringify('development')}),
new webpack.HotModuleReplacementPlugin({
})
],
devServer: {
progress:true,// 进度条
port: 10086, // 本地服务器端口号
hotOnly:true,// 页面构建失败不刷新页面
hot: true, // 热重载
open: true, // 自定打开浏览器
proxy:{
'/api':'http:localhost:8080'
}
},
// 开发环境可以直接断点源码
devtool: 'source-map',
// HotModuleReplacementPlugin 热更新后刷新页面需要这个配置,问题描述如下
target: "web"
}
webpack5不能更新后不刷新浏览器,这是个BUG:
问题:
Yes, we need to fix it in webpack-dev-server, hope I will find time on it, anyway you can send a PR
解决方案:
adding target: ‘web’ (which overwrites the default being ‘browserlist’ since 5.0.0-rc.1) to Webpack config resolves the issue
以上配置只能支持css变更后页面不刷新,显示新的效果。如果要对js刷新页面状态不刷新,则需要在业务代码入口entry加入:
https://webpack.docschina.org/guides/hot-module-replacement/
// entry:index.js
if (module.hot) {
// util.js 更新代码后页面不会刷新,但是功能会生效
module.hot.accept('./util.js', function() {
console.log('Accepting the updated printMe module!');
printMe();
})
}
JS热更新可能会带来问题,所以建议默认。如果修改代码后一直刷新会影响开发效率的时候可以开启这个配置。
DLL动态链接库,在C时代不想暴露源码打包成dll给第三方使用。我们在打包的时候可以把一些一直不变的包达成dll,下次打包忽略这些文件,就可以节省打包时间。
如React、ReactDOM
虽然是个好东西,但是webpack足够强大,DllPlugin被废弃,例如create-react-app
、Vue-cli
https://github.com/vuejs/vue-cli/issues/1205
dll option will be removed. Webpack 4 should provide good enough perf and the cost of maintaining DLL mode inside Vue CLI is no longer justified.
跟后端联调的时候如果直接访问后端地址会出现跨域问题,与其前后端做各种配置不如直接使用代理。
// webpack.config.dev.js
{
devServer: {
progress:true,// 进度条
port: 10086, // 本地服务器端口号
hotOnly:true,// 页面构建失败不刷新页面
hot: true, // 热重载
open: true, // 自定打开浏览器
proxy:{
'/api':'http:localhost:8080'
}
}
}