webpack汇总

253 阅读9分钟

webpack读物

webpack.wuhaolin.cn/1%E5%85%A5%…

webpack打包原理

根据webpack-cli返回的complier对象构建整个流程,执行complier构建钩子:1,通过option参数确认入口2、构建依赖树并通过loader去处理对应的module3、将结果进行合并输出到对应的dist目录中。过程中需执行的plugin会根据传入的complier对象执行的钩子执行操作,并将结果返回。

webpack启动流程

juejin.cn/post/725032…

webpack热更新原理

juejin.cn/post/684490…

  1. 使用webpack-dev-server启动服务
  2. 已经启动了服务,并建立了socket连接,监听本地文件变化和浏览器进行双向通信
  3. devServer会在entry中添加两个文件,用于监听内容的变化及建立连接
  4. 浏览器接收到热更新的通知
  5. 比较hash值确认是否需要更新,需要就更新注入在bundle中的代码
  6. 如果开启了模块热更新将使用会删除旧的模块,添加新的module

webpack plugin

在开发 Plugin 时最常用的两个对象就是 Compiler 和 Compilation,它们是 Plugin 和 Webpack 之间的桥梁。 Compiler 和 Compilation 的含义如下:

  • Compiler 对象包含了 Webpack 环境所有的的配置信息,包含 options,loaders,plugins 这些信息,这个对象在 Webpack 启动时候被实例化,它是全局唯一的,可以简单地把它理解为 Webpack 实例;
  • Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。当 Webpack 以开发模式运行时,每当检测到一个文件变化,一次新的 Compilation 将被创建。Compilation 对象也提供了很多事件回调供插件做扩展。通过 Compilation 也能读取到 Compiler 对象。 Compiler 和 Compilation 的区别在于:Compiler 代表了整个 Webpack 从启动到关闭的生命周期,而 Compilation 只是代表了一次新的编译。

Webpack 插件的工作原理是基于 Webpack 的事件机制。Webpack 在构建过程中会触发一系列的事件,插件可以通过监听这些事件,并在特定的时机执行自定义的逻辑。 Webpack 插件通常是一个 JavaScript 类或函数,它需要实现一个 apply 方法。在 apply 方法中,插件可以通过 Webpack 提供的钩子函数来注册自己的逻辑。

    class MyPlugin {
        apply(compiler) {
          // 注册一个事件监听器,在构建完成后执行自定义逻辑
          compiler.hooks.done.tap('MyPlugin', (stats) => {
            console.log('Build is done!');
            console.log(stats);
          });
        }
      }
      
      module.exports = MyPlugin;

实现一个日期时间处理的webpack plugin

class TimeFormatPlugin {
    constructor(options) {
        this.options = options; // 将传入的配置选项保存到实例的 options 属性中
    }

    apply(compiler) {
        compiler.hooks.emit.tapAsync('TimeFormatPlugin', (compilation, callback) => {
            const { format } = this.options; // 从配置选项中获取时间格式化字符串

            const timestamp = new Date().toLocaleString(format); // 获取当前时间的格式化字符串

            compilation.assets['timestamp.txt'] = { // 将生成的文件添加到 compilation.assets 对象中
                source: () => timestamp, // 文件内容为当前时间的格式化字符串
                size: () => timestamp.length, // 文件大小为字符串的长度
            };

            callback(); // 告诉 Webpack 插件已经完成操作
        });
    }
}

Tapable

zhuanlan.zhihu.com/p/79221553 image.png

webpack loader

使用loader-utils将参数source转成浏览器识别的字符串

同步loader和异步loader

同步 loader 是指处理模块的过程是同步的,即 loader 会立即返回处理结果。同步 loader 的编写比较简单,只需要编写一个函数,接收源代码作为参数,返回转换后的代码即可。同步 loader 的执行顺序是按照配置中 loader 数组的顺序依次执行的。 异步 loader 是指处理模块的过程是异步的,即 loader 会返回一个 Promise 对象,异步地处理源代码,并在处理完成后通过 resolve 方法返回转换后的代码。异步 loader 的编写比较复杂,需要使用异步编程的技巧,例如 Promise、async/await 等等。异步 loader 的执行顺序是按照配置中 loader 数组的倒序依次执行的。babel-loader,ts-loader,eslint-loader都是异步loader

    module.exports = function(source) {
        // 将 CSS 文件内容转换为 JavaScript 可以理解的模块格式
        const cssModule = JSON.stringify(source);
      
        // 返回 JavaScript 代码,导出 CSS 模块
        return `module.exports = ${cssModule};`;
      };

Pitching Loader

Pitching Loader 是 webpack 中的一个特殊类型的 loader,它可以在其他 loader 之前执行,并且可以中断整个 loader 链的执行。类似style-loader就是一个pitching-loader

module: {
        rules: [
          {
            test: /\.js$/,
            use: [
              'pitching-loader',
              'normal-loader',
            ],
            enforce: 'pre',
          },
        ],
      },

常用的webpack loader和plugin

esbuild-loader

zhuanlan.zhihu.com/p/529586982

github.com/esbuild-kit…

babel-loader

Babel 是一个广泛使用的 JavaScript 编译器,它可以将较新版本的 JavaScript 代码转换为向后兼容的版本,以便在旧版浏览器或其他环境中运行。Babel 的实现原理是基于 AST(抽象语法树)的转换。Babel 的转换过程就是将源代码转换为 AST,然后对 AST 进行遍历和修改,最后将修改后的 AST 转换回代码。

esbuild-loader和babel-loader打包产物的差异

使用esbuild-loader打包出的内容不包含webpackJSONP加载文件的方法

webpackJSONP是什么

webpackJSONP 是 Webpack 在打包时生成的一个全局函数,用于实现模块的异步加载和代码分割。

在 Webpack 中,当你使用 import()require.ensure() 等语法来异步加载模块时,Webpack 会将这些模块打包成一个个独立的 chunk,并将它们存储在一个叫做 chunkFilename 的文件中。当页面需要加载这些异步模块时,Webpack 会通过 jsonp 的方式动态地将这些模块加载到页面中。

webpack presets

Presets 的作用是简化 webpack 的配置过程,减少配置选项的数量和复杂度。通过使用 Presets,可以快速启用和配置 webpack 的各种功能,例如代码压缩、文件处理、代码分离、热更新等等。 webpack 提供了一些内置的 Presets,例如 @babel/preset-env@babel/preset-react@babel/preset-typescript 等等。这些 Presets 包含了一系列的插件和配置选项,可以用于编译和打包不同类型的代码。 除了内置的 Presets,还可以使用第三方的 Presets,例如 webpack-mergewebpack-box 等等。这些 Presets 可以根据项目的需求进行定制和扩展,提供更加灵活和个性化的配置选项。

bundle.js app.js chunk.js

  1. bundle.jsbundle.js 是整个应用程序的主要 bundle,它包含了所有应用程序代码和依赖的第三方库代码。在 React 应用中,bundle.js 包含了 React 库、ReactDOM 库以及应用程序的所有组件和模块代码。
  2. app.jsapp.js 是应用程序的入口文件,它包含了应用程序的启动逻辑和路由配置等。在 React 应用中,app.js 通常包含了应用程序的根组件以及路由配置等。
  3. chunk.jschunk.js 是按需加载的代码块,它包含了应用程序中某些功能模块的代码。在 React 应用中,chunk.js 通常是通过 React Router 或其他路由库动态加载的组件和模块代码。

split chunk

splitChunks 是 Webpack 中用于配置代码分割的选项,它可以将应用程序的代码拆分成多个小的块,以便实现按需加载和提高应用程序性能。splitChunks 选项可以配置以下值:

  1. chunks:指定哪些块需要被分割。可选值有 allasyncinitial。默认值为 async
  2. minSize:指定块的最小大小,只有大于这个值的块才会被分割。默认值为 30000
  3. maxSize:指定块的最大大小,只有小于这个值的块才会被分割。默认值为 0,表示没有大小限制。
  4. minChunks:指定一个模块被引用的最小次数,只有被引用次数大于等于这个值的模块才会被分割。默认值为 1
  5. maxAsyncRequests:指定按需加载时并行请求的最大数量。默认值为 5
  6. maxInitialRequests:指定入口点并行请求的最大数量。默认值为 3
  7. automaticNameDelimiter:指定自动生成的名称中用于分隔符的字符串。默认值为 ~
  8. name:指定拆分出来的块的名称。默认情况下,Webpack 会根据拆分出来的块的内容和位置自动生成名称。
  9. cacheGroups:用于配置缓存组,可以将符合条件的模块打包到同一个块中。每个缓存组可以配置 testpriorityreuseExistingChunkenforcename 等选项。

常见的webpack优化策略

scope hositing

Scope Hoisting 的原理是通过静态分析模块之间的依赖关系,将模块之间的依赖关系图转换为一个简单的函数调用关系图。这样,Webpack 就可以将所有模块中的变量声明提升到模块作用域的顶部,从而避免了在每个模块中创建闭包的开销。

  1. 代码拆分(Code Splitting):通过拆分代码块,将应用程序的代码分割成更小的块,实现按需加载和减少初始加载时间。可以使用 Webpack 的 splitChunks 配置选项来进行代码拆分。
  2. 懒加载(Lazy Loading):将不常用的模块或页面延迟加载,只在需要时才加载。可以使用动态 import()require.ensure() 来实现懒加载。
  3. Tree Shaking:通过静态分析代码,删除未使用的代码,减小打包后的文件大小。可以使用 Webpack 的 mode 配置选项设置为 production,以及使用工具如 UglifyJSTerser 来进行代码压缩和消除未使用的代码。
  4. 模块热替换(Hot Module Replacement):在开发环境中,使用模块热替换功能,实现在运行时更新模块,而不需要刷新整个页面。可以使用 Webpack 的 HotModuleReplacementPlugin 插件来启用模块热替换。
  5. 缓存优化:使用文件名哈希或内容哈希作为文件名的一部分,实现文件内容变化时,文件名也会变化,从而利用浏览器缓存机制,减少文件的重复加载。可以使用 Webpack 的 [hash][contenthash] 来生成哈希文件名。
  6. 并行构建:使用多线程或并行构建工具,如 thread-loaderhappypack,将任务分发给多个子进程并行执行,加快构建速度。
  7. 外部引入:将一些不常变化的库或依赖通过外部引入的方式加载,如使用 externals 配置选项或 CDN 引入,减少打包体积。
  8. 优化图片:对图片进行压缩、转换为 Base64 编码或使用懒加载等方式,减小图片的体积和加载时间。
  9. 优化字体:对字体进行压缩、子集化或使用外部引入等方式,减小字体的体积和加载时间。
  10. 缩小搜索范围:通过配置 resolvealiasextensions,缩小模块搜索的范围,加快模块的查找速度。
externals: {
          // 将不需要打包的库或依赖通过外部引入的方式加载
          // 如 jQuery、React、Vue 等
          jquery: 'jQuery',
          react: 'React',
          'react-dom': 'ReactDOM',
          vue: 'Vue',
        },
      
        module: {
          rules: [
            // ...其他规则
      
            {
              test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
              use: [
                {
                  loader: 'url-loader',
                  options: {
                    limit: 8192,
                    name: 'img/[name].[hash:8].[ext]',
                    esModule: false,
                  },
                },
              ],
            },
          ],
        },
        plugins: [
          new HtmlWebpackPlugin({
            // 配置 HtmlWebpackPlugin 的选项
            // ...
            externals: {
              // 将不需要打包的库或依赖通过外部引入的方式加载
              // 如 jQuery、React、Vue 等
              jquery: 'jQuery',
              react: 'React',
              'react-dom': 'ReactDOM',
              vue: 'Vue',
            },
          }),
          new CleanWebpackPlugin(),
          new webpack.HotModuleReplacementPlugin(),
          //开启scope hositing
          new webpack.optimize.ModuleConcatenationPlugin(),
        ],
        resolve: {
          alias: {
            '@': path.resolve(__dirname, 'src'),
          },
          extensions: ['.js', '.json', '.vue'],
        },
        optimization: {
        minimizer: [
            //
            new CssMinimizerPlugin(),
            // 压缩线上js文件
            new ESBuildMinifyPlugin({
                target: 'es2015'
            })
        ],
        splitChunks: {
            chunks: 'async',
            minSize: 100 * 1024,
            minRemainingSize: 0,
            minChunks: 1,
            maxAsyncRequests: 8,
            maxInitialRequests: 8,
            enforceSizeThreshold: 50000,
            cacheGroups: {
                ...config.cacheGroups,
                // 
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    name(module, chunks, cacheGroupKey) {
                        if (config.splitNodeModules) {
                            // const packageName = module.identifier().match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
                            // return `${cacheGroupKey}-${packageName}`
                            const chunkFilename = chunks.map((item) => item.name).slice(0, 2).join('~')                
                            return `${cacheGroupKey}-${chunkFilename}`
                        }
                        return 'vendors'                     
                    },
                    priority: -10,
                    reuseExistingChunk: true,
                },
                common: {
                    name: 'common',
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true,
                }
            }
        }
    }
      };

升级webpack