webpack实战

webpack4与webpack5的基础与自定义

Posted by QY on March 11, 2020

前言

随着webpack第五版beta的释放, 也是时候重新学习与总结一下webpack它的详细配置然后再自己动手做一做了, 独乐乐不如众乐乐, 学习的过程也将分享出来

webpack简介

回顾我们了解的一些预处理器, 针对CSS的LESS, SCSS, Stylus等, 可以针对JS的babel, uglify等, 如果靠我们手动一步步对代码进行预处理再上线会浪费许多开发时间, 因此自动化构建是非常有必要的

webpack作为一款自动化打包工具, 它的流程无外乎指定一系列对应文件然后对其进行预处理然后统一输出到指定的文件中, 但是它的精巧之处在于它的模块化特性, 会自动按照文件的引入顺序一步步引入各种各样的文件模块而不必像grunt, gulp那样指定对应的文件且相对能省略许多可选项的配置, 现在的各种类型的公司都已经转而使用webpack进行前端的构建

webpack的五个核心概念

配置webpack时主要围绕这五个核心进行配置, 我已经在前端自动化构建中大致介绍了webpack, 需要的朋友可以去回顾一下

  • Entry

    • 指示webpack从哪个文件作为入口起点开始打包, 开始分析构建依赖关系图
  • Output

    • 指示webpack将打包后的各种bundle文件以哪个文件保存到哪个位置
  • Loader

    • 协助webpack处理它自身无法”理解”的文件, 因为webpack原生只支持Javascript与JSON
  • Plugins

    • 插件能够帮助webpack处理更加广泛的任务, 插件可以应用的范围包括了从打包优化, 压缩到重新定义环境变量等等
  • Mode

    • webpack使用相应模式来决定使用哪一些预设插件, 分成development和production两种模式, 生产模式会使用代码压缩等插件让代码更方便生产环境使用

webpack初体验

  1. webpack是基于nodeJS的自动化构建工具, 在使用webpack之前必须安装nodeJS才能正常运行

  2. 使用nodeJS自带的包管理器npm全局下载安装webpack及其命令行工具

     npm install webpack webpack-cli -g
    
  3. 创建项目文件并且编写测试用的js文件并将命令行的路径切换到项目文件中, 项目目录如下

     webpack
         |
         |---src
              |--index.js
         |---build
         |---webpack.config.js
    
  4. 初始化项目并启动webpack脚本执行项目构建

     npm init
     webpack ./src/index.js -o ./build/bundle.js --mode=development
    
  5. 执行webpack自动生成的js文件

     node ./build/bundle.js
    

构建开发环境

在进行开发过程中通常只需要一些基本的插件与loader, 当配置好以下内容后已经可以进行一些基本的开发工作

  • 准备工作

    1. 在项目中安装各种需要用到的loader与plugin

       npm i webpack webpack-cli html-webpack-plugin style-loader css-loader less=loader html-loader url-loader file-loader -D
      
    2. 组织合适的项目目录并编写示例文件

      参考我的仓库

  • 配置文件

      //引入用于链接路径的api
      const {resolve} = require('path')
      //引入可将源代码中的html文件打包进构建好的文件夹中的插件
      const htmlWebpackPlugin = require('html-webpack-plugin')
    
      module.exports = {
          //指定入口文件, 可有多个, 参考文档
          entry: './src/index.js',
          //指定打包后存放的文件名与路径
          output: {
              filename: 'bundle.js',
              path: resolve(__dirname, 'build')
          },
          //指定与loader有关的一些配置
          module: {
              //此参数下指定匹配规则及其对应的loader
              rules: [{
                  /* css: 执行css-loader解释引入css文件, 执行style-loader将引入的css文件插入到DOM的style标签中
                    
                     less: 执行less-loader将less文件转换成css文件并执行与css相同的后续操作
    
                     图片: 将大小小于8kb的图片以base64的格式插入js文件中, 大小超出的自动调用file-loader将其打包到出口路径
    
                     html: 将html文件中引入资源的路径与文件名转化成打包后的路径与文件名
                   */
    
                  //使用正则匹配对应的文件名
                  test: /\.css$/,
                  //指定使用哪些loader来处理匹配到的文件, 需要注意的是loader的加载执行顺序为从右到左
                  use: ['style-loader', 'css-loader']
              }, {
                  test: /\.less$/,
                  use: ['style-loader', 'css-loader', 'less-loader']
              }, {
                  test: /\.(png|jpg|jpeg)$/,
                  loader: 'url-loader',
                  options: {
                      limit: 8 * 1024,
                      name: '[hash:10].[ext]'
                  }
              },{
                  test: /\.html$/,
                  loader: 'html-loader'
              },{
                  //获取排除匹配上的文件后的所有文件
                  exclude: /\.(js|html|jpg|png|jpeg|css|less)$/,
                  //指定对应的loader, 在本示例中是为了引入字体文件
                  loader: 'url-loader',
                  options: {
                      limit: 8*1024,
                      name: '[hash:10].[ext]'
                  }
              }]
          },
          //使用插件时必须调用插件的实例并将参数作为构造函数的参数引入
          plugins: [new htmlWebpackPlugin({
              template: resolve(__dirname, 'index.html')
          })],
          //指定webpack的执行模式
          mode: 'development'
      }
    
  • 执行构建

      webpack
    

使用dev-server

通过体验了开发环境中的编写代码然后打包的流程我们基本了解了webpack的工作流程, 但是在日常开发中每次修改代码就要重新打包明显会降低我们的开发效率, 因此需要引入一个热重载的功能, 创建一个开发服务器来实现这个功能

  • 安装依赖

      npm i webpack-dev-server -D
    
  • 编写热重载

      const {resolve} = require('path')
      const htmlWebpackPlugin = require('html-webpack-plugin')
    
      module.exports = {
          entry: './src/js/index.js',
          output: {
              filename: 'bundle.js',
              path: resolve(__dirname, 'js/build')
          },
          module: {
              rules: [{
                  test: /\.less$/,
                  use: ['style-loader', 'css-loader', 'less-loader']
              },{
                  test: /\.html$/,
                  loader: 'html-loader'
              }]
          },
          plugins: [new htmlWebpackPlugin({
              template: 'src/index.html'
          })],
          mode: 'development',
          //配置开发服务器
          devServer: {
              //指定服务器的根目录, 默认为项目的根目录
              contentBase: 'src',
              //开启gzip压缩用于处理开发服务器与打包后代码的通信
              compress: true,
              //开启执行编译后自动打开默认浏览器
              open: true
          }
      }
    
  • 使用热重载

      npx webpack-dev-server
    

构建生产环境

当我们需要将编写好的代码构建成生产环境需要的代码时通常需要配置一系列兼容, 压缩, 优化等预处理工作, 因此相较于开发环境需要使用到更多的插件与loader

  • 准备工作

    在提前下载需要的插件与loader前需要了解在生产环境中我们需要额外做些什么事情, 应该包括css的预处理(less, scss, stylus), 兼容性处理, 压缩, 从js中提取css文件, js的语法检查, js的兼容性检查与矫正, js的压缩, html的压缩等功能

    1. 下载相关依赖

       //基础loader与plugin
       npm i css-loader less-loader html-loader html-webpack-plugin url-loader file-loader -D
       //css兼容性处理
       npm i postcss-loader postcss-preset-env -D
       //css压缩
       npm i optimize-css-assets-webpack-plugin -D
       //从js中提取css文件
       npm i mini-extract-css-plugin -D
       //js语法检查
       npm i eslint eslint-loader -D
       //引入Airbnb的语法规则
       npm i eslint-config-airbnb-base eslint-plugin-import -D
       //js兼容性检查
       npm i babel-loader @babel/core @babel/preset-env core-js -D
       //js压缩只需要将模式转换成production即可
       //html压缩在html-webpack-plugin中增加配置即可
      
    2. 组织合适的项目目录并编写示例文件

      参考我的仓库

  • 生产环境配置

    配置webpack-config.js中的代码

        
      const {resolve} = require('path')
      const htmlWebpackPlugin = require('html-webpack-plugin')
      const postcssPresetEnv = require('postcss-preset-env')
      const miniCssExtractPlugin = require('mini-css-extract-plugin')
      const optimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
    
      const commonCssUse = [miniCssExtractPlugin.loader, 'css-loader', {
                      loader: 'postcss-loader',
                      options: {
                          //开启此插件让css的兼容性通过检查browserslist中的规则进行自动修正
                          plugins: () => [postcssPresetEnv]
                      }
                  }]
      //browserslist只会根据此变量来决定使用哪一套规则
      process.env.NODE_ENV = 'production'
    
      module.exports = {
          entry: './src/index.html',
          output: {
              filename: 'js/bundle.js',
              path: resolve(__dirname, 'build'
          },
          module: {
              rules: [{
                  test: /\.css$/,
                  use: commonCssUse
              }, {
                  test: /\.less$/,
                  use: [...commonCssUse, 'less-loader']
              }, {
                  test: /\.js$/,
                  exclude: /node_modules/
                  use: [{
                      loader: 'babel-loader',
                      options: {
                          //开启corejs来做到按需引入, 可选项中不指定targets时默认根据browserslist中的规则进行polyfill
                          preset: [
                              ['@label/preset-env', {
                              useBuiltIns: 'usage',
                              corejs: 3
                              }]
                          ]
                      }
                  },{
                      //开启自动修正语法错误, 当在package.json中配置了eslint-config时根据指定的规则进行语法判断, 需要注意的是语法检查应该在兼容性处理之前
                      loader: 'eslint-loader',
                      options: {
                          fix: true
                      }
                  }]
              }, {
                  test: /\.(png|jpg|gif)$/,
                  loader: 'url-loader',
                  options: {
                      limit: 8*1024,
                      outputPath: 'img'
    
                  }
              },
              {
                  exclude: /\.(css|less|html|js|png|jpg|gif)$/,
                  loader: 'file-loader',
                  options: {
                      outputPath: 'assets'
                  }
              }, {
                  test: /\.html$/,
                  loader: 'html-loader'
              }]
          },
          plugins: [
              //开启提取css文件的功能, 可选项可当做参数在此引入也可在module中作为options指定
              new miniCssExtractPlugin({
              filename: 'css/[name].[hash].css'
          }), new htmlWebpackPlugin({
              template: 'src/index.html',
              minify: true
              //开启css压缩的插件
          }), new optimizeCssAssetsWebpackPlugin()],
          mode: 'production'
      }
    

    在package.json中添加以下两条配置

      {
          "eslintConfig": {
              "extends": "airbnb-base"
          },
          "browserslist": {
              "development": [
                  "last 1 chrome version",
                  "last 1 firefox version",
                  "last 1 safari version",
              ],
              "production": [
                  "> 2%",
                  "not dead",
                  "ie >= 9",
                  "android >= 4.0"
              ]
          } 
      }
    
  • 执行构建

      webpack
    

性能优化

前两章已经介绍了在开发环境与生产环境中所做的基本配置, 而在实际的对应环节中, 我们还需要针对各种环境进行优化来尽可能的提升开发与运行效率, 因此针对不同环境还有许多优化方法可以使用

  • 开发环境优化

    • 构建优化

      1. HMR(hot module replacement)热模块替换

        当通过devserver监听代码修改时, 如果此时修改了代码会让整个包重新构建, 而包的体积足够大时频繁由于只修改了部分代码而造成整个包重新构建会耗费大量的重构时间, 降低开发效率

        使用HMR技术可只让被修改的文件重新构建, 提升开发效率

        引入此功能时先要开启devserver的HMR再考虑css, js, html三类文件

        由于style-loader内部集成了HMR功能, 只需要在devserver中将hot属性置为true即可

        对于js文件需要手动配置HMR代码使其生效

        html文件由于正常项目中只会有一个, 因此基本不需要启用此功能, 如果想要启用只需将其作为入口文件之一即可

    • 调试优化

      1. source map

        可以通过指定开发者需要的映射关系来控制浏览器中的调试信息输出, 包括输出错误信息的文件位置(构建后代码还是源代码还是不告知), 错误信息的具体位置(哪一行出错或者哪一条语句出错)等等

        在devtool中指定, 取值有[hidden-|nosources-|[inline-|eval-][cheap-[module-]]]source-map

        inline与eval表示映射文件为内联会造成js文件体积增大但是构建速度增快, eval速度会更快由于其使用了javascript的eval的机制

        hidden表示错误信息不会定位到源代码, 只定位到构建后的代码

        nosources表示错误信息不会定位到具体代码

        cheap表示错误信息会定位到通过loader转换过的代码且指示错误信息的行

        module必须跟cheap合用, 表示错误信息会定位到具体的语句

    • 优化配置

      webpack.config.js

        const {resolve} = require('path')
        const htmlWebpackPlugin = require('html-webpack-plugin')
        const webpack = require('webpack')
        module.exports = {
            entry: './src/js/index.js',
            output: {
                filename: 'js/bundle.js',
                path: resolve(__dirname, 'build')
            },
            module: {
                rules: [{
                    test: /\.css$/,
                    use: ['style-loader', 'css-loader']
                }, {
                    test: /\.less$/,
                    use: ['style-loader', 'css-loader', 'less-loader']
                }]
            },
            plugins: [new htmlWebpackPlugin({
                template: 'src/index.html'
                //开启HMR的api且启用其相关功能
            }), new webpack.HotModuleReplacementPlugin()],
            mode: 'development',
            //开启source map功能
            devtool: 'eval-source-map',
            devServer: {
                contentBase: './src',
                open: true,
                //允许开发服务器使用HMR功能
                hot: true
            }
                  
        }
      

      index.js

        import sum from './sum.js'
        console.log('加载index.js')
        //监听热替换模块, 当指定依赖发生修改只更新指定的文件且执行传入的回调函数
        if(module.hot) {
            module.hot.accept('./sum.js', () => console.log('HMR更新'))
        }
      

      sum.js

        const sum = (a, b) => {
            return a+b
        }
        export default sum
      
  • 生产环境优化

    • 构建优化

      1. tree shaking

        实际开发过程中我们很可能引入各种各样的冗余库导致实际上线的文件大小超出了必要的限度, 因此我们需要利用此机制来自动将没有使用的库或其中的一部分方法代码删去来压缩文件体积

        mode设置为production并且通过ES6语法加载其他文件即可启用

        package.json中配置sideEffects可以让数组中被匹配到的文件不会被删除, 如在js中引入的各种css文件等

      2. one of

        在构建过程中每当加载特定的依赖文件时都会与module.rules中的每一条规则去进行匹配, 当文件数量足够大时后续的无效匹配会占用大量构建时间, 因此需要开启此功能让某一匹配规则执行后, 后续不再进行匹配

      3. babel缓存

        babel对js文件的转换速度比较慢, 因此考虑将转换的结果作为缓存保存下来, 在第二次转换时能够显著提升转换效率, 在babel的options中开启cacheDirectory即可

      4. 多进程构建

        对于一些耗时比较长的loader, 单独给其开启一个进程, 将所有处理步骤放入worker pool中运行, 可以做到并行处理, 加快构建速度, 但是需要注意单独开启新进程需要花费600ms左右的开销且进程间通讯的数据会受到一定限制, 因此谨慎决定是否开启此功能

        在需要新进程处理的loader前添加thread-loader即可实现此功能

      5. externals

        当在生产环境中我们需要运用CDN将第三方库分布式引入时就不应该将这些库文件添加到构建依赖图中, 通过指定externals的依赖列表可以指定不将哪些文件生成构建

        此时第三方库需要手动或者通过插件引入到构建后的html或其他文件中

      6. dll

        类似于externals, 假如生产环境中需要的第三方库文件是部署在自己的服务器上时, 我们可以通过dll技术将这些库文件根据webpack.dll.js的配置进行单独打包并通过minifest文件指定不参与构建的库文件来达到减少打包构建时间的目的

    • 上线后优化

      1. 文件缓存

        将代码部署后通常服务器会设置强缓存, 此时如果代码出现错误需要紧急修改的情况下, 当文件名没有发生变更, 用户的强缓存策略仍然生效, 会导致强缓存到期为止用户加载的错误文件将一直无法修复

        为了解决这一问题我们需要引入构建后的文件名命名策略, 通过给文件名中添加hash值来达到一旦修改文件重新构建后由于文件名发生改变不再触发强缓存的目的

        filename的配置中有三种配置hash值分别为hash, chunkhash, contenthash

        hash取值为每一次webpack构建时自动得到的字符串

        chunkhash取值为每一次webpack构建时同一类chunk共用的一串字符串

        contenthash取值为由每一个文件的内容计算出来的hash值

      2. PWA(渐进式网络应用程序)

        为了让用户断网时访问网站仍然能够访问到部分内容而不是直接返回无网络连接从而提升用户访问体验, 近年来出现新的PWA技术, 它充分利用了缓存与worker server来做到此功能的实现

      3. js文件懒加载与预加载

        对某些刚进入页面暂时不需要加载的js文件通过懒加载可以使其在触发此条语句才进行加载

        预加载是在所有其他必须加载的文件都加载完成后空闲时间中进行加载, 但兼容性较弱, 需要谨慎使用

      4. 代码分割

        当项目比较大时, 打包后代码集中在单个js文件会影响页面正常显示造成长时间白屏, 因此需要在必要的时候将部分代码分割到其他的js文件中

        通过指定多个入口文件可以手动分割成指定数量的js文件, 配置optimization可以将大于30kb且位于node_modules中的库代码进行分割且不会造成代码重复加载的问题

    • 生产环境优化配置

      src中的代码路径请参考此处

      webpack.config.js

        const {resolve} = require('path')
        const htmlWebpackPLugin = require('html-webpack-plugin')
        const {DllReferencePlugin} = require('webpack')
        const addAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
        const workboxPlugin = require('workbox-webpack-plugin')
        module.exports = {
            entry: {
                //通过设置多入口文件将两个js源码分割打包成两个文件
                index: './src/js/index.js',
                register: './src/js/register.js'
            },
            output: {
                filename: 'js/[name].[contenthash].js',
                path: resolve(__dirname, 'build')
            },
            module: {
                rules: [{
                    oneOf: [{
                    test: /\.html$/,
                    loader: 'html-loader'
                }, {
                    test: /\.js$/,
                    //开启多线程执行js相关的转换
                    use: ['thread-loader', {
                        loader: 'babel-loader',
                        options: {
                            presets: [
                                ['@babel/preset-env', {
                                    useBuildIns: 'usage',
                                    corejs: 3
                                }]
                            ]
                        }
                    }, {
                        loader: 'eslint-loader',
                        options: {
                            fix: true
                        }
                    }]
                }]
                }]
            },
            plugins: [new htmlWebpackPLugin({
                template: './src/index.html'
                //指定需要读取的manifest文件的路径
            }), new DllReferencePlugin({
                manifest: resolve(___dirname, 'dll/manifest.json')
                //给html文件自动添加script标签, src路径为filepath指定的路径
            }), new addAssetsHtmlWebpackPlugin({
                filepath: resolve(__dirname, 'dll/*.dll.js')
            }),
            //通过workbox插件开启PWA使应用在离线时仍能访问
            //开启后会在项目根目录下创建server-worker.js文件, 通过实例中main.js文件的注册代码使其生效
            new workboxPlugin.generateSW({
                clientsClaim: true,
                skipWaiting: true
            })]
            //默认将位于node_modules中大于30kb的文件分割成单独的js文件, chunk名默认为vendors, 其他参数配置参考splitChunksPlugin
            optimization: {
                splitChunks: {
                    chunks: 'all'
                }
            },
            //指定全局变量中对应的库不在构建过程中被打包, 与dll之间需要二选一, 为了演示dll功能此处注释
            /* externals: {
                jquery: 'jQuery'    需要注意此处的value值必须为库向外暴露的变量名
            }, */
            mode: 'production'
        }
      

      webpack.dll.js

        const {resolve} = require('path')
        const {DllPlugin} = require('webpack')
        module.exports = {
            entry: {
                jquery: 'jquery'
            },
            output: {
                //设置dll文件输出路径与文件名
                filename: './[name].dll.js',
                path: resolve(__dirname, 'dll'),
                //指定此dll文件映射的字符串
                library: '[name]_[hash]'
            },
            plugins: [new dllPlugin({
                //指定manifest文件中保存哪些映射字符串对应的文件需要被忽略打包
                name: '[name]_[hash]',
                //指定manifest文件存放的路径
                path: resolve(__dirname, 'dll/manifest.json')
            })]
        }
      

      register.js

        //注册service worker
        if ('serviceWorker' in navigator) {
            window.addEventListener('load', () => {
                navigator.serviceWorker.register('/server-worker.js').then((ans) => console.log('PWA registed: ' + ans))
            }).catch((err) => console.log('registed fail: ' + err))
        }
      

      index.js

        //使用ES6语法可以自动启动tree shaking功能
        import $ from 'jQuery'
        //由于后续没有调用add的代码, 因此add.js将不会参与构建
        import add from 'add.js'
      
        console.log('print.js 被加载了')
        $(() = > {
            $('#btn').click(() => {
                //使用import函数可以触发懒加载, 只有第一次点击时才发起文件请求, 而且会触发代码分割, 将此文件以chunkname作为output.filename的[name]值
                import(/*webpackChunkName: print*/'./print.js').then((print) => {
                    print('点击')
                })
            })
        })
              
      

      add.js

        const add = (a, b) => a+b
      
        export default add
      

      print.js

        const print = (val) => console.log(val)
      
        export default print
      

      index.html

        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>webpack</title>
        </head>
        <body>
            <h1 id="title">hello, webpack</h1>
            <button id="btn">按钮</button>
        </body>
        </html>
      

webpack参数详解

除了以上章节展示的代码以外各个字段还有众多其他可选参数, 在最后我们统一进行总结

entry

  1. 字符串

    指定构建项目为单入口, 最后项目输出的js文件只有一个(不包括dll, 代码分割等其他方式引入的文件)

  2. 数组

    指定构建项目为多入口, 最后项目输出的js文件为一个, 输出文件中通过闭包引入执行多个入口文件

    通常用于在开发环境中使html的热更新生效

  3. 对象

    指定构建项目为多个入口, 有几个key则输出几个打包后文件, key值即为chunk名

    value值可以是数组, 指定多个入口文件打包成一个chunk文件, 例如几个库文件打包成一个库文件

  • 示例

      module.exports = {
          entry: 'src/js/index.js',//字符串
          /* entry: ['src/js/index.js', 'src/index.html'] 数组 */
          /* entry: {
              index: 'src/js/index.js',
              jquery: 'jQuery',
              react: ['react', 'reactDOM']
          } */
          output: {
              filename: 'js/[name].js',
              path: resolve(__dirname, 'build')
          },
          plugins: [new htmlWebpackPlugin()],
          mode: 'development'
      }
    

output

  1. filename

    指定输出的文件名与其路径

  2. path

    指定所有输出文件的公共路径

  3. publicPath

    指定打包后的文件内部链接的前缀

  4. chunkFilename

    指定输出文件默认名字, 即chunk名, 常用于对代码分割后的非入口输出文件的重命名

  5. library

    指定打包后的文件向外(全局)暴露其内部的变量名

  6. libraryTarget

    指定打包后的文件如何向外(全部)暴露其内部代码, 可选值有var, this, window, global, commonjs

  • 示例

      module.exports = {
          entry: {
              jquery: ['jQuery']  //当使用node_modules中的文件时大小写不敏感, 保持跟模块名一致
          },
          output: {
              filename: 'dll/[name].dll.js',  //指定打包输出的文件名, [name]即文件chunk名
              path: resolve(__dirname, 'build'),
              chunkFilename: 'jquery',    //指定打包输出的chunk名为jquery
              /*  Hash: 1f5e31c5620ea2141df4
                  Version: webpack 4.43.0
                  Time: 344ms
                  Built at: 2020-05-29 18:08:36
                            Asset      Size  Chunks             Chunk Names
                  ./jquery.dll.js  88.8 KiB       0  [emitted]  jquery
                  Entrypoint jquery = ./jquery.dll.js
                  [0] dll jquery 12 bytes {0} [built]
                      + 1 hidden module */
              library: '[name]_[hash]',   //指定向外暴露的变量名
              libraryTarget: 'window'     //将变量以window[[name]_[hash]]的形式添加到全局, 适用于浏览器
          },
          plugins: [new webpack.DllPlugin({
              name: '[name]_[hash]',  //将指定变量保存在manifest文件中, 方便引入
              path: resolve(__dirname, 'dll/manifest.json')
          })]
          mode: 'development'
      }
    

module

主要参数名基本集中在module.rules中, 为方便起见通过示例解释

module.exports = {
    entry: 'src/js/index.js',
    output: {
        filename: 'js/[name].js',
        path: resolve(__dirname, 'build')
    },
    module: {
        rules: [
            {
                test: /\.js$/,  //指定哪些文件参与此loader的构建
                exclude: /node_modules/, //指定哪些路径不参与此loader的构建
                include: resolve(__dirname, 'src/js'),   //指定哪些路径参与此loader的构建
                enforce: 'pre',     //指定当前配置的loader与其他loader匹配的先后顺序, 可使之与配置编写顺序无关
                loader: 'eslint-loader' //指定使用哪些loader
                /* use: [{      当有多个loader需要使用时通过use引入
                    loader: 'eslint-loader',
                    options: {
                        fix: true
                    }
                }] */
                options: {  //指定当前loader使用什么参数配置
                    fix: true
                }
            },
            {
                oneOf: []   //数组中的规则与上面的示例相同, 此数组中的所有loader会在第一次匹配后不再与后面的test继续匹配
            }
        ]
    }
}

resolve

  1. alias

    值为对象, 指定源文件中引入其他文件时的根路径别名, 可将过长路径简写以方便开发

  2. extensions

    值为数组, 指定在源文件引入其他文件时需要省略的后缀名, 从左到右匹配

  3. modules

    值为数组, 指定引入node_modules时查询的路径, 未查询到路径时按照npm规则递归向上查找

  • 示例

    webpack.config.js

      module.exports = {
          entry: 'src/js/index.js',
          output: {
              filename: 'js/[name].js',
              path: resolve(__dirname, 'build')
          },
          plugins: [new htmlWebpackPlugin()],
          resolve: {
              alias: {
                  $css: resolve(__dirname, 'src/css') //生成别名, 用key来替换value
              },
              extensions: ['js', 'css'],  //忽略扩展名, 先判断是不是.js再判断是不是.css, 通常用于框架的.jsx, .vue等后缀, 如果文件同名会造成意外效果
              modules: ['./node_modules', 'node_modules'] //指定node_modules的查找方式, 先根据数组中第一个值的路径去找,未找到则按照第二个值去找
          },
          mode: 'development'
      }
    

    index.js

      import '$css/index' //代替了__dirname + 'src/css/index.css'
    

devServer与optimization

开发服务器只应该应用于开发环境, 生产环境不要使用

optimization用于对代码的各种优化, 包括与缓存有关的文件名, 代码分割, 压缩插件等

  • 示例

      module.exports = {
          entry: 'src/js/index.js',
          output: {
              filename: 'js/[name].js',
              path: resolve(__dirname, 'build')
          },
          plugins: [new htmlWebpackPlugin()],
          mode: 'development',
          devServer: {
              contentBase: resolve(__dirname, 'build'),    //指定开发服务器的根目录
              watchContentBase: resolve(__dirname, 'build'), //指定监听的目录, 当目录中的文件发生修改则reload整个服务器
              watchOptions: { //配置监听的任选项
                  ignore: /node_modules/      //指定忽视监听某些文件, 当文件数量巨大时能有效加快速度
              },
              open: true,  //设置开启开发服务器时是否默认自动打开浏览器
              clientLogLevel: 'none', //设置显示在开发服务器控制台中的信息, 可选项有info, warning, error, none
              compress: true,  //是否开启开发服务器时各项服务的gzip压缩功能
              quiet: true,    //开启后除了初始信息以外的所有信息都不会显示在控制台中
              port: 5000,     //指定服务器端口号
              host: '127.0.0.1',  //指定服务器的主机地址
              hot: true,      //是否允许服务器支持HMR功能
              overLay: true,  //是否允许服务器将error或者warning显示在浏览器中
              proxy: {    //是否开启开发服务器代理, 开启后能通过服务器的转发来解决浏览器跨域问题
                  '/api': {   //当遇到访问了指定的路径前缀时开启转发
                      target: '127.0.0.1:4000',   //将收到的请求转发到target的服务器中
                      pathRewrite: {'^/api': ""}  //自动替换匹配到的key值为value再转发到target服务器中
                  }
              }
          },
          optimization: {
              splitChunks: {
                  //以下配置为默认配置, 无需手动设置
                  chunks: 'all',  //指定参与打包的库文件,默认为全部
                  minSize: 30*1024,  //指定参与代码分割的最小文件大小
                  maxSize: 0,  //指定将大于此大小的文件拆分成不小于minSize指定的文件块
                  minChunks: 1, //一个模块被引入的次数大于等于这个值才会参与代码分割
                  maxAsyncRequests: 3,  //按需加载时最大并行的请求数量
                  maxInitialRequests: 5,   //最大并行加载的入口文件请求数量
                  automaticNameDelimiter: '~',    //默认情况下分割后的文件以chunk~vendors.js来命名, 此值可以指定中间的连接符
                  name: 'true',   //设置为true会自动将块命名为块名+缓存密钥哈希值, 也可以传入函数与字符串来自定义块名, 建议在生产环境设置为false来放置不必要的命名变化
                  cacheGroups: {  //将块进行分组输出, 其中单独的配置会继承或者覆盖以上配置
                      vendors: {  //将符合其中配置要求的统一以vendor为chunk名输出成单一文件
                          test: /\/node_modules\//,
                          priority: -10   //分组规则可以同时匹配到相同的文件, 可以通过设置优先级让其只与优先级高的规则匹配到, 默认值为0
                      },
                      default: {
                          priority: -20,
                          minChunks: 2,
                          reuseExistingChunk: false   //当前chunk如果包含了那些从主bundle中已经分离出来的模块时, 会重新利用那些模块而不是再生成一次, 此配置可能会影响生成的chunk的文件名
                      }
                  }
              },
              runtimeChunks:{
                  name: entry => 'runtime-' + entry.name     /* 为每一个引入了分割后的chunk的入口文件生成一个额外的runtime文件
                                                              一旦chunk内容变更导致的文件名改变会只修改它本身与runtime文件名, 可以提升缓存利用率*/
              },
              minimizer: [compirer => {   //决定使用的压缩插件, 由于uglify不再更新因此新版本的webpack中默认使用terser进行压缩
                  const terser = require('terser-webpack-plugin')
                  new terser({
                      cache: true,    //决定是否缓存文件压缩结果
                      parallel: true, //是否多进程压缩文件
                      sourceMap: true //当配置文件中设置了sourcemap时必须设置此值为true
                  }).apply(compirer)
              }]
          }
      }
    

webpack5的介绍与使用

在webpack5中目前最大的变化有以下几点:

  1. 在多重引入模块时会让没有用到的中间文件不再参与打包过程, 极大减少了打包后存在的无用代码

  2. 拥有了更多默认配置, 几乎做到零配置上手即用

  3. 重新打包时使用缓存, 只会更新发生修改了的文件

  4. 原来需要在各个loader或者plugin中配置的如babel中的cacheDirectory等缓存相关内容可以统一配置了

  5. 优化, 新增了哈希算法, 提升了强缓存效率, 默认在生产环境中启动

  6. 为tree shaking增加了对commonjs的支持

  7. 自动删除了原先为了支持nodeJS模块的polyfill

  8. 打包后输出的代码可以选择为ES5, ES6代码了, 在output.ecmaVersion中设置

  9. 在代码分割中可以对不同的文件分别配置了

     {
         splitChunks: {
             chunks: {
                 /* webpack4
                 minSize: 30*1024 */
                 minSize: {
                     javascript: 30000,
                     style: 40000
                 }
             }
         }
     }
    
  • 基本使用

    1. 下载webpack

      目前版本未正式释放, 释放后包名会被修改

       npm i webpack@next webpack-cli -D   
      
    2. 编写配置demo

       module.exports = {
           mode: 'development'
       }
       /* 由于许多配置都已经默认设置了, 因此以上配置代码与下面的代码是相同的
       const {resolve} = require('path')
       module.exports = {
           entry: 'src/index.js',
           output: {
               filename: '[name].js',
               path: resolve(__dirname, 'dist')
           },
           mode: 'development'
       } */