性能优化

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

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

开发环境

HMR

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

devServer中开启

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

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

  • js文件

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

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

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

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

source-map

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

  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

module: {
  rules: [
    {
      // 其他loader
    },
    {
      oneOf:[
        // loader配置
      ]
    }
  ]
}

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

缓存

  1. babel缓存

    {
      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中进行配置。

"sideEffects": [
  "*.css",
  "*.less"
]

代码分割

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

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

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

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

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

    /*
      通过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('文件加载失败~');
      });

懒加载和预加载

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

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

yarn add workbox-webpack-plugin -D

修改webpack配置

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

在入口文件配置serviceworker

// 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中进行修改。

{
  "eslintConfig": {
    "extends": "airbnb-base",
    "env": {
      "browser": true
    }
  }
}

多进程打包

安装插件

yarn add thread-loader -D

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

{
  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即可。

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

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

dll

安装插件

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

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

/**
 * @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中使用

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')
    })
  ]
}