wepack packaging optimization

Development environment performance optimization

  • Optimize package build speed
    • HMR
  • Optimize code debugging
    • source-map

Production environment performance optimization

  • Optimize package build speed
    • oneOf
    • babel-cache
    • Multi-process packaging
    • externals
    • dll
  • Optimize the performance of code execution
    • Cache (hash-chunkhash-contenthash)
    • tree shaking
    • code split
    • lazy loading/preloading
    • pwa
/**
 * 开发环境:
 * 1. 优化打包构建速度
 * 性能优化1:
 * HMR:hot module replacement 热模块替换 / 模块热替换 
 * 作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块),极大提升构建速度
 * 方式:在devServer配置hot: true
 * 样式文件:可以使用HMR功能,因为style-loader内部实现了
 * js文件:默认不能使用HMR功能
 * html文件:默认不能使用HMR功能,同时会导致问题:html文件不能热更新(不用做HMR功能,因为它改变了都要重新刷新页面)
 * 解决:修改entry入口,将html引入
 * 
 * 2. 优化代码调式
 * 性能优化2:
 * devtool --> eval-source-map  source-map
 * 
 * 生产环境:
 * 1. 优化打包构建速度
 * 性能优化1:rules中增加oneOf,以下模块只匹配一个,不用全部去匹配
 * 性能优化2:开启babel缓存 cacheDirectory: true (更新js时不用每个模块都是编译) --> 让第二次打包构建速度更快
 * 性能优化3:多进程打包
   性能优化4:externals忽略jquery打包,但是要使用CDN引入 ---> 不需要打包,CDN引入
   性能优化5:dll单独打包第三方库,  ---> 需要打包一次,运行webpack --config webpack.dll.js,后面不需要打包
 * 
 * 2. 优化代码运行性能:
 * 性能优化:1: 文件资源缓存 
*                hash:每次webpack构建时会生成唯一的hash值,
*                问题:因为js和css同时使用一个hash值,如果重新打包会使缓存失效(可能我就修改了一个文件)
*                解决:chunkhash: 根据chunk生成的hash值,如果打包来源于同一个chunk, 那么hash值就一样
          * 问题: js和css的hash值还是一样的
                    因为css是在js中被引入的,所以同属于一个chunk
                contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样    
                --> 让代码上线运行缓存更好使用
  性能优化2:tree shaking: 去除无用代码
            前提:1、必须使用ES6模块化 2、开启production环境
            作用: 减少代码体积
            在package.json中配置 
              "sideEffects": false 所有代码都没有副作用(都可以进行tree shaking)
                问题:可能会把css / @babel/polyfill (副作用)文件干掉
              "sideEffects": ["*.css", "*.less"]
  性能优化3:optimization,将node_modules中代码单独打包一个chunk最终输出
  性能优化4:文件懒加载,把文件放在异步回调函数中
  性能优化5:pwa离线文件加载 service-worker
 */

// resolve用来拼接绝对路径的方法
const {
    
     resolve } = require('path')

const HtmlWebpackPlugin = require('html-webpack-plugin')

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

const WorkboxWebpackPlugin = require('workbox-webpack-plugin')

const webpack = require('webpack')

const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')

const {
    
     CleanWebpackPlugin } = require('clean-webpack-plugin')

// 设置 nodejs 环境变量 ,设置为开发环境。postcss-loader默认是生产环境
// process.env.NODE_ENV = 'development'

// css-loader代码复用
const commonCssLoader = [
  // 创建style标签,将js中的样式资源插入进去,添加到head中生效
  // 'style-loader',

  // MiniCssExtractPlugin.loader,
  // 配置MiniCssExtractPlugin的options
  {
    
    
    loader: MiniCssExtractPlugin.loader,
    options: {
    
     // 解决打包后img路径不对问题
      publicPath: '../'
    }

  },

  // 将css文件变成commonjs模块加载到js中,里面的内容是样式字符串
  'css-loader',
  /**
   * css兼容性处理:postcss --> postcss-loader postcss-preset-env
   * 帮postcss找到package.json中browerslist里面的配置,通过配置加载指定的css兼容性样式
   * 
   */

  // 使用loader的默认配置
  // 'postcss-loader',

  // 修改loader配置
  // 将css做兼容性处理
  {
    
    
    loader: 'postcss-loader',
    options: {
    
    
      ident: 'postcss',
      plugins: () => [
        // postcss的插件
        require('postcss-preset-env')()
      ]
    }
  }
]

module.exports = {
    
    
  // 入口起点
  // entry: ['./src/js/index.js', './src/index.html'],
  // 单入口,最终输出只有一个bundle
  entry: './src/js/index.js',
  // 多入口,会打包成两个js文件
  // entry: {
    
    
  //   index: './src/js/index.js',
  //   print: './src/js/print.js'
  // },

  // 输出
  output: {
    
    
    // [name]:输出文件名
    filename: 'js/[name].[contenthash:10].js',
    // 输出路径
    // __dirname node.js变量,代表当前文件的目录的绝对路径
    path: resolve(__dirname, 'build')
  },

  // loader的配置
  module: {
    
    
    rules: [
      /* 性能优化:提取到外层,解决oneOf问题
        语法检查: eslint-loader eslint 
        注意:只检查自己写的源代码,第三方的库是不用检查的 
        设置检查规则: 
        package.json 中 eslintConfig 中设置~ 
        "eslintConfig": { 
          "extends": "airbnb-base" 
        } 
        airbnb --> eslint-config-airbnb-base eslint-plugin-import eslint 
        */
        {
    
    
        test: /\.js$/,
        //  排除node_modules,不进行检查
        exclude: /node_modules/,
        //  优先执行(当一个文件被多个loader执行,那么一定要指定loader执行顺序,先执行eslint-loader,再执行babel-loader)
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
    
    
          // 自动修复 eslint 的错误 
          fix: true
        }
      },
      {
    
    
        // 以下loader只匹配一个
        // 注意:不能有两个配置处理同一个文件,否则第一个匹配上了,就不会向下匹配,第二个会失效,
        // 解决办法:提取到外层
        oneOf: [
          {
    
    
            // 匹配哪些文件
            test: /\.css$/,
            // 使用哪里loader进行处理
            // use数组中loader执行顺序: 从右到做,从下到上依次执行
            use: [...commonCssLoader]
          },
          {
    
    
            // 匹配哪些文件
            test: /\.less$/,
            // 使用哪里loader进行处理
            // use数组中loader执行顺序: 从右到做,从下到上依次执行
            // 使用多个loader就使用user,使用一个则使用loader
            use: [...commonCssLoader, 'less-loader']
          },
          {
    
    
            // 处理图片资源
            test: /\.(jpg|png|gif)$/,
            // 下载url-loader file-loader
            loader: 'url-loader',
            options: {
    
    
              // 图片大小小于8kb,就会被base64处理
              // 优点:减少请求数量(减轻服务器压力)
              // 缺点:图片体积会更大(文件请求速度更慢)
              limit: 8 * 1024,
              // 问题:因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
              // 解析出问题:[object Module]
              // 解决:关闭url-loader的es6模块化,使用commonjs解析
              esModule: false,
              // 给图片进行重命名
              // [hash:10]取图片的hash的前10位
              // [ext] 取文件原扩展名
              name: '[hash:10].[ext]',
              outputPath: 'imgs'
            }
          },
          {
    
    
            test: /\.html$/,
            // 处理html中的img图片,负责引入img,从而被url-loader进行处理
            loader: 'html-loader'
          },
          {
    
    
            // 打包其他资源(除去html/js/css/less资源以外的资源)
            exclude: /\.(css|less|js|json|html|jpg|png|gif)$/,
            loader: 'file-loader',
            options: {
    
    
              name: '[hash:10].[ext]',
              outputPath: 'media'
            }
          },
          /**
           * js兼容性处理:
           * 1、基本的js兼容性处理 --> @babel/preset-env
           * 问题:只能转换基本的语法,如promise高级语法不能转换
           * 2、全部js兼容性处理 --> @babel/polyfill
           * 问题:我们只要解决部分兼容性问题,但是所有兼容性代码都引入,体积太大
           * 3、按需加载 --> core-js
           */
          {
    
    
            test: /\.js$/,
            //  排除node_modules,不进行检查
            exclude: /node_modules/,
            use: [
              /* 
                开启多进程打包。 
                进程启动大概为600ms,进程通信也有开销。
                只有工作消耗时间比较长,才需要多进程打包
              */
              {
    
    
                loader: 'thread-loader',
                options: {
    
    
                  workers: 2 // 进程2个
                }
              },
              {
    
    
                loader: 'babel-loader',
                options: {
    
    
                  // 预设:指示 babel 做怎么样的兼容性处理
                  presets: [
                    [
                      '@babel/preset-env',
                      {
    
    
                        // 按需加载
                        useBuiltIns: 'usage',
                        // 指定 core-js 版本
                        corejs: {
    
    
                          version: 3
                        },
                        // 指定兼容性做到哪个版本浏览器
                        targets: {
    
    
                          chrome: '60',
                          firefox: '60',
                          ie: '9',
                          safari: '10',
                          edge: '17'
                        }
                      }
                    ]
                  ],
                  // 开启babel缓存
                  // 第二次构建时,会读取缓存,不用没有js模块都去编译
                  cacheDirectory: true
                }
              }
            ]
          }
        ]
      }

    ]
  },
  // plugins的配置
  plugins: [
    // 默认创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS)
    // 需求:需要有结构的HTML
    new HtmlWebpackPlugin({
    
    
      // 复制 ./src/index.html 文件,并自动引入打包输出所有资源
      template: './src/index.html',
      // 压缩 html 代码
      minify: {
    
    
        // 移除空格
        collapseWhitespace: true,
        // 移除注释
        removeComments: true
      }
    }),
    // 从js中分离css
    new MiniCssExtractPlugin({
    
    
      // 给css重命名
      filename: 'css/built.[contenthash:10].css'
    }),
    // 压缩css
    new OptimizeCssAssetsWebpackPlugin(),
    new WorkboxWebpackPlugin.GenerateSW({
    
    
      /*
        1. 帮助serviceworker快速启动
        2. 删除旧的 serviceworker

        生成一个 serviceworker 配置文件~
      */
      clientsClaim: true,
      skipWaiting: true
    }),
    // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
    new webpack.DllReferencePlugin({
    
    
      manifest: resolve(__dirname, 'dll/manifest.json')
    }),
    // 将某个文件打包输出去,并在html中自动引入该资源
    new AddAssetHtmlWebpackPlugin({
    
    
      filepath: resolve(__dirname, 'dll/jquery.js')
    }),
    // 打包后先清除build中文件
    new CleanWebpackPlugin()
  ],

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

  // 使用CDN引入,拒绝打包
  externals: {
    
    
    // 拒绝jQuery被打包进来
    jquery: 'jQuery'
  },

  // mode的配置
  mode: 'development', // 开发模式
  // mode: 'production' // 生产模式,会自动压缩js代码

  // 开发服务器devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器)
  // 特点:只会在内存中编译打包,不会有任何输出
  // 启动devServer指令为:npx webpack-dev-server
  devServer: {
    
    
    // 项目构建后路径
    contentBase: resolve(__dirname, 'build'),
    // 启动gzip压缩
    compress: true,
    // 端口号
    port: 3000,
    // 自动打开浏览器
    open: true,
    // 开启HMR功能
    // 当修改了webpack配置,新配置想生效,必须重启webpack
    hot: true
  },
  devtool: 'eval-source-map'
}

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

    [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
*/

Guess you like

Origin blog.csdn.net/weixin_44999830/article/details/106416924