webpack4.X 实战(四):企业SPA 24点总结(下)

13. 配置 打包输出路径

  • webpack 配置文件输出路径时,常用三种配置

    • 配置一:出口 entry.path,配置所有文件的输出路径

      // webpack 配置
      const path = require('path');
      const rootPath = path.resolve(__dirname, '');
      
      
      module.exports = {
          output: {
              path: path.resolve(rootPath, './dist')
          }
      }
      
      // 打包后的文件,所有资源 统一输出到 项目根目录下的 dist 文件夹下
      
    • 配置二:loaderoptions.outputPath,配置 当前 loader 匹配到文件 的输出路径

      这项配置的文件输出 以 entry.path 路径为标准

      // webpack 配置
      
      module.exports = {
          output: {
              path: path.resolve(rootPath, './dist')
          },
                 
          module: {
              rules: [{
                  test: /\.(png|jpg|gif|ico)$/,
                  use: [{
                      loader: 'url-loader',
                      options: {
                          limit: 8192,
                          name: '[name].[ext]',
                          outputPath: 'static/assets/'
                      }
                  }]
              }]
          }
      };
      
      // 打包后的文件,图片资源 统一输出到 项目根目录下的 `dist/static/assets` 文件夹下
      
    • 配置三:name 选项的值,可改变当前包的输出路径

      出口 entry.chunkFilename,改变所有 chunk 包的输出位置(runtimeChunk 除外)

      // webpack 配置
             
      module.exports = {
          output: {
              path: path.resolve(rootPath, './dist'),
              chunkFilename: 'static/js/[name].js'
          }
      };
         
      // 打包后的 chunk 包,统一输出到 dist/static/js 文件夹下
      

      optimization.splitChunksname 值,以 output.chunkFilename 路径为标准

      // webpack 配置
      
      
      module.exports = {
          output: {
              path: path.resolve(rootPath, './dist'),
              chunkFilename: 'static/js/[name].js'
          },
                 
          optimization: {
              splitChunks: {
                  cacheGroups: {
                      libs: {
                          test: /[\\/]node_modules[\\/]/,
                          priority: 20,
                          name: '../libs/index',
                          chunks: 'all'
                      }
                  }
              }
          }
      };
      
      // 打包后的 splitChunks 包,统一输出到 dist/static/js 文件夹下
      

      【特殊】optimization.runtimeChunkname 值,以 output.path 路径为标准

      // webpack 配置
                     
      module.exports = {
          output: {
              path: path.resolve(rootPath, './dist'),
              chunkFilename: 'static/js/[name].js'
          },
         
          optimization: {
              runtimeChunk: {
                  name: 'static/runtime/index'
              }
          }
      };
         
      // 打包后的 runtime 包,统一输出到 dist/static/runtime 文件夹下
      

14. 配置 静态资源发布路径(方便CDN请求资源)

  • 作用:配置 发布路径,让页面请求 CDN 上的静态资源,速度更快

  • 配置 公共发布路径: output.publicPath

    // webpack 配置
    
    module.exports = {
        output : {
            publicPath : 'http://aaa.com/'
        }
    };
    
  • 单独配置静态资源: loader options

    module.exports = {
        module: {
            rules: [{
                test: /\.(png|jpg|gif|ico)$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 8192,
                        name: '[name].[hash:8].[ext]',
                        outputPath: 'static/assets/',
                        publicPath: 'http://aaa.com/assets/'
                    }
                }]
            }]
        }
    };
    

15. 配置 打包文件缓存 hash

  • webpack 打包后的模块 默认命名规则:

    • webpack 默认为给各个模块分配一个 id,作为模块的名称

      默认 id 是根据模块引入的顺序,赋予一个整数(0、1、2、3……)

      默认 id 用来处理模块之间的依赖关系

    • 通过配置 不同的 hash,缓存文件

  • webpack 内置了多种可使用 hash,官网解释分别如下:

    • hash: the hash of the module identifier

    • chunkHash: the hash of the chunk content

    • contentHash: the hash of extracted content

  • 设置 哈希 的长度

    // 全局设置 hash 长度
    
    output.hashDigestLength
    
    // 局部设置 hash 长度
    
    [hash:16] 等方式
    
  • 配置 何时生成 hash

    // output.hashDigest
    
  • 如何选择正确的 hash

    • JS、分离后的CSS、资源文件(图片、字体图标)都使用 hash

      • 每次打包 JS、分离后的CSS 文件 哈希值 都一样

      • 项目代码没有变化,再次打包;所有文件哈希值不变

      • 只要JS、CSS 有一处变化,所有 JS、分离后的CSS 文件 哈希值 都变化

      • 资源文件(图片、字体图标)没更新,哈希值不变;使用 hash,是个不错的选择

    • JS、分离后的CSS、资源文件(图片、字体图标)都使用 chunkHash

      • 每次打包 同一个页面的 JS、分离后的CSS 哈希值一样;不同页面 JS、CSS文件 哈希值不一样

      • 项目代码没有变化,再次打包;所有文件哈希值不变

      • 项目代码有变化,只是有更改的JS、对应的CSS文件 哈希值变化;其他的文件 哈希值不变

      • 资源文件(图片、字体图标)不能使用 chunkHash,会报错

    • 最佳实践:

      • JS 文件:[name].[chunkHash:8].js

      • 分离后的CSS 文件:[name].[contentHash:8].css

        不会 随着JS的改变,而更改 hash

      • 资源文件(图片、字体图标):[name].[hash:8].[ext]

16. 打包前 自动清除文件 clean-webpack-plugin

  • 使用 clean-webpack-plugin 插件,在打包时先清空上一次打包的文件

    如果不清空,打包后的文件体积会越来越大

  • 安装

    npm i clean-webpack-plugin@1.0.0 -D
    
  • 配置

    详细配置见

    // webpack 中配置
    const CleanWebpackPlugin = require('clean-webpack-plugin');
    
    module.exports = {
        plugins: [
            new CleanWebpackPlugin(['dist'])    // 值为根目录下 清空的文件夹
        ]
    }
    

17. 根据查找规则 精简打包

  • webpack 提供内置插件:ContextReplacementPlugin

    官方配置

    // 如下:只打包 moment 的中文包
    
    ...
    
    plugins: [
      new webpack.ContextReplacementPlugin(
        /moment[/\\]locale$/,
        /zh-cn/,
      ),
    ]
    ...
    

18. 打包时 代码分割

  • 新旧版本 API更新

    • webpack3 使用 内置插件 optimize.CommonsChunkPlugin 来分割代码

    • webpack4 移除内置插件,新增 optimization.splitChunksoptimization.runtimeChunk 来分割代码

      Webpack 4 的 Code Splitting 最大的特点就是配置简单(0配置起步),和基于内置规则自动拆分

  • 为什么要分割代码?

    • 编译:减少编译的整体大小,以提高构建性能

    • 运行:减小文件体积,提高加载速度

      这是相对的,在代码分割减小文件体积的同时,也应该考虑 合理的HTTP请求次数

  • 通常分割哪些代码?

    • 提取公有代码

    • 提取常用库代码

    • 提取 webpack 的 runtime (运行时) 代码

  • webpack4默认的分割代码机制,满足如下条件的默认都会被分割

    • 新 bundle 被两个及以上模块引用,或者来自 node_modules

    • 新 bundle 大于 30kb (压缩之前)

    • 异步加载并发加载的 bundle 数不能大于 5 个

    • 初始加载的 bundle 数不能大于 3 个

  • 默认配置如下:

    详细配置 见官网

    // 默认配置如下:
    module.exports = {
        // ...
        optimization: {
            splitChunks: {
                chunks: 'async',    //默认只作用于异步模块,为`all`时对所有模块生效,`initial`对同步模块有效
                minSize: 30000,     //合并前模块文件的体积
                maxSize: 0,
                minChunks: 1,       //最少被引用次数
                maxAsyncRequests: 5,
                maxInitialRequests: 3,
                automaticNameDelimiter: '~',    //自动命名连接符
                name: true,
                cacheGroups: {
                    vendors: {
                        test: /[\\/]node_modules[\\/]/,
                        priority: -10           // 同时满足条件的,优先级更高的生效
                    },
                    default: {
                        minChunks: 2,
                        priority: -20,
                        reuseExistingChunk: true
                    }
                }
            }
        }
    };
    
  • 自定义代码分割 optimization.splitChunks.cacheGroups

    optimization.splitChunks.cacheGroups 下每一个 chunk 的配置 选项 同 optimization.splitChunks 的配置选项

    如果设置了将 覆盖 optimization.splitChunks 下的配置选项;如果没设置 就继承

    // 将代码库 单独打包
    
    module.exports = {
        optimization: {
            splitChunks: {
                cacheGroups: {
                    libs: {
                        test: /[\\/]node_modules[\\/]/,
                        priority: 20,
                        name: '../libs/index',
                        chunks: 'all'
                    }
                }
            }
        }
    };
    
  • 将 webpack runtime 代码单独打包

    // 将代码库 单独打包
    
    module.exports = {
        optimization: {
            runtimeChunk: {
                name: 'static/runtime/index'
            }
        }
    };
    
  • 特殊场景下,需要我们 手动代码分割,可参考如下案例

19. 使用 HappyPack 多线程打包(提升编译速度)

  • 默认情况下,webpack 单线程处理任务

    由于运行在 Node.js 之上的 Webpack 是单线程模型的

    所以Webpack 需要处理的事情需要一件一件的做,不能多件事一起做

  • HappyPack 可以让 webpack 同时处理多个任务

    它将任务分解给多个子进程去并发执行,子进程处理完成后再将结果发送给主进程中

    从而减少总的构建时间,提升构建效率

  • HappyPack 处理 JS,配置如下

    // 安装
    
    npm i happypack@5.0.1 -D
    
    // webpack 配置如下
    
    const os = require('os');
    const HappyPack = require('happypack');
    const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
    
    
    module.exports = {
        module: {
            rules: [{
                test: /\.js$/,
               
                // 把对.js 的文件处理交给id为 happyBabel 的 HappyPack 的实例执行
                loader: 'happypack/loader?id=happyBabel'
            }]
        },
        plugins: [
            new HappyPack({
                id: 'happyBabel',                   // 用id来标识 happypack处理那里类文件
    
                // 如何处理  用法和loader 的配置一样
                loaders: [{
                    loader: 'babel-loader?cacheDirectory=true'
                }],
                threadPool: happyThreadPool,        // 共享进程池
                verbose: true                       // 允许 HappyPack 输出日志
            })
        ]
    };
    
  • 说明 module.rules.loader

    • 在 Loader 配置中,所有文件的处理都交给了 happypack/loader 去处理

    • 使用紧跟其后的 ?id=happyBabel 去告诉 happypack/loader 去选择哪个 HappyPack 实例去处理文件

  • 说明 HappyPack 参数

    • id: String 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件

    • loaders: Array 用法和 webpack Loader 配置中一样

    • threads: Number 代表开启几个子进程去处理这一类型的文件,默认是3个,类型必须是整数

    • verbose: Boolean 是否允许 HappyPack 输出日志,默认是 true

    • threadPool: HappyThreadPool 代表共享进程池(即多个 HappyPack 实例使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多)

    • verboseWhenProfiling: Boolean 开启 webpack --profile ,仍然希望HappyPack产生输出

    • debug: Boolean 启用debug 用于故障排查;默认 false

  • 注意:

    • HappyPack 在处理 CSS / SCSS 时,需要单独创建一个 postcss.config.js 文件;否则会报错

    • HappyPack 对 url-loader 和 file-loader 的支持度有限

  • 存疑

    • 问题:项目中只配置了子线程编译 JS,编译速度却更慢了(项目没有分离CSS;项目代码不多,生产环境打包后体积 1.13M)

    • 原因猜测:HappyPack 多线程的原理是,先开启子线程处理,完成后再讲结果传递给主线程,这个时间 超过了 多线程打包节省的时间(由于需要编译的JS不多)

20. DLL动态链接库(提升编译速度)

  • 认识 DLL

    可以包含给其他模块调用的函数和数据

    用过 Windows 系统的人应该会经常看到以 .dll 为后缀的文件

  • web 项目构建接入动态链接库,需要完成以下事情:

    • 把网页依赖的基础模块抽离出来,打包到一个个单独的动态链接库中(一个动态链接库中可以包含多个模块)

    • 当需要导入的模块存在于某个动态链接库中时,这个模块不能被再次被打包,而是去动态链接库中获取

    • 页面依赖的所有动态链接库需要被加载

  • web 项目构建接入动态链接库,好处:提升构建速度(每次构建 不用再重复打包)

  • webpack 已经内置了对动态链接库的支持

    • DllPlugin 插件:打包出一个个单独的动态链接库文件

    • DllReferencePlugin 插件:在主要配置文件中去引入 DllPlugin 插件打包好的动态链接库文件

  • 实践 DLL

    // webpack/dll.config.js 配置文件
    
    const path = require("path");
    const webpack = require("webpack");
    const libs = require('../configs/dll.config');
    const CleanWebpackPlugin = require('clean-webpack-plugin');
    
    
    
    module.exports = {
        mode: 'production',
    
        entry: {
            // 把 vue 相关模块的放到一个单独的动态链接库
            vue: ['vue', 'vue-router'],
       
            // 把项目需要所有的 lib 放到一个单独的动态链接库
            lib: ['jquery', 'moment'],
        },
    
        output: {
            path: path.resolve(__dirname, './dll'),
            filename: "[name].js",
            library: "_dll_[name]"
        },
    
        plugins: [
            new webpack.DllPlugin({
                name: "_dll_[name]",
                path: path.join(__dirname, 'dll', 'manifest.json'),
            }),
    
            // 清空 dll 文件夹
            new CleanWebpackPlugin(['dll']),
        ]
    }
    
    
    
    配置 npm script
    
    {
        "scripts": {
            "dll": "webpack --config webpack/dll.config.js",
        }
    }
    
    安装 依赖包
    
    npm i clean-webpack-plugin@1.0.0 -D
    
    // webpack 配置文件
    
    const webpack = require('webpack');
    
    
    module.exports = {
       plugins: [
            // 启用 dll
            new webpack.DllReferencePlugin({
                context: __dirname,
                manifest: require('./dll/manifest.json')
            })
        ]
    };
    
    // 新dowload下来的项目,需要先打 DLL 包,再运行项目
    

21. 全局引入 第三方库

  • webpack 内置插件,支持全局引入第三方库

    // webapck.config.js 文件中配置如下:
    
    const webpack = require('webpack');
    
    plugins:[
        new webpack.ProvidePlugin({
            $:"jquery"
        })
    ],
    
  • 对比:

    • 全局引入:

      全局引入后,项目代码中直接使用即可

      如果没有地方用,第三方库将不会被打包

    • 局部引入:

      每个文件中需要的话,都单独引入

      只要引入了,即使没用,第三方库也会打包

22. 可视化 分析打包体积

  • 插件:webpack-bundle-analyzer

    • 效果

    • 生成的报告中有三种尺寸大小:

      • stat: 压缩等转换之前的输入文件大小(从webpack的stat对象里得到的

      • parsed: webpack 打包压缩后 输出的JS文件大小(不含资源文件、分离后的CSS等)

      • gzip: 经过 gzip 压缩后的大小

    • 使用

      // 安装
      
      npm i webpack-bundle-analyzer@3.0.3 -D
      
      // webpack 配置
      
      const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
      
      
      module.exports = {
          plugins: [
              new BundleAnalyzerPlugin()
          ]
      };
      
      // 打包完成后 自动在浏览器中显示分析结果
      
  • 插件:webpack-chart

  • 插件:webpack-analyse

23. 精简 终端输出

  • 部署打包 配置

    // webpack 配置文件
    
    module.exports = {
        // 精简 终端输出(打包部署)
        stats: {
            modules: false,
            children: false,
            chunks: false,
            chunkModules: false
        }
    };
    
    
  • 本地运行 配置 webpack-dev-server

    // webpack 配置文件
    
    module.exports = {
       devServer: {
            // 精简 终端输出(本地运行)
            stats: {
                modules: false,
                children: false,
                chunks: false,
                chunkModules: false
            }
        }
    };
    
  • 详细配置 见 webpack state

24. 显示打包进度

  • 如下插件,可显示打包进度,具体配置可查阅 GitHub

    • progress-bar-webpack-plugin

    • nyan-progress-webpack-plugin

    • progress-bar-webpack-plugin

    • progress-webpack-plugin