• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

源码 file-loader 哈希生成规则

武飞扬头像
天猫精灵998
帮助1

我们知道,在 Webpack 中有三种哈希:

  • hash:一次 compilation 总体的哈希,只要有一个文件修改,整个哈希就会发生变化
  • chunk-hash:根据 chunk 生成的哈希,同一个 chunk 中所有文件的哈希相同
  • content-hash:根据文件内容生成的哈希

Vue-cli 默认 Webpack 配置中,对 JS 启用 chunk-hash,CSS 启用 content-hash,而图片和字体文件则是 hash。这样就产生一个问题,修改 JS 代码后,图片和字体的哈希是否会发生变化?

这个问题看起来有点中二,如果修改 JS 代码,导致图片、字体的哈希改变了,显然非常不合理。。但还是抱着好奇的心态去看了源码

file-loader 源码简化之后如下:

// 这里的 loader-utils 是 Webpack 暴露给 loader 的 API
import { getOptions, interpolateName } from 'loader-utils';

export default function loader(content) {
  // getOptions 方法用于获取 loader 的配置
  const options = getOptions(this);
  // 这里的 name 选项是配置中传递的
  const name = options.name || '[contenthash].[ext]';
  // interpolateName 方法可以根据 name 和 content 内容生成哈希
  // 可以保证文件内容没有发生变化的时候,文件名中的 [hash] 字段不变
  const url = interpolateName(this, name, { content });
  // 拼接文件路径
  // 这里的 __webpack_public_path__ 是 Webpack 提供的运行时的全局变量,即 publicPath
  let publicPath = `__webpack_public_path__   ${JSON.stringify(url)}`;
  // emitFile 是 Loader Context 中的 API,告诉 Webpack 创建一个文件
  // 这样 Webpack 就会在 dist 目录下创建一个对应的文件
  this.emitFile(url, content);
  const esModule =
    typeof options.esModule !== 'undefined' ? options.esModule : true;
  // 返回一个字符串形式的 JS 模块,显然是在浏览器端执行的
  return `${esModule ? 'export default' : 'module.exports ='} ${publicPath};`;
}

// 记得加上这个,默认情况下 Webpack 会把文件内容当做 utf8 字符串处理
// 而图片是二进制的,当做 utf8 会导致图片格式错误
export const raw = true;
学新通

上面的代码完全可以正常运行。我们可以看到,file-loader 其实就做了三件事:

  • 根据给定的文件名配置和文件内容,生成带有哈希的文件路径;
  • 根据生成的文件路径,创建一个文件;
  • 最后返回一个字符串形式的 JS 模块,加载这个模块,就可以得到文件路径;

关于 Loader Context,应该有不少小伙伴都知道,例如可以使用 this.callback() 返回多个结果,使用 this.async() 指定异步 loader。这里用到的 this.emitFile() 也是 Loader Context 上的方法,用于创建一个文件。

https://webpack.docschina.org/api/loaders/#thisemitfile

但是可能大家对 loader-utils 了解得比较少,这同样也是 Webpack 暴露给 Loader 的 API,只不过这个是通过第三方库的形式引入的。

这里提一下,loader-utils 中的 getOptions 方法在 v3.2.0 中已经被移除了,Webpack5 可以从 Loader Context 的 this.getOptions 方法获取。这相当于是一个破坏性更新,file-loader 中之所以还能使用,是因为依赖版本锁定为 "loader-utils": "^2.0.0",也就是范围在 >=2.0.0 <3.0.0

从上面的代码中可以看出,生成哈希相关逻辑都在 interpolateName 这个方法里面,部分源码如下:

function interpolateName(loaderContext, name, options = {}) {
	let filename = name || "[hash].[ext]";
	const content = options.content;
	// ...
	let url = filename;
	if (content) {
	    // Match hash template
	    url = url
	      // `hash` and `contenthash` are same in `loader-utils` context
	      // let's keep `hash` for backward compatibility
	      .replace(
	        /\[(?:([^:\]] ):)?(?:hash|contenthash)(?::([a-z] \d*))?(?::(\d ))?\]/gi,
	        (all, hashType, digestType, maxLength) =>
	          getHashDigest(content, hashType, digestType, parseInt(maxLength, 10))
	      );
    }
    // ...
}
学新通

看这边用到了 getHashDigest 方法,我们可以不用关心内部实现。源码中的正则表达式,我们使用 "[hash].[ext]" 进行实验,发现能拿到 hashcontenthash 配置信息的,只有第一个参数 all,其他都是 undefined
学新通
然而在源码中这个 all 根本就没有传给 getHashDigest 方法,传递的参数中唯一有用的参数就是 content,也就是文件内容。因此我们可以得出结论,文件名中无论配置 hash 或者 contenthash,都是等价的,实际上都是 contenthash,都是根据文件本身的内容生成的,与 Webpack 的构建过程无关。

参考

webpack 源码解析:file-loader 和 url-loader

https://github.com/webpack-contrib/file-loader

https://github.com/webpack/loader-utils

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgfjbge
系列文章
更多 icon
同类精品
更多 icon
继续加载