较容易理解的webpack性能优化!!!

优化环境配置

1-webpack性能优化

  • 开发环境性能优化
  • 生产环境性能优化

开发环境性能优化

  • 优化打包构建速度
    • HMR
  • 优化代码调试
    • source-map

生产环境性能优化

  • 优化打包构建速度
    • oneOf
    • babel缓存
    • 多进程打包
    • externals
    • dll
  • 优化代码运行的性能
    • 缓存(hash-chunkhash-contenthash)
    • tree shaking
    • code split
    • 懒加载/预加载
    • pwa

01-HMR 热模块替换

基于开发环境的devserver(开发环境没有hmr功能)

HMR: hot module replacement 热模块替换 / 模块热替换

作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块) 极大提升构建速度

  • 样式文件:可以使用HMR功能:因为style-loader内部实现了~

  • js文件:默认不能使用HMR功能–>需要修改js代码,添加支持HMR功能的代码。

    注意:HMR功能对js的处理,只能处理非入口js文件的其他文件。

  • html文件: 默认不能使用HMR功能.同时会导致问题:html文件不能热更新了~ (不用做HMR功能)

    • 解决:修改entry入口,将html文件引入
  entry: ['./src/js/index.js', './src/index.html'],
  ...  
  devServer: {
    static: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    // 开启HMR功能
    // 当修改了webpack配置,新配置要想生效,必须重新webpack服务
    hot: true
  }
  
  
在需要热模块替换的非入口js文件中协商如下代码,可实现该方法的热模块替换
if (module.hot) {
  // 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效
  module.hot.accept('./print.js', function() {
    // 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。
    // 会执行后面的回调函数
    print();
  });
}

复制代码

02-source-map

(开发环境)
source-map: 一种 提供源代码到构建后代码映射 技术 (如果构建后代码出错了,通过映射可以追踪源代码错误)

//在webpack.config.js中加
devtool: '...'

    <!--[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map-->

    <!--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加入-->

    <!--内联 和 外部的区别:1. 外部生成了文件,内联没有 2. 内联构建速度更快-->

    <!--开发环境:速度快,调试更友好-->
    <!--  速度快(eval>inline>cheap>...)-->
    <!--    eval-cheap-souce-map-->
    <!--    eval-source-map-->
    <!--  调试更友好  -->
    <!--    souce-map-->
    <!--    cheap-module-souce-map-->
    <!--    cheap-souce-map-->

    <!--  --> eval-source-map  / eval-cheap-module-souce-map-->

    <!--生产环境:源代码要不要隐藏? 调试要不要更友好-->
    <!--  内联会让代码体积变大,所以在生产环境不用内联-->
    <!--  nosources-source-map 全部隐藏-->
    <!--  hidden-source-map 只隐藏源代码,会提示构建后代码错误信息-->

    <!--  --> source-map / cheap-module-souce-map-->
复制代码

03-oneof

优化生产环境打包构建速度的

以下loader只会匹配一个

注意:不能有两个配置处理同一种类型文件

oneof使一个文件只会匹配一个loader,否则会进行多次匹配。不能有两个配置处理同一种类型文件。因为eslint-loader和babel-loader都匹配js文件,所以要把一个loader放在oneof外面。

 module: {
    rules: [
      {
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      {
        // 以下loader只会匹配一个
        // 注意:不能有两个配置处理同一种类型文件
        oneOf: [
          {
            test: /\.css$/,
            use: [...commonCssLoader]
          },
          {
            test: /\.less$/,
            use: [...commonCssLoader, 'less-loader']
          },
          /*
            正常来讲,一个文件只能被一个loader处理。
            当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
              先执行eslint 在执行babel
          */
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            options: {
              presets: [
                [
                  '@babel/preset-env',
                  {
                    useBuiltIns: 'usage',
                    corejs: {version: 3},
                    targets: {
                      chrome: '60',
                      firefox: '50'
                    }
                  }
                ]
              ]
            }
          },
          {
            exclude: /\.(js|css|less|html|jpg|png|gif)/,
            loader: 'file-loader',
            options: {
              outputPath: 'media'
            }
          }
        ]
      }
    ]
  },
复制代码

04-缓存

类似开发环境的hmr
缓存:

  • babel缓存cacheDirectory: true–> 让第二次打包构建速度更快
  • 文件资源缓存
    • hash: 每次wepack构建时会生成一个唯一的hash值。
      • 问题: 因为js和css同时使用一个hash值。
        如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)
    • chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
      • 问题: js和css的hash值还是一样的
        因为css是在js中被引入的,所以同属于一个chunk
    • contenthash:根据文件的内容生成hash值。不同文件hash值一定不一样 –> 让代码上线运行缓存更好使用。

//开启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
      }
},

//文件资源缓存
  output: {
    filename: 'js/built.[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
复制代码

05-tree-shaking

tree shaking:去除无用代码

  • 前提:1. 必须使用ES6模块化 2. 开启production环境
  • 作用: 减少代码体积
在package.json中配置 
  "sideEffects": false 所有代码都没有副作用(都可以进行tree shaking)
    问题:可能会把css / @babel/polyfill (副作用)文件干掉
  "sideEffects": ["*.css", "*.less"]
复制代码

06-代码分割

入口几个文件 出口就几个文件

 entry: {
    // 多入口:有一个入口,最终输出就有一个bundle
    index: './src/js/index.js',
    test: './src/js/test.js'
  },
 output: {
    // [name]:取文件名
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
复制代码

可以将node_modules中代码单独打包一个chunk最终输出

  /*
    1. 可以将node_modules中代码单独打包一个chunk最终输出
  */
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
复制代码

单入口

//在某一js文件中写如下配置实现输出多文件
/*
  通过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('文件加载失败~');
  });
    
复制代码

07-懒加载

懒加载~:当文件需要使用时才加载~
预加载 prefetch:会在使用之前,提前加载js文件
正常加载可以认为是并行加载(同一时间加载多个文件)
预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源(预加载兼容问题有点严重)

console.log('index.js文件被加载了~');

// import { mul } from './test';

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

复制代码

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

workbox —->workbox-wepack-plugin

webpack.config.js

plugins: [
    new WorkboxWebpackPlugin.GenerateSW({
      /*
        1. 帮助serviceworker快速启动
        2. 删除旧的 serviceworker

        生成一个 serviceworker 配置文件~
      */
      clientsClaim: true,
      skipWaiting: true
    })
  ],

复制代码

入口js文件配置


/*
  1. eslint不认识 window、navigator全局变量
    解决:需要修改package.json中eslintConfig配置
      "env": {
        "browser": true // 支持浏览器端全局变量
      }
   2. sw代码必须运行在服务器上
      --> nodejs
      -->
        npm i serve -g
        serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去
*/
// 注册serviceWorker
// 处理兼容性问题
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/service-worker.js')
      .then(() => {
        console.log('sw注册成功了~');
      })
      .catch(() => {
        console.log('sw注册失败了~');
      });
  });
}
复制代码

09-多进程打包

下载thread-loader,对谁进行多进程打包就把thread-loader配置在它的后面

开启多进程打包。 (有利有弊)
进程启动大概为600ms,进程通信也有开销。
只有工作消耗时间比较长,才需要多进程打包

  {
    test: /\.js$/,
    exclude: /node_modules/,   
    use: [
      /* 
        开启多进程打包。 
        进程启动大概为600ms,进程通信也有开销。
        只有工作消耗时间比较长,才需要多进程打包
      */
      {
        loader: 'thread-loader',
        options: {
          workers: 2 // 进程2个
        }
      },
      {
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                useBuiltIns: 'usage',
                corejs: { version: 3 },
                targets: {
                  chrome: '60',
                  firefox: '50'
                }
              }
            ]
          ],
          // 开启babel缓存
          // 第二次构建时,会读取之前的缓存
          cacheDirectory: true
        }
      }
    ]
  },
复制代码

10-externals

有些包需要用cdn引入进来,防止将这些包打包进来,使用externals忽略这个包名。

  mode: 'production',
  externals: {
    // 拒绝jQuery被打包进来
    jquery: 'jQuery'
  }
复制代码
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
复制代码

11-dll

对代码单独打包

新建webpack.dll.js

/*
  使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包
    当你运行 webpack 时,默认查找 webpack.config.js 配置文件
    需求:需要运行 webpack.dll.js 文件
      --> webpack --config webpack.dll.js
*/

const { resolve } = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    // 最终打包生成的[name] --> jquery
    // ['jquery'] --> 要打包的库是jquery
    jquery: ['jquery'],
  },
  output: {
    filename: '[name].js',
    path: resolve(__dirname, 'dll'),
    library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容叫什么名字
  },
  plugins: [
    // 打包生成一个 manifest.json --> 提供和jquery映射
    new webpack.DllPlugin({
      name: '[name]_[hash]', // 映射库的暴露的内容名称
      path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
    })
  ],
  mode: 'production'
};

复制代码

webpack.config.js

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
    new webpack.DllReferencePlugin({
      manifest: resolve(__dirname, 'dll/manifest.json')
    }),
    // 将某个文件打包输出去,并在html中自动引入该资源
    new AddAssetHtmlWebpackPlugin({
      filepath: resolve(__dirname, 'dll/jquery.js')
    })
  ],
  mode: 'production'
};

复制代码

仅作为笔记参考 如有问题请留言!!!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享