webpack之前端系列,知其然,知其所以然

255 阅读9分钟

核心概念

1. Webpack 是什么?它的核心概念有哪些?

  • Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具,本质上将 JS、CSS、图片等一切资源都视为模块,打包成浏览器可运行的静态文件。它解决了模块依赖管理、资产优化、代码分割和开发工作流效率等问题。
  • 核心概念:入口(entry)、输出(output)、loader、插件(plugins)、模式(mode)、模块(module)、依赖图(dependency graph)

解决的问题:

  • 支持模块化(ESM、CommonJS)
  • 自动打包、压缩、按需加载
  • 提高开发效率(HMR、SourceMap)
  • 实现构建优化(Tree Shaking、Code Splitting)

2. Webpack 的构建流程是怎样的?

  • 初始化参数:从配置文件和shell语句中读取参数
  • 开始编译:用上一步得到的参数初始化Compiler对象
  • 确定入口:根据配置中的entry找出所有入口文件
  • 编译模块:从入口文件出发,调用所有配置的loader对模块进行翻译,再找出该模块依赖的模块
  • 完成模块编译:得到每个模块被翻译后的最终内容及它们之间的依赖关系
  • 输出资源:根据入口和模块之间的依赖关系,组装成包含多个模块的Chunk
  • 输出完成:根据配置确定输出的路径和文件名,把文件内容写入文件系统

构建分为 初始化 → 编译 → 输出 三个阶段:

1. 初始化:读取配置,加载插件和 loader
2. 编译:从入口出发递归分析模块依赖,调用 loader和plugin 处理资源
3. 输出:生成最终的 bundle 文件并写入磁盘

Loader 相关

3. 什么是loader?常用的loader有哪些?

  • loader 用于对模块的源代码进行转换,可以将文件从不同的语言转换为JavaScript

  • 常用loader:

    • babel-loader:转换ES6+代码
    • style-loader:将CSS插入到DOM中
    • css-loader:处理CSS文件
    • file-loader/url-loader:处理图片等资源文件(Webpack 5 已内置部分 Loader 功能(如 asset modules 替代 file-loader))
    • ts-loader:处理TypeScript
    • sass-loader/less-loader:处理Sass/Less
    • eslint-loader:通过 ESLint 检查 JavaScript 代码
    • tslint-loader:通过 TSLint检查 TypeScript 代码

4. loader的执行顺序是怎样的?

  • 从右到左,从下到上执行
  • 例如:use: ['style-loader', 'css-loader'],先执行css-loader,再执行style-loader

module loader 可以链式调用。链中的每个 loader 都将对资源进行转换,不过链会逆序执行。第一个 loader 将其结果(被转换后的资源)传递给下一个 loader,依此类推。最后,webpack 期望链中的最后的 loader 返回 JavaScript。

Plugin 相关

5. 什么是plugin?常用的plugin有哪些?

  • plugin 用于扩展webpack功能,在webpack构建流程中注入钩子

  • 常用plugin:

    • HtmlWebpackPlugin:生成HTML文件
    • CleanWebpackPlugin:清理构建目录
    • MiniCssExtractPlugin:提取CSS到单独文件
    • DefinePlugin:定义环境变量
    • HotModuleReplacementPlugin:热模块替换
    • SplitChunksPlugin:代码分割
    • speed-measure-webpack-plugin: 可以看到每个 Loader 和 Plugin 执行耗时 (整个打包耗时、每个 Plugin 和 Loader 耗时)
    • webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)

6. HtmlWebpackPlugin

HtmlWebpackPlugin 是 webpack 的一个核心插件,用于简化 HTML 文件的创建和管理,尤其是在与 webpack 打包的 JavaScript/CSS 资源结合时。以下是它的关键作用和使用方法:

常用配置选项

new HtmlWebpackPlugin({
  title: 'My App',              // 生成的HTML文件的标题
  filename: 'admin.html',       // 输出的HTML文件名,默认是index.html
  template: 'src/index.html',   // 模板文件路径
  favicon: 'src/favicon.ico',   // favicon路径
  meta: {                       // 注入meta标签
    viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no',
    'theme-color': '#4285f4'
  },
  chunks: ['main', 'vendors'],  // 包含的chunk
  chunksSortMode: 'manual',     // chunk的排序方式
  inject: true,                 // 是否注入资源到模板中,true | 'head' | 'body' | false
  minify: {                     // 压缩HTML的配置
    collapseWhitespace: true,
    removeComments: true,
    removeRedundantAttributes: true,
    removeScriptTypeAttributes: true,
    removeStyleLinkTypeAttributes: true,
    useShortDoctype: true
  }
})

核心功能

  1. 自动生成 HTML 文件

    • 根据模板(或默认模板)生成一个 index.html 文件,并自动注入打包后的资源(JS、CSS)。
  2. 动态注入资源

    • 无需手动在 HTML 中写 <script> 或 <link> 标签,插件会根据 webpack 的打包结果自动添加哈希后的资源路径。
  3. 支持模板定制

    • 可以使用自定义的 HTML 模板(如 public/index.html),保留原有结构的同时动态注入资源。
  4. 多页面支持

    • 通过配置多个 HtmlWebpackPlugin 实例,生成多个 HTML 文件(适用于多页应用)。
module.exports = {
  entry: {
    page1: './src/page1.js',
    page2: './src/page2.js',
    page3: './src/page3.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/page1.html',
      filename: 'page1.html',
      chunks: ['page1']
    }),
    new HtmlWebpackPlugin({
      template: './src/page2.html',
      filename: 'page2.html',
      chunks: ['page2']
    }),
    new HtmlWebpackPlugin({
      template: './src/page3.html',
      filename: 'page3.html',
      chunks: ['page3']
    })
  ]
}

7. loader和plugin的区别是什么?

  • loader:用于转换特定类型的模块,是一个转换器
  • plugin:针对webpack整个构建过程,扩展功能,是一个扩展器

优化相关

8. 如何优化 Webpack 构建速度?

优化 Webpack 打包速度可以从多个方面入手,包括缓存、多进程构建、代码分割、DLL 预编译等。以下是几种主要的优化策略:


1. 使用缓存(Cache)

Webpack 5 内置了持久化缓存功能,可大幅提升二次构建速度: 开启后,Webpack 会将模块和 loader 的解析结果缓存到文件系统,下次构建时可以直接读取,显著加快二次构建速度。

module.exports = {
  cache: {
    type: 'filesystem', // 文件系统缓存
    cacheDirectory: path.resolve(__dirname, '.temp_cache'),
    buildDependencies: {
      config: [__filename], // 配置文件变化时缓存失效
    },
  },
};
  • 效果:首次构建后,后续构建可跳过未更改的模块,速度提升 80% 以。

  • 其他缓存方式

    • babel-loader 开启 cacheDirectory

      // ...
      {
        test: /\.js$/,
        loader: 'babel-loader',
        options: {
          cacheDirectory: true, // 开启缓存
        },
      }
      // ...
      
    • cache-loader(适用于开销大的 loader,如 babel-loader 和 ts-loader)。


2. 多进程构建

  • thread-loader(推荐):

    module: {
      rules: [
        {
          test: /.js$/,
          use: [
            'thread-loader', // 放在其他 loader 前面
            'babel-loader',
          ],
        },
      ],
    }
    
    • 适用场景:适用于 Babel、TypeScript 等耗时 loader。
    • 注意:小型项目可能反而变慢,建议仅在大中型项目使用。
  • TerserPlugin 多进程压缩

    optimization: {
      minimizer: [
        new TerserPlugin({ parallel: true }), // 默认开启多进程
      ],
    }
    
    • 替代方案ParallelUglifyPlugin(适用于 Webpack 4)。

3. 缩小构建范围

  • exclude / include
    精确匹配文件: 使用 includeexclude 字段,只对必要的文件应用 Loader,避免不必要的处理。

    module: {
      rules: [
        {
          test: /.js$/,
          exclude: /node_modules/, // 排除 node_modules
          include: path.resolve('src'), // 仅处理 src 目录
          use: ['babel-loader'],
        },
      ],
    }
    
    • 效果:减少不必要的文件解析。

4. 代码分割(Code Splitting)

  • SplitChunksPlugin(Webpack 4+):

    optimization: {
      splitChunks: {
        chunks: 'all', // 提取公共代码
        cacheGroups: {
          vendors: {
            test: /[\/]node_modules[\/]/,
            name: 'vendors',
            chunks: 'all',
          },
        },
      },
    }
    
    • 作用:避免重复打包第三方库,利用浏览器缓存。
  • 动态导入(import()

    const HomePage = lazy(() => import('./pages/Home')); // React 懒加载
    
    • 适用场景:按需加载路由组件,减少首屏加载时间。

5. DLL 预编译(DllPlugin)

  • 适用场景:稳定第三方库(如 React、Vue)单独打包,避免重复构建。

  • 步骤

    1. 创建 webpack.dll.config.js 打包 vendor 库:

      const vendors = ['react', 'react-dom'];
      module.exports = {
        entry: { vendor: vendors },
        plugins: [
          new webpack.DllPlugin({
            name: '[name]',
            path: 'manifest.json',
          }),
        ],
      };
      
    2. 主配置引用 DllReferencePlugin

      new webpack.DllReferencePlugin({
        manifest: require('./manifest.json'),
      });
      
    • 效果:减少 30%-50% 构建时间。

6. 配置别名alias使用快速的解析器

  • resolve.alias:通过设置别名,缩短模块查找路径,加快解析速度。

    module.exports = {
      // ...
      resolve: {
        alias: {
          '@': path.resolve(__dirname, 'src/'),
        },
      },
    };
    
  • resolve.modules: 明确告诉 Webpack 哪些目录存放模块,减少不必要的查找。

7. 分析打包报告

  • 使用 webpack-bundle-analyzer 等工具生成可视化报告。通过分析报告,你可以清楚地看到每个模块的体积、依赖关系,从而找出优化瓶颈,例如:是否存在重复引入、是否有不必要的模块被打包进来。

    npm install --save-dev webpack-bundle-analyzer
    
    const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
    
    module.exports = {
      plugins: [
        new BundleAnalyzerPlugin(),
      ],
    };
    

8. 选择合适的 devtool (Source Map 选项)

devtool 配置项用于控制 Source Map 的生成方式。不同的值会影响构建速度和调试体验。

  • eval: 最快的选项,将 Source Map 以 eval() 的形式内联到每个模块中。适合开发,但无法精确到列。
  • eval-source-map: 较快,生成完整的 Source Map,并以 eval() 形式内联。调试体验好,适合开发。
  • cheap-module-eval-source-map: 比 eval-source-map 略快,不包含列信息,但依然能映射到原始代码。
  • 避免在开发环境使用 source-maphidden-source-map:这些选项会生成独立的 Source Map 文件,构建速度较慢。

9. 合理使用 externals

如果项目中引入了大型的第三方库(如 React, Vue, jQuery 等),并且这些库通常通过 CDN 引入,可以使用 externals 将它们排除在 Webpack 的打包范围之外。这样可以减少 Webpack 的打包工作量,并利用 CDN 的缓存优势。

module.exports = {
  // ...
  externals: {
    react: 'React', // 表示项目中 require('react') 时,会从全局的 React 变量获取
    'react-dom': 'ReactDOM',
  },
};

总结


技术/手段原理
缓存(cache-loader、babel-loader 缓存)避免重复编译
多进程构建(thread-loader)提高 CPU 利用率
exclude/include减少 loader 处理范围
使用 resolve.alias减少路径解析开销
使用externals减少打包数量
DLLPlugin(已不推荐)分离第三方模块
Vite 替代(ESBuild 更快)新一代构建工具
HappyPack(已过时)多线程加载资源

9. 如何优化 Webpack 构建产物(体积)?

  • ✅ 开启 Tree Shaking(去掉无用代码)
  • ✅ 使用动态 import 拆包(按需加载)
  • ✅ MiniCssExtractPlugin + CssMinimizerPlugin 压缩 CSS
  • ✅ TerserPlugin 压缩 JS
  • ✅ 图片资源优化(image-webpack-loader)
  • ✅ 使用 CDN 加载大库(如 React)
  • ✅ 通过 externals 排除外部库

优化 Webpack 构建产物体积是前端性能优化的重要一环。更小的产物意味着更快的下载速度、更短的首屏时间以及更好的用户体验。以下是一些常用的优化策略:

1. 代码层面优化

1. 摇树优化 (Tree Shaking)
  • 原理: 移除项目中未被引用(“死代码”)的代码。Webpack 依赖 ES Module 的静态分析能力来识别和删除未使用的导出。

  • 实现:

    • 使用 ES Module 语法(import/export)。

    • mode 设置为 production (默认会启用 TerserWebpackPlugin)。

    • package.json 中配置 sideEffects 属性,明确哪些文件包含副作用(即即使没有被直接引用也不应被删除)。

      // package.json
      {
        "name": "my-app",
        "sideEffects": false // 表示所有文件都没有副作用,可以尽情摇树
        // 或者 "sideEffects": ["./src/foo.js", "*.css"] // 明确哪些文件有副作用
      }
      
    • 确保你使用的第三方库也支持 Tree Shaking。

2. 代码分割 (Code Splitting)
  • 原理: 将大的代码包拆分成更小的块,可以按需加载或并行加载。这不会减小总的代码体积,但可以显著减少初始加载的体积。

  • 实现:

    • 动态 import() 用于按需加载组件或路由。Webpack 会自动将 import() 中的模块拆分成单独的 chunk。

      // 例如,按需加载组件
      const MyComponent = () => import('./MyComponent.vue');
      
      // 例如,路由懒加载
      const routes = [
        {
          path: '/about',
          component: () => import('./views/About.vue'),
        },
      ];
      
    • optimization.splitChunks 配置: Webpack 4+ 默认开启,用于自动提取公共模块(如第三方库、多个入口文件共享的模块)到单独的 chunk。可以根据需要进行高级配置。

      // webpack.config.js
      //'async' (默认):只分割动态导入的模块,以下为默认配置(除chunks: 'all')
      module.exports = {
        // ...
        optimization: {
          splitChunks: {
            chunks: 'all', // 异步和同步模块都进行优化,
            minSize: 20000, // 提取新 chunk 的最小体积(字节)
            minChunks: 1, // 最小共享 chunk 数
            maxAsyncRequests: 30, // 按需加载时并行请求的最大数量
            maxInitialRequests: 30, // 入口点并行请求的最大数量
            enforceSizeThreshold: 50000, // 强制生成 chunk 的大小阈值
            cacheGroups: {
              vendors: {
                test: /[\/]node_modules[\/]/, // 匹配 node_modules 下的模块
                priority: -10, // 优先级
                name: 'vendors', // chunk 名称
                reuseExistingChunk: true, // 如果该 chunk 已经被打包,则复用
              },
              default: {
                minChunks: 2, // 至少被两个 chunk 引用才进行分割
                priority: -20,
                reuseExistingChunk: true,
              },
            },
          },
        },
      };
      
3. 按需加载 (On-Demand Loading) / 局部引入
  • 许多大型库(如 Lodash、Ant Design、Element UI)都支持按需加载。只引入你实际使用的模块,而不是整个库。
  • 例如,Lodash 可以使用 lodash-webpack-pluginbabel-plugin-lodash 来实现按需加载。
4. 移除不必要的代码和资源
  • 生产环境配置 mode: 'production' 这会默认启用许多优化,包括代码压缩(通过 TerserWebpackPlugin)、作用域提升(Scope Hoisting)等。

  • 移除 Console 和 Debugger: 在生产环境中移除 console.logdebugger 语句。可以使用 terser-webpack-plugindrop_consoledrop_debugger 选项。

    // webpack.config.js
    const TerserPlugin = require('terser-webpack-plugin');
    
    module.exports = {
      // ...
      optimization: {
        minimize: true,
        minimizer: [
          new TerserPlugin({
            terserOptions: {
              compress: {
                drop_console: true, // 移除 console 语句
                drop_debugger: true, // 移除 debugger 语句
              },
            },
          }),
        ],
      },
    };
    
  • 清理 dist 目录: 使用 clean-webpack-plugin 在每次构建前清理旧的构建产物。

    // webpack.config.js
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    
    module.exports = {
      // ...
      plugins: [new CleanWebpackPlugin()],
    };
    

2. 资源层面优化

1. 图片优化
  • 压缩图片: 使用 image-minimizer-webpack-plugin 结合不同的图片压缩工具(如 imagemin-mozjpeg, imagemin-pngquant 等)对图片进行无损或有损压缩。

  • 转换为 WebP/AVIF 格式: 考虑使用 WebP 或 AVIF 等新一代图片格式,它们通常具有更高的压缩率。可以使用 Webpack loader 转换。

  • 小图片转 Base64: 对于小于特定大小(如 8KB)的图片,可以使用 Webpack 5 的 asset/inline 类型将其转换为 Base64 编码内联到 CSS 或 JS 中,减少 HTTP 请求。

    // webpack.config.js
    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /.(png|jpe?g|gif|svg)$/i,
            type: 'asset', // 自动在 asset/resource 和 asset/inline 之间选择
            parser: {
              dataUrlCondition: {
                maxSize: 8 * 1024, // 小于 8KB 的图片转为 Base64
              },
            },
          },
        ],
      },
    };
    
2. 字体优化
  • 按需加载字体子集: 如果只使用字体文件中的部分字符,可以工具生成字体子集,减小字体文件大小。
  • 使用 WOFF2 格式: WOFF2 格式通常比 WOFF 或 TTF 格式更小。
3. CSS 优化
  • 提取 CSS: 使用 MiniCssExtractPlugin 将 CSS 从 JavaScript bundle 中提取到单独的 .css 文件,实现并行加载,避免 FOUC。

    // webpack.config.js
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
    module.exports = {
      // ...
      plugins: [
        new MiniCssExtractPlugin({
          filename: '[name].[contenthash].css',
        }),
      ],
      module: {
        rules: [
          {
            test: /.css$/,
            use: [MiniCssExtractPlugin.loader, 'css-loader'],
          },
        ],
      },
    };
    
  • CSS 压缩: 使用 CssMinimizerWebpackPlugin 压缩 CSS 文件。

    // webpack.config.js
    const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
    
    module.exports = {
      // ...
      optimization: {
        minimizer: [new CssMinimizerPlugin()],
      },
    };
    
  • 移除未使用的 CSS: 使用 PurgeCSS-webpack-pluginpostcss-purgecss 等工具,配合 glob-all,分析 HTML/JS/Vue/React 文件,移除 CSS 中未使用的样式。这对于引入了大型 UI 库的项目特别有效。

const PurgeCSSPlugin = require('purgecss-webpack-plugin');

new PurgeCSSPlugin({
  paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
})

3. 通用优化策略

1. JS/CSS 压缩 (Minification)
  • JavaScript:production 模式下,Webpack 默认使用 TerserWebpackPlugin 进行 JavaScript 压缩。
  • CSS: 使用 CssMinimizerWebpackPlugin 进行 CSS 压缩。
2. Gzip/Brotli 压缩
  • 虽然 Webpack 本身不直接生成 .gz.br 文件,但可以在服务器端进行配置,或者在构建时使用 compression-webpack-plugin 预先生成压缩文件。浏览器在请求时会自动协商使用这些压缩文件。

    // webpack.config.js
    const CompressionPlugin = require('compression-webpack-plugin');
    
    module.exports = {
      // ...
      plugins: [
        new CompressionPlugin({
          test: /.(js|css|html|svg)$/, // 匹配需要压缩的文件
          threshold: 10240, // 只对大于 10KB 的文件进行压缩
          minRatio: 0.8, // 只有压缩率达到 0.8 才进行压缩
          // algorithm: 'gzip', // 可以选择 gzip 或 brotli
        }),
      ],
    };
    
3. 长期缓存 (Long-Term Caching)
  • 通过在输出文件名中使用 [contenthash][chunkhash][fullhash],当文件内容发生变化时,文件名也会随之改变,从而使浏览器能够缓存未更改的文件。

    // webpack.config.js
    module.exports = {
      // ...
      output: {
        filename: '[name].[contenthash].js', // JS 文件
        chunkFilename: '[name].[contenthash].js', // 异步加载的 JS 文件
      },
    };
    
  • 结合 optimization.splitChunks,将不常变动的第三方库单独打包,进一步提升缓存命中率。

4. 分析打包报告
  • 使用 webpack-bundle-analyzer 生成可视化报告,直观地发现哪些模块占据了较大的体积,从而有针对性地进行优化。

    // webpack.config.js
    const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
    
    module.exports = {
      // ...
      plugins: [new BundleAnalyzerPlugin()],
    };
    
5. 合理使用 externals排除不必要的依赖打包
  • 如果某些大型库是通过 CDN 引入,并且希望不将其打包进 Webpack bundle,可以使用 externals 配置。这可以减小 Webpack 的打包体积,并利用 CDN 的缓存和并行加载优势。

    // webpack.config.js
    module.exports = {
      // ...
      externals: {
        react: 'React',
        'react-dom': 'ReactDOM',
      },
    };
    

4. 一些高级/特定场景优化

1. Scope Hoisting (作用域提升)
  • Webpack 4+ 在 production 模式下默认启用。它通过将多个模块合并到一个函数中,减少了模块包装函数的开销,从而减小了代码体积并提升了运行时性能。
2. resolve.aliasresolve.modules
  • alias 为模块路径设置别名,可以简化导入路径,有时也能帮助优化 Tree Shaking。
  • modules 明确告诉 Webpack 哪些目录存放模块,可以避免在不必要的目录中查找,从而加快解析速度(虽然对最终体积影响不大)。
3. 避免重复依赖
  • 检查 package.json 中的依赖,确保没有安装相同库的不同版本。npm lsyarn why 可以帮助你检查依赖树。
4. 使用更小/按需的第三方库
常用库替代方案或优化手段
lodashlodash-es + babel-plugin-lodash
moment.jsdayjs(更轻量)
antdbabel-plugin-import + 按需加载
date-fnstree-shakable,推荐使用
通过综合运用这些策略,你将能够显著优化 Webpack 构建产物的体积,从而提升你的应用的加载性能和用户体验。

5. 默认开启的优化项 设置 mode: 'production'

使用 mode: 'production' 启动生产构建,会自动启用压缩、Tree Shaking、scope hoisting 等优化配置,你一般不需要再手动配置它们,除非你有定制化需求。

功能自动开启说明
✅ Tree Shaking去除未使用的 ESM 导出(配合 sideEffects: false 效果更佳)
✅ 压缩 JS启用 TerserPlugin 自动压缩 JS
✅ 压缩 CSS是(Webpack 5)默认使用 CssMinimizerPlugin(若用 MiniCssExtractPlugin
✅ Scope Hoisting启用模块合并,减少函数包装
✅ 自动设置 process.env.NODE_ENV = 'production'可用于 React/Vue 启用生产环境优化
✅ 去除注释/空格/console(部分情况)TerserPlugin 会干掉 console.log(可配置)

总结

优化项说明
Tree Shaking移除无用 JS 代码
sideEffects防止误保留代码
splitChunks拆包分离业务与公共依赖
动态导入减少首次加载体积
externals + CDN排除 React/Vue 等大库依赖打包
babel-plugin-import实现 UI 组件按需加载
图片 base64 + WebP提升小图性能,压缩大图体积
PurgeCSS移除未用的 CSS
Terser + CSS Minimizer压缩 JS 和 CSS
分析工具找到大包根源(如 lodash、chart.js)

10. 什么是Tree Shaking?它是如何工作的?

  • Tree Shaking 是移除JavaScript上下文中未引用代码(dead-code)的优化技术

  • 工作原理:

    • 依赖于ES6模块语法的静态结构特性(import/export)
    • 在打包过程中通过静态分析识别哪些代码没有被使用
    • 在压缩阶段(UglifyJS等)删除这些未使用的代码
  • 开启条件:

    • 使用 ES Module 语法(不能用 require)
    • mode: production 或配置 optimization.usedExports = true它会启用各种优化,包括 TerserPlugin 进行代码压缩)
    • 加上 sideEffects: false(在 package.json 或模块级)告知 Webpack 哪些文件具有副作用(因此即使未直接导入也不应被摇树)。

11. 如何实现按需加载/代码分割?

方式一:动态导入(推荐)

import('./moduleA').then(m => m.default())

方式二:配置多个 entry 或使用 optimization.splitChunks 自动拆包

方式三:React 中用 React.lazy + Suspense

将第三方库(如 lodashreact 等)提取到单独的 vendor chunk 是一种常见的 Webpack 优化策略,它可以显著提升应用性能。以下是详细解释和配置方法:


为什么要提取 Vendor Chunk?

代码分割的核心意义在于避免重复打包以及提升缓存利用率,进而提升访问速度。比如,我们将不常变化的第三方依赖库进行代码拆分,方便对第三方依赖库缓存,同时抽离公共逻辑,减少单个文件的 size 大小。

解释 optimization.splitChunks 及其优点

  1. 长效缓存(Long-term Caching)

    • 第三方库代码变更频率低,单独打包后客户端可以长期缓存,减少重复下载。
  2. 减小主包体积

    • 业务代码和第三方代码分离,避免每次业务代码更新导致用户重新下载整个大文件。
  3. 并行加载优化

    • 浏览器可以并行加载 vendor.js 和业务代码,加快页面渲染速度。

配置相关

12. 如何配置多入口文件?

module.exports = {
  entry: {
    app: './src/app.js',
    admin: './src/admin.js'
  },
  output: {
    filename: '[name].bundle.js',
    path: __dirname + '/dist'
  }
};

13. 描述一个典型的 Webpack 配置文件(webpack.config.js)。

一个典型的 webpack.config.js 是一个导出一个对象的 Node.js 模块。这个对象包含 entryoutputmodule(用于加载器)、pluginsmodedevelopmentproduction)、devtooldevServer 等属性。

14. Webpack 中 mode 有哪些选项?它们有什么作用?

  • development优化构建速度,并提供有用的调试信息(例如,源映射、未压缩的代码)。
  • production优化生产环境下的 bundle(例如,摇树优化、代码压缩、作用域提升)。
  • none:不提供任何默认优化。

15. 如何在 Webpack 中处理 CSS/SCSS?

通常,你会使用 css-loader 来解释 @importurl() 这样的语句,并将其解析为 import/require()。然后使用 style-loader 将 CSS 注入到 DOM 中。对于生产环境,通常使用 MiniCssExtractPlugin 将 CSS 提取到单独的文件中,以避免 FOUC(无样式内容闪烁)并允许缓存。对于 SCSS,在 css-loader 之前使用 sass-loader(结合 node-sassdart-sass)将 SCSS 编译为 CSS。

16. 如何在 Webpack 中处理资源模块(图片和字体等)?

Webpack 5 引入了内置的资产模块(Asset Modules)type: 'asset/resource'type: 'asset/inline'type: 'asset/source'type: 'asset')。

  • asset/resource:生成单独的文件并导出 URL。(类似于 file-loader
  • asset/inline:导出数据 URI。(类似于 url-loaderinline 选项)
  • asset/source:导出资产的源代码。(类似于 raw-loader
  • asset:根据 maxSize 自动选择 resourceinline

17. 什么是 webpack-dev-server?它有什么用处?

webpack-dev-server 是一个开发服务器,提供热重载(Live Reloading)模块热替换(Hot Module Replacement - HMR) 。它从内存中提供打包的文件,从而加快开发速度,因为每次更改后无需将文件重新构建到磁盘。它对于流畅的开发体验至关重要。

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

 module.exports = {
   mode: 'development',
   entry: {
     index: './src/index.js',
     print: './src/print.js',
   },
   devtool: 'inline-source-map',
+  devServer: {
+    static: './dist',
+  },
   plugins: [
     new HtmlWebpackPlugin({
       title: 'Development',
     }),
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
     clean: true,
   },
+  optimization: {
+    runtimeChunk: 'single',
+  },
 };

以上配置告知 webpack-dev-server 将 dist 目录下的文件作为可访问资源部署在 localhost:8080

webpack-dev-server 会将在 output.path 中定义的目录中的 bundle 文件作为可访问资源部署在 server 中,即文件可以通过 http://[devServer.host]:[devServer.port]/[output.publicPath]/[output.filename] 进行访问。

从 webpack-dev-server v4.0.0 开始,模块热替换是默认开启的

18. 模块占位符

Webpack 中的模块占位符(Module Placeholders)主要用于文件命名,例如 JS、CSS、图片等构建产物的文件名,用来帮助生成唯一或有意义的文件名,特别是在构建缓存优化中非常重要


占位符说明示例输出
[name]模块或 chunk 的名称(来自 entry key 或 chunk 名)main.js, vendor.js
[id]模块 ID(数字 ID)0.js, 1.js
[hash]整个项目构建的 hashbundle.34af98.js
[chunkhash]基于 chunk 的哈希 每个 chunk 的 hash(受 chunk 内容变化影响)main.2a4e91.js
[contenthash]基于 content 的哈希 每个文件内容的 hash(推荐用于 CSS/图片等)style.abcd1234.css
[query]引用模块时的 query 参数(如:img.png?abc=123img.png?abc=123
[ext]文件扩展名(Webpack 5 Asset Modules 支持).js, .css, .png
[base]文件基础名(带扩展名)logo.png
[file]完整文件路径(如:images/logo.png)images/logo.png
[path]相对路径(Webpack 5 Asset Modules)src/assets/
[runtime]chunk 所属 runtime 名称(Webpack 5)runtime~main.js

1. [name]

  • 作用:使用模块的名称(通常取自 entry 或 splitChunks 配置)。

  • 示例

    output: {
      filename: '[name].bundle.js', // 输出如 main.bundle.js
      chunkFilename: '[name].chunk.js', // 动态导入的 chunk 名称
    }
    

2. [id]

  • 作用:使用 Webpack 内部生成的模块 ID(数字或哈希)。

  • 示例

    output: {
      chunkFilename: '[id].chunk.js', // 输出如 1.chunk.js
    }
    

3. [contenthash]

  • 作用:基于文件内容生成哈希,用于长期缓存(文件内容不变则哈希不变)。

  • 示例

    output: {
      filename: '[name].[contenthash:8].js', // 如 main.a1b2c3d4.js
    }
    
    • 最佳实践:生产环境必备,避免浏览器缓存旧文件。

4. [chunkhash]

  • 作用:基于整个 chunk 内容生成哈希(适用于代码分割)。

  • 示例

    output: {
      chunkFilename: '[name].[chunkhash:8].js', // 如 vendors.5e6f7g8h.js
    }
    

5. [hash]

  • 作用:基于整个构建过程生成哈希(所有文件共享同一哈希)。
  • 适用场景:较少使用,通常用 [contenthash] 替代。

6. [query]

  • 作用:保留模块的查询参数(如 import('./module.js?param=value'))。

  • 示例

    output: {
      filename: '[name][query].js', // 如 main.js?param=value
    }
    

7. 动态导入占位符(Magic Comments)

在动态导入时,通过注释指定 chunk 名称或加载行为:

import(
  /* webpackChunkName: "my-chunk" */ 
  /* webpackPrefetch: true */ 
  './module.js'
);
  • webpackChunkName:指定 chunk 名称(配合 [name] 使用)。
  • webpackPrefetch/webpackPreload:控制资源加载优先级。

8. 路径占位符([path][folder]

  • 作用:保留模块的原始路径信息(通常用于 file-loader 或 url-loader)。

  • 示例

    {
      test: /.(png|jpg)$/,
      use: [{
        loader: 'file-loader',
        options: {
          name: '[path][name].[ext]', // 保留目录结构
        },
      }],
    }
    

9. 自定义占位符

通过 webpack.DefinePlugin 定义全局常量:

plugins: [
  new webpack.DefinePlugin({
    __BUILD_VERSION__: JSON.stringify('1.0.0'),
  }),
],

在代码中使用:

console.log(__BUILD_VERSION__); // 输出 "1.0.0"

关键场景应用

1. 代码分割 + 长期缓存
output: {
  filename: '[name].[contenthash:8].js',
  chunkFilename: '[name].[contenthash:8].chunk.js',
},
optimization: {
  splitChunks: {
    chunks: 'all',
    name: 'vendors', // 配合 [name] 使用
  },
},
2. 动态加载的组件
const LazyComponent = lazy(() => import(
  /* webpackChunkName: "lazy-component" */ 
  './LazyComponent'
));
3. 图片/字体资源
{
  test: /.(png|svg)$/,
  type: 'asset/resource',
  generator: {
    filename: 'assets/[name].[hash:8][ext]', // 输出如 assets/logo.a1b2c3d4.png
  },
}

总结

占位符用途典型场景
[name]模块名称入口文件、代码分割
[contenthash]基于内容的哈希生产环境长期缓存
[chunkhash]基于 chunk 的哈希代码分割
[id]模块 ID动态导入的默认命名
Magic Comments控制动态导入行为懒加载、预加载

通过合理组合占位符,可以实现:

  1. 可读性:清晰的输出文件命名。
  2. 缓存优化[contenthash] 避免浏览器缓存失效。
  3. 按需加载:动态导入 + webpackChunkName 分割代码。

高级特性

19. 什么是热模块替换(HMR)?如何实现?

  • HMR(Hot Module Replacement)可以在不刷新页面的情况下更新模块

  • 实现方式:

    • 配置devServer.hot为true
    • 使用HotModuleReplacementPlugin
    • 在代码中添加HMR接收逻辑(module.hot.accept)

20. Webpack 的 source map 是什么?有哪些类型?

  • source map 将编译后的代码映射回原始源代码,便于调试

  • 常见类型:

    • eval:最快,但不映射行号
    • source-map:最完整但最慢
    • cheap-module-source-map:折中方案
    • inline-source-map:将map作为DataURI嵌入 Webpack 的 devtool 配置项用于控制如何生成 Source Map(源码映射) ,方便调试和定位问题。

什么是 Source Map

Source Map 是一种映射文件,它能将 压缩/打包后的代码 映射回 原始源代码
这对于调试非常重要,比如在浏览器里看到的是 index.js:1 出错,但通过 Source Map 能指向 src/utils/math.js:42

devtool 常用选项

以下是常见的 devtool 配置选项,按开发和生产环境分类:

选项构建速度质量适用场景
eval⚡⚡⚡⚡⚡ (最快)❌ (无 source map)开发环境(快速 rebuild)
eval-source-map⚡⚡⚡ (中等)✅ (原始源码)开发环境(需要完整源码映射)
cheap-eval-source-map⚡⚡⚡⚡ (较快)⚠️ (仅行映射,无列)开发环境(折中方案)
eval-cheap-module-source-map⚡⚡⚡ (中等)⚠️ (行映射 + loader 源码)开发环境(推荐)
source-map⚡ (最慢)✅ (完整 source map)生产环境(需精确调试)
hidden-source-map⚡ (慢)✅ (生成但不引用)生产环境(Sentry 等错误监控)
nosources-source-map⚡ (慢)⚠️ (无源码,仅错误位置)生产环境(安全保护源码)

不同的 devtool 设置会导致性能差异。

  • "eval" 具有最好的性能,但并不能帮助转译代码。
  • 如果能接受稍差一些的映射质量,可以使用 cheap-source-map 变体配置提高性能。
  • 使用 eval-source-map 变体配置进行增量编译。

在大多数情况下,最佳选择是 eval-cheap-module-source-map

其他

21. Webpack 5 有哪些新特性?

  • 持久化缓存(显著提升构建速度)
  • 更好的Tree Shaking
  • 模块联邦(Module Federation)
  • 资源模块(asset modules)替代file-loader/url-loader
  • 移除Node.js polyfill
  • 更好的长期缓存支持

22. Webpack 和 Rollup/Vite 有什么区别?

  • Webpack:功能全面,适合复杂应用
  • Rollup:更适合库的打包,Tree Shaking更高效
  • Vite:基于ESM的开发服务器,开发体验更快