性能优化

性能优化包含开发环境优化和生产环境优化。

  1. 开发环境
    • 优化打包构建速度
    • 优化代码调试
  2. 生产环境
    • 优化打包构建速度
    • 优化代码运行的性能

开发环境

HMR

即热更新:一个模块发生变化,只会重新打包这一个模块。

devServer中开启

1
2
3
4
5
6
7
8
9
10
11
{
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
// 开启HMR功能
// 当修改了webpack配置,新配置要想生效,必须重新webpack服务
hot: true
}
}
  • 样式文件

    样式文件可以使用热更新,因为style-loader内部实现了

  • js文件

    默认不使用HMR功能,需要手动修改支持HMR。且只能处理非入口文件。

    1
    2
    3
    4
    5
    6
    7
    8
    if (module.hot) {
    // 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效
    module.hot.accept('./print.js', function() {
    // 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。
    // 会执行后面的回调函数
    print();
    });
    }
  • HTML文件

    默认不支持,需要修改webpack中entry入口

    1
    2
    3
    module.exports = {
    entry: ['./src/js/index.js', './src/index.html']
    }

source-map

source-map是一种提供源代码到构建后代码映射技术。开启source-map只需要在devServer中开启devtool即可。

1
2
3
4
5
6
7
8
9
  devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
hot: true
},
devtool: 'source-map'
};

关于devtool的选项

选项位置特点错误原因及位置
source-map外部错误代码准确信息
源代码的错误位置
inline-source-map内联只生成一个内联source-map错误代码准确信息
源代码的错误位置
hidden-source-map外部错误代码错误原因,但是没有错误位置不能追踪源代码错误,只能提示到构建后代码的错误位置
eval-source-map内联每一个文件都生成对应的source-map,都在eval错误代码准确信息
源代码的错误位置
nosources-source-map外部错误代码准确信息, 但是没有任何源代码信息
cheap-source-map外部只能精确的行错误代码准确信息
源代码的错误位置
cheap-module-source-map外部module会将loader的source map加入错误代码准确信息
源代码的错误位置

内联和外部的区别:外部生成了文件,内联没有;内联速度比外部快

开发环境与生产环境下source-map的选择:

  • 开发环境:速度快,调试更友好

    • 速度快
      • eval-cheap-souce-map
      • eval-source-map
    • 调试友好
      • souce-map
      • cheap-module-souce-map
      • cheap-souce-map

    因此开发环境下可以选择:eval-source-map或者eval-cheap-module-souce-map

  • 生产环境

    • 不隐藏源代码(便于调试)

      • source-map
      • cheap-module-souce-map
    • 因此源代码

      • nosources-source-map

        全部隐藏

      • hidden-source-map

        只隐藏源代码,会提示构建后代码错误信息

oneOf

1
2
3
4
5
6
7
8
9
10
11
12
module: {
rules: [
{
// 其他loader
},
{
oneOf:[
// loader配置
]
}
]
}

只需要将loader放入到oneOf数组中即可,使用oneOf后不能有两个loader处理同一类型的文件,因此需要将相同的loader提取到外边

缓存

  1. babel缓存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    {
    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'babel-loader',
    options: {
    presets: [
    [
    '@babel/preset-env',
    {
    useBuiltIns: 'usage',
    corejs: { version: 3 },
    targets: {
    chrome: '60',
    firefox: '50'
    }
    }
    ]
    ],
    // 开启babel缓存
    // 第二次构建时,会读取之前的缓存
    cacheDirectory: true
    }
    }
  2. 文件资源缓存

    • hash

      和整个项⽬目的构建相关,只要项⽬目⽂文件有修改,整个项⽬目构建的 hash 值就会更更改

    • chunkhash

      和 webpack 打包的 chunk 有关,不不同的 entry 会⽣生成不不同的 chunkhash 值

    • contenthash

      根据⽂文件内容来定义 hash ,⽂文件内容不不变,则 contenthash 不不变

一般情况下js文件使用chunkhash,css文件和图片使用contenthash,图片文件使用hash

生产环境

去除无用代码

可以按需加载需要使用的代码,减少代码体积。

  1. 使用ES6模块化
  2. 开启production环境

此方法可能会将css资源忽略掉,因此可以在package.json中进行配置。

1
2
3
4
"sideEffects": [
"*.css",
"*.less"
]

代码分割

即按需加载,生成多个js文件。

  1. 根据入口文件进行分割

    1
    2
    3
    4
    5
    6
    7
    8
    9
    module.exports = {
    // 单入口
    // entry: './src/js/index.js',
    entry: {
    // 多入口:有一个入口,最终输出就有一个bundle
    index: './src/js/index.js',
    test: './src/js/test.js'
    }
    }
  2. 分割node_moudle模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    module.exports = {
    /*
    1. 可以将node_modules中代码单独打包一个chunk最终输出
    2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
    */
    optimization: {
    splitChunks: {
    chunks: 'all'
    }
    }
    }
  3. 通过JS代码使某个文件被单独打包

    此时webpack的入口可以设置单入口。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /*
    通过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('文件加载失败~');
    });

懒加载和预加载

只需要在触发某个条件时才引入外部文件。

1
2
3
4
5
6
7
8
9
document.getElementById('btn').onclick = function() {
// 懒加载~:当文件需要使用时才加载~
// 预加载 prefetch:会在使用之前,提前加载js文件
// 正常加载可以认为是并行加载(同一时间加载多个文件)
// 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
};

PWA

渐进式网络开发应用程序(离线可访问)

在webpack中使用pwa需要安装插件workbox-webpack-plugin

1
yarn add workbox-webpack-plugin -D

修改webpack配置

1
2
3
4
5
6
7
8
9
10
11
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
/*
1. 帮助serviceworker快速启动
2. 删除旧的 serviceworker
生成一个 serviceworker 配置文件~
*/
clientsClaim: true,
skipWaiting: true
})
],

在入口文件配置serviceworker

1
2
3
4
5
6
7
8
9
10
11
12
13
// code
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js')
.then(() => {
console.log('sw注册成功了~');
})
.catch(() => {
console.log('sw注册失败了~');
});
});
}

如果发生eslint错误,则可以在package.json中进行修改。

1
2
3
4
5
6
7
8
{
"eslintConfig": {
"extends": "airbnb-base",
"env": {
"browser": true
}
}
}

多进程打包

安装插件

1
yarn add thread-loader -D

一般多进程对babel-loader进行。只需要将thread-loader放在其他loader前即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'thread-loader',
options: {
// 进程为2
workers: 2
}
},
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
// 开启babel缓存
// 第二次构建时,会读取之前的缓存
cacheDirectory: true
}
}
]
}

多进程不一定会使打包时间变短,需合理使用。因为进程启动也需要消耗时间。

使用CDN

在webpack中配置字段externals即可。

1
2
3
4
externals: {
// 忽略的库名 -- npm 包名
jquery: 'JQuery'
}

忽略后需在html手动引入CDN链接。

dll

安装插件

1
yarn add add-asset-html-webpack-plugin -D

webpack.config.js同目录建立一个webpack-dll.js文件。写入如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @description:
* @author: 小康
* @url: https://xiaokang.me
* @Date: 2021-01-02 15:30:59
* @LastEditTime: 2021-01-02 15:31:00
* @LastEditors: 小康
*/
// 使用dll技术,对某些第三方库进行单独打包
const { resolve } = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
// 最终要打包生成的文件名:[要打包的库]
jquery: ['jquery']
},
output: {
filename: '[name].js',
path: resolve(__dirname, 'dll'),
// 打包的库向外暴露出去的内容的名字
library: '[name]_[hash]'
},
plugins: [
new webpack.DllPlugin({
// 打包生成manifest.json提供映射关系
name: '[name]_[hash]',
path: resolve(__dirname, 'dll/manifest.json')
})
],
mode: 'production'
}

webpack.config.js中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const webpack = require('webpack')
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
{
plugins: [
// 告诉webpack哪些库不参与打包,同时使用时引入的昵称也需要改
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 将某个文件打包输出 并在HTML中自动引入
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
]
}