阅读视图

发现新文章,点击刷新页面。

原来Webpack在大厂中这样进行性能优化!

性能优化方案

优化分类:

  1. 优化打包后的结果(分包、减小包体积、CDN 服务器) ==> 更重要
  2. 优化打包速度(exclude、cache-loader)

代码分割(Code Splitting)

一、主要目的

  • 减少首屏加载体积:避免一次性加载全部代码
  • 利用浏览器缓存:第三方库(如 React、Lodash)变动少,可单独缓存
  • 按需加载/并行请求:路由、组件、功能模块只在需要时加载(按需加载或者并行加载文件,而不是一次性加载所有代码)

二、三种主要的代码分割方式

1. 入口起点(Entry Points)手动分割

通过配置多个 entry 实现。

// webpack.config.js
module.exports = {
  entry: {
    main: './src/main.js',
    vendor: './src/vendor.js', // 手动引入公共依赖
  },
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
  },
};

缺点:

  • 无法自动提取公共依赖(比如 mainvendor 都用了 Lodash,会重复打包)
  • 维护成本高

上面写的是通用配置,但我们在公司一般会分别配置开发和生产环境的配置。大多数项目中,entry 在 dev 和 prod 基本一致,无需差异化配置。差异主要体现在 output 和其他插件/加载器行为上。

// webpack.config.prod.js
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    filename: 'js/[name].[contenthash:8].js', // 生产环境用 [contenthash](而非 [hash] 或 [chunkhash]),确保精准缓存
    chunkFilename: 'js/[name].[contenthash:8].js',
    path: path.resolve(__dirname, 'dist'), // 必须输出到磁盘用于部署
    publicPath: '/static/', // 用于 CDN 或静态资源服务器
    clean: true, // 清理旧文件
  },
};
// webpack.config.dev.js
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'js/[name].js',  // 开发环境若加 hash,每次保存都会生成新文件,可能干扰热更新或者devtools混乱
    chunkFilename: 'js/[name].js',
    path: path.resolve(__dirname, 'dist'), // 通常仍写 dist,但实际不写入磁盘(webpack-dev-server 默认内存存储),节省IO,提高编译速度
    publicPath: '/', // 与 devServer 一致
    // clean: false (默认)
  },
};
2. SplitChunksPlugin(推荐!自动代码分割)

自动提取公共模块和第三方库。webpack 已默认安装相关插件。

默认行为(仅在 production 模式生效):

// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'async', // 默认只分割异步模块
    },
  },
};

常用配置:

// webpack.config.prod.js
optimization: { 
  // 自动分割
  // https://twitter.com/wSokra/status/969633336732905474
  // https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366    
  splitChunks: {
    // chunks: async | initial(对通过的代码处理) | all(同步+异步都处理)
    chunks: 'initial',
    minSize: 20000, // 模块大于 20KB 才分割(Webpack 5 默认值)
    maxSize: 244000, // 单个 chunk 最大不超过 244KB(可选)
    cacheGroups: { // 拆分分组规则
      // 提取 node_modules 中的第三方库
      vendor: {
        test: /[\\/]node_modules[\\/]/, // 匹配符合规则的包
        name: 'vendors', // 拆分包的name 属性
        chunks: 'initial',
        priority: 10, // 优先级高于 default
        enforce: true,
      },
      // 提取多个 chunk 公共代码
      default: {
        minChunks: 2, // 至少被 2 个 chunk 引用
        priority: -20,
        reuseExistingChunk: true, // 复用已存在的 chunk
        maxInitialRequests: 5, // 默认限制太小,无法显示效果
        minSize: 0, // 这个示例太小,无法创建公共块
      },
    },
  },
  // runtime相关的代码是否抽取到一个单独的chunk中,比如import动态加载的代码就是通过runtime 代码完成的
  // 抽离出来利于浏览器缓存,比如修改了业务代码,那么runtime加载的chunk无需重新加载
  runtimeChunk: true,
}

在开发环境下 splitChunks: false, 即可。

生产环境:

  • 生成 vendors.xxxx.js(第三方库)
  • 生成 default.xxxx.js(项目公共代码)
  • 主 bundle 体积显著减小
3. 动态导入(Dynamic Imports)—— 按需加载

使用 import() 语法(符合 ES Module 规范),实现懒加载。

Webpack 会为每个 import() 创建一个独立的 chunk,并自动处理加载逻辑。

三、魔法注释(Magic Comments)—— 控制 chunk 名称等行为

// 自定义 chunk 名称(便于调试和长期缓存)
const module = await import(
  /* webpackChunkName: "my-module" */
  './my-module'
);

其他常见注释:

  • /* webpackPrefetch: true */:空闲时预加载(提升后续访问速度)
  • /* webpackPreload: true */:当前导航关键资源预加载(慎用)
// 预加载“下一个可能访问”的页面
import(
  /* webpackChunkName: "login-page" */
  /* webpackPrefetch: true */
  './LoginPage'
);

详细比较:

  • preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
  • preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。

CND

内容分发网络(Content Delivery Network 或 Content Distribution Network)

它是指通过相互连接的网络系统,利用最靠近每个用户的服务器;更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户;提供高性能、可扩展性及低成本的网络内容传递。

工作中,我们使用 CDN 的主要方式有两种:

  1. 打包所有静态资源,放到 CDN 服务器,用户所有资源都是通过 CND 服务器加载的
    1. 通过 output.publicPath 改为自己的的 CDN 服务器,打包后就可以从上面获取资源
    2. 如果是自己的话,一般会从阿里、腾讯等买 CDN 服务器。
  2. 一些第三方资源放在 CDN 服务器上
    1. 一些库/框架会将打包后的源码放到一些免费的 CDN 上,比如 JSDeliver、bootcdn 等
    2. 这样的话,打包的时候就不需要对这些库进行打包,直接使用 CDN 服务器中的源码(通过 externals 配置排除某些包)

CSS 提取

将 css 提取到一个独立的 css 文件。

npm install mini-css-extract-plugin -D
// webpack.config.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  mode: 'production',
  module: {
    rules: [
      // 生产环境:使用 MiniCssExtractPlugin.loader
      {
        test: /\.css$/i,
        use: [
          MiniCssExtractPlugin.loader, // 替换 style-loader
          'css-loader',
          'postcss-loader',
        ],
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader',
          'sass-loader',
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css',
      chunkFilename: 'css/[name].[contenthash:8].css',
    }),
  ],
};

Terser 代码压缩

Terser 可以帮助我们压缩、丑化(混淆)我们的代码,让我们的 bundle 变得更小。

Terser 是一个单独的工具,拥有非常多的配置,这里我们只讲工作中如何使用,以一个工程的角度学习这个工具。

真实开发中,我们不需要手动的通过 terser 来处理我们的代码。webpack 中 minimizer 属性,在 production 模式下,默认就是使用的 TerserPlugin 来处理我们代码的。我们也可以手动创建 TerserPlugin 实例覆盖默认配置。

// webpack.prod.js 
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  mode: 'production',
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true, // 多核 CPU 并行压缩,默认为true,并发数默认为os.cpus().length-1
        terserOptions: {
          compress: { // 压缩配置
            drop_console: true,
            drop_debugger: true, // 删除debugger
            pure_funcs: ['console.info', 'console.debug'], // 只删除特定的函数调用
          },
          mangle: true, // 是否丑化代码(变量)
          toplevel: true, // 顶层变量是否进行转换
          keep_classnames: true, // 是否保留类的名称
          keep_fnames: true, // 是否保留函数的名称
          format: {
            comments: /@license|@preserve/i, // 保留含 license/preserve 的注释(某些开源库要求保留版权注释)
          },
        },
        extractComments: true, // 默认为true会将注释提取到一个单独的文件(这里用于保留版权注释),false表示不希望保留注释
        sourceMap: true,   // 需要 webpack 配置 devtool 生成 source map
      }),
    ],
  },
};

不要在开发环境启动 terser,因为:

  • 压缩会拖慢构建速度
  • 混淆后的代码无法调试
  • hmr 和 source-map 会失效

CSS 压缩

CSS 压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等;我们一般使用插件 css-minimizer-webpack-plugin;他的底层是使用 cssnano 工具来优化、压缩 CSS(也可以单独使用)。

使用也是非常简单:

minimizer: [
  new CssMiniMizerPlugin()({
    parallel: true
  })
]

Tree Shaking 摇树

详情见之前文章:《简单聊聊 webpack 摇树的原理》

HTTP 压缩

HTTP 压缩(HTTP Compression)是一种 在服务器和客户端之间传输数据时减小响应体体积 的技术,通过压缩 HTML、CSS、JavaScript、JSON 等文本资源,显著提升网页加载速度、节省带宽。

一、主流压缩算法

算法 兼容性 压缩率 速度 说明
gzip ✅ 几乎所有浏览器(IE6+) 最广泛使用,Web 标准推荐
Brotli (br) ✅ 现代浏览器(Chrome 49+, Firefox 44+, Safari 11+) ⭐ 更高(比 gzip 高 15%~30%) 较慢(压缩),解压快 推荐用于静态资源
deflate ⚠️ 支持不一致(部分浏览器实现有问题) 已基本淘汰,不推荐使用

二、工作原理(协商压缩)

HTTP 压缩基于 请求头 ↔ 响应头协商机制:

  1. 客户端请求(表明支持的压缩格式)
GET /app.js HTTP/1.1
Host: example.com
Accept-Encoding: gzip, deflate, br // 客户端支持的压缩算法列表
  1. 服务端响应(返回压缩后的内容)
HTTP/1.1 200 OK
Content-Encoding: br  // 服务端使用的压缩算法
Content-Type: application/javascript
Content-Length: 102400  // 注意:这是压缩后的大小!

...(二进制压缩数据)...
  • 浏览器自动解压,开发者无感知

三、如何启用 HTTP 压缩?

我们一般会优先使用 Nginx 配置做压缩(生产环境最常用),这样就无需应用层处理。

除此之外,我们还会进行预压缩 + 静态文件服务,这主要就是 webpack 要做的工作。

在构建阶段(Webpack/Vite)就生成 .gz.br 文件,部署到 CDN 或静态服务器。

// webpack.config.js
const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
  plugins: [
    // 生成 .gz 文件
    new CompressionPlugin({
      algorithm: 'gzip',
      test: /\.(js|css|html|svg)$/,
      threshold: 8192, // 大于 8KB 才压缩
      minRatio: 0.8,  // 至少的压缩比例
    }),
    // 生成 .br 文件(需额外安装)
    new CompressionPlugin({
      algorithm: 'brotliCompress',
      test: /\.(js|css|html|svg)$/,
      compressionOptions: { level: 11 }, // 最高压缩率
    }),
  ],
};

Nginx 配合预压缩文件:

gzip_static on;    # 优先返回 .gz 文件
brotli_static on;  # 优先返回 .br 文件

打包分析

打包时间分析

我们需要借助一个插件 speed-measure-webpack-plugin,即可看到每个 loader、每个 plugin 消耗的打包时间。

// webpack.config.js
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');

const smp = new SpeedMeasurePlugin();

const config = {
  // 你的正常 Webpack 配置
  entry: './src/index.js',
  module: { /* ... */ },
  plugins: [ /* ... */ ],
};

// 仅当环境变量 ANALYZE_SPEED=1 时包裹配置
module.exports = process.env.ANALYZE_SPEED ? smp.wrap(config) : config;

打包文件分析

方法一、生成 stats.json 文件
"build:stats": "w--config ./config/webpack.common.js --env production --profile --json=stats.json",

运行 npm run build:stats,可以获取到一个 stats.json 文件,然后放到到 webpack.github.com/analyse 进行分析。

方法二、webpack-bundle-analyzer

更常用的方式是使用 webpack-bundle-analyzer 插件分析。

// webpack.prod.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  mode: 'production',
  plugins: [
    // 其他插件...
    new BundleAnalyzerPlugin({
      analyzerMode: 'static', // 生成静态 HTML 报告(默认)
      openAnalyzer: false,    // 不自动打开浏览器
      reportFilename: 'bundle-report.html',
      generateStatsFile: true, // 可选:同时生成 stats.json
      statsFilename: 'stats.json',
    }),
  ],
};

关于XSS和CSRF,面试官更喜欢这样的回答!

这是我们前端最常见的两种攻击手段,也是面试中最常考的前端攻击。这篇文章我用最精炼、最优雅,也是面试官最喜欢的回答方式来讲解下 XSS 和 CSRF。

一、XSS(跨站脚本)

原理

攻击者把 恶意脚本 注入到受信任页面并被浏览器执行,脚本 利用页面的信任上下文(Cookies、localStorage、DOM)窃取数据或劫持会话。

常见类型

  • 反射型(参数或路径直接反射到页面并执行)
  • 存储型(恶意内容存储在服务器,其他用户访问时触发)
  • DOM-based(客户端不安全的 DOM 操作导致执行,和服务器无关)

最小复现示例(不安全的后端 + 不安全的前端)

后端(Express — 危险示例)

// server.js(示例,仅演示不安全行为)
const express = require('express');
const app = express();

app.get('/search', (req, res) => {
  const q = req.query.q || '';
  // 直接把用户输入拼到 HTML 中 —— 危险!
  res.send(`<html><body>搜索: ${q}</body></html>`);
});

app.listen(3000);

访问 /search?q=<script>alert(1)</script> 会执行脚本(反射型)。

前端 DOM XSS(危险)

<div id="out"></div>
<script>
  const q = location.search.split('q=')[1] || '';
  document.getElementById('out').innerHTML = q; // 不转义 —— 危险(DOM XSS)
</script>

实战防范要点

  1. **输出编码(服务器端)**:所有插入 HTML 的内容做 HTML 转义(&<>\"')。
  2. 前端最小化 innerHTML:尽量用框架绑定(React/Vue 的模板)替代 innerHTML

    框架框出来的插值({value} / {{ value }})会自动做 HTML 转义,把 <>&"' 等关键字符替换成实体(&lt; 等),从而把攻击脚本当文本显示,而不是执行。

  3. 富文本白名单清洗:对于必须存储/渲染的 HTML(富文本),后端用白名单 sanitizer(比如 bleach / html-sanitizer),前端再用 DOMPurify 做一次保护,对标签属性等进行清洗。
  4. Content-Security-Policy(CSP)头部:禁止内联脚本、只允许可信源。
  5. HttpOnly Cookie 头部:token/cookie 设置 HttpOnly,防止被脚本直接读取(减轻 XSS 后果)。

示例代码 — 安全改造

后端(Express + 转义)

const escapeHtml = s => String(s)
  .replace(/&/g, '&amp;')
  .replace(/</g, '&lt;')
  .replace(/>/g, '&gt;')
  .replace(/"/g, '&quot;')
  .replace(/'/g, '&#39;');

app.get('/search', (req, res) => {
  const q = escapeHtml(req.query.q || '');
  res.send(`<html><body>搜索: ${q}</body></html>`);
});

前端(若必须渲染 HTML,用 DOMPurify)

<!-- npm install dompurify -->
<script src="https://unpkg.com/dompurify@2.<!--version-->/dist/purify.min.js"></script>
<div id="content"></div>
<script>
  // htmlFromServer 来自后端 API,仍需 sanitize
  const htmlFromServer = '<img src=x onerror=alert(1)>';
  document.getElementById('content').innerHTML = DOMPurify.sanitize(htmlFromServer);
</script>

设置 CSP(Nginx/Express header 示例)

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none';

二、CSRF(跨站请求伪造)

原理

利用用户已登录且浏览器会自动带上凭证(cookie)的特性,攻击者诱导用户发起对受信任站点的请求(如通过自动提交表单或图片请求),从而在用户 名下执行未授权操作。

最小复现示例(攻击者页面)

如果 bank.com/transfer 接受 GET 或 POST 并依赖 cookie 验证,攻击页面可这样写:

<!-- auto.html(在攻击者域名上) -->
<form action="https://bank.com/transfer" method="POST" id="f">
  <input name="to" value="attacker" />
  <input name="amount" value="1000" />
</form>
<script>document.getElementById('f').submit();</script>

用户在已登录 bank.com 的情况下访问攻击页面时,浏览器会自动带上 bank.com 的 cookie,导致转账。

防护要点

  1. SameSite Cookie:把 session/cookie 设置 SameSite=LaxStrict(Lax 对 POST 有保护,适配大多数情形)。
  2. **CSRF Token(同步/双提交)**:服务端生成随机 token,响应给前端;敏感请求必须携带并校验该 token。

    该 token 不同于 jwt token ,此处的 csrf-token 只为配合 session+cookie 传统鉴权策略做安全防护。

  3. 检查 Origin/Referer:对跨站请求校验 OriginReferer 头(通常对 POST/PUT/DELETE 生效)。
  4. 避免用 cookie 做对外 API 的认证:采用 Authorization: Bearer header 的 token 机制(只有 JS 能读/写),结合 CORS 限制。
  5. 敏感操作二次确认:密码/OTP/二次验证。

示例代码(Express + scrf token + csurf)

csurf 使用 **双提交验证机制(CSRF Token)**:

  1. 服务端生成一个 CSRF Token,放在 cookie 或 session 中。
  2. 前端每次发 POST/PUT/DELETE 请求要带上这个 token,常放在请求头或表单隐藏字段,比如:X-CSRF-Token: ey2423482374823748234
  3. 服务端校验 token,是否匹配、是否未过期、是否合法。

后端(Express)

// server.js
const express = require('express');
const cookieParser = require('cookie-parser');
const csurf = require('csurf');

const app = express();
app.use(cookieParser());
app.use(express.json());
app.use(csurf({ cookie: { httpOnly: true, sameSite: 'lax' } }));

app.get('/csrf-token', (req, res) => {
  // 返回 token 给 SPA 前端(用于后续请求 header)
  res.json({ csrfToken: req.csrfToken() });
});

app.post('/transfer', (req, res) => {
  // csurf 中间件会自动校验请求中的 token(_csrf 字段或 X-CSRF-Token header)
  // 执行转账逻辑...
  res.json({ ok: true });
});

app.listen(3000);

前端 SPA(获取 token 并在请求头中发送)

// 初始化时获取 token
async function init() {
  const r = await fetch('/csrf-token', { credentials: 'include' });
  const { csrfToken } = await r.json();
  window.__CSRF_TOKEN = csrfToken;
}

// 发送受保护请求
async function transfer() {
  await fetch('/transfer', {
    method: 'POST',
    credentials: 'include', // 仍然带 cookie
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': window.__CSRF_TOKEN
    },
    body: JSON.stringify({ to: 'bob', amount: 100 })
  });
}

只用 SameSite(简洁替代,适用多数场景),在服务端设置 cookie:

Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/;

这就能阻止绝大多数通过第三方页面触发的 POST/跨站敏感操作。

三、XSS 与 CSRF 的关键总结

概念:

  • XSS:攻击者注入脚本并可读取页面内容(更强),根源是输出/DOM 不安全。
  • CSRF:攻击者伪造用户请求,无法直接读取响应,根源是浏览器自动带凭证。

防护:

  1. 后端统一使用 HTML escape 库;富文本走白名单 sanitizer。
  2. 全站 Cookie:HttpOnly; Secure; SameSite=Lax
  3. 对需要的页面开启 CSP(report-only 先观测,再 enforce)。
  4. SPA:首次获取 CSRF token 并在后续请求中以 header 发送;服务端检查 Origin/Referer
  5. CI/代码审查禁止随意使用 innerHTML/eval/dangerouslySetInnerHTML
  6. 对关键操作实施二次验证(密码/OTP)。

Webpack高级之常用配置项

常用配置项——Mode

默认值是 production(什么都不设置的情况下);可选值有:'none' | 'development' | 'production'。

  • development:会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development,为模块和 chunk 启用有效的名。
  • production:会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production,为模块和 chunk 启用确定性的混淆名称。
  • none:不使用任何默认优化选项

常用配置项——Source Map

一、如何在 Webpack 中启用 Source Map

webpack.config.js 中,通过配置 devtool 选项来控制是否生成 source map 以及生成的类型:

module.exports = {
  // ...
  devtool: 'source-map', // 或其他值
};

二、常见的 devtool 选项及其区别

💡 实践建议

  • 开发/测试环境:常用 eval-cheap-module-source-map(速度快 + 能定位到原始模块)
  • 生产环境
    • 如果需要错误监控(如 Sentry),可使用 hidden-source-mapnosources-source-map
      • 通过 sentry-cli.map 文件上传到 Sentry
      • 用户报错时,Sentry 利用 source map 自动还原原始错误位置
    • 一般不直接暴露完整 source-map,以防源码泄露

三、Source Map 的工作原理简述

  1. Webpack 在打包时根据 devtool 配置生成 .map 文件(如 main.js.map
  2. 在输出的 bundle 文件(转换后的文件)末尾添加一行魔法注释:
//# sourceMappingURL=main.js.map
  1. 浏览器 DevTools 自动加载该 .map 文件,并将压缩代码映射回原始源码
  2. 开发者可以在 DevTools 中像调试原始代码一样设置断点、查看变量等

常用配置项——Babel、Browserslist、Polyfill

见之前的文章:《身为大厂前端的你,不能不知道 Babel + Polyfill!》

常用配置项——devServer

你有没有想过一个问题,我们干前端的,为什么要在构建工作内嵌一个服务器(devServer)。

原因其实有这么两个:

  1. 文件变化时完成自动构建
  2. 启动一个服务查看页面展示(最最之前我们学 html 的时候也是通过 live server 启动的一个内置服务器查看页面)

webpack-dev-server 在编译之后不会写入到任何输出文件,而是将 bundle 文件保留在内存中。

常见配置:

devServer: {
  host: '0.0.0.0', // 允许外部访问(如手机调试)
  port: 3000,
  open: true, // 启动后自动打开默认浏览器
  hot: true, // 启用 HMR(模块热更新)
  static: { // 指定静态资源目录(如 public 文件夹)
    directory: path.join(__dirname, 'public'), // 静态资源根目录
    publicPath: '/', // 访问路径前缀
    watch: true,     // 监听变化并刷新
  },
  compress: true, // 是否为静态文件开启 gzip压缩,默认为false
  proxy: { // 一般用于开发环境反向代理避免CORS
    '/api': {
      target: 'http://localhost:8080',
      changeOrigin: true, // 改变请求头中的 host
      pathRewrite: {
        '^/api': '', // 重写路径,去掉 /api 前缀
      },
    },
  },
  historyApiFallback: true, // 解决SPA页面在路由跳转之后,进行页面刷新时,返回404的错误
}
  • 默认 localhost(仅本机访问),这里我们在实际开发中可能会遇到一个问题,就是后端同学无法通过我们项目的 ip 地址访问,这是因为你和他的主机处于同一网段
  • 设为 '0.0.0.0' 可局域网访问(常用于真机调试)
  • hot: true:支持 HMR,若失败则 fallback 到页面刷新
  • hotOnly: true:仅 HMR,编译失败不刷新页面(hot: true 编译失败会重新刷新整个页面)

常用配置——哈希

在我们给打包的文件进行命名的时候,会使用 placeholder 占位符,这里详细说说占位符的这几个属性:

hash 本身是通过 MD4 的散列函数处理后,生成一个 128 位的 hash 值(32 个十六进制)

  • hash 值的生成和整个项目有关系:
    • 比如我们现在有两个入口 index.js 和 main.js;
    • 它们分别会输出到不同的 bundle 文件中,并且在文件名称中我们有使用 hash;
    • 这个时候,如果修改了 index.js 文件中的内容,那么 hash 会发生变化,两个文件的名称都会发生变化;
  • chunkhash 可以有效的解决上面的问题,它会根据不同的入口进行借来解析来生成 hash 值:
    • 比如我们修改了 index.js,那么 main.js 的 chunkhash 是不会发生改变的;
  • contenthash 表示生成的文件 hash 名称,只和内容有关系:
    • 比如我们的 index.js,引入了一个 style.css,style.css 有被抽取到一个独立的 css 文件中;
    • 这个 css 文件在命名时,如果我们使用的是 chunkhash,那么当 index.js 文件的内容发生变化时,css 文件的命名也会发生变化;
    • 这个时候我们可以使用 contenthash,不影响 css 文件名
❌