我们知道,在 Webpack 中有三种哈希:
hash :一次 compilation 总体的哈希,只要有一个文件修改,整个哈希就会发生变化chunk-hash :根据 chunk 生成的哈希,同一个 chunk 中所有文件的哈希相同content-hash :根据文件内容生成的哈希
在 Vue-cli 默认 Webpack 配置中,对 JS 启用 chunk-hash ,CSS 启用 content-hash ,而图片和字体文件则是 hash 。这样就产生一个问题,修改 JS 代码后,图片和字体的哈希是否会发生变化?
这个问题看起来有点中二,如果修改 JS 代码,导致图片、字体的哈希改变了,显然非常不合理。。但还是抱着好奇的心态去看了源码
file-loader 源码简化之后如下:
import { getOptions, interpolateName } from 'loader-utils';
export default function loader(content) {
const options = getOptions(this);
const name = options.name || '[contenthash].[ext]';
const url = interpolateName(this, name, { content });
let publicPath = `__webpack_public_path__ + ${JSON.stringify(url)}`;
this.emitFile(url, content);
const esModule =
typeof options.esModule !== 'undefined' ? options.esModule : true;
return `${esModule ? 'export default' : 'module.exports ='} ${publicPath};`;
}
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) {
url = url
.replace(
/\[(?:([^:\]]+):)?(?:hash|contenthash)(?::([a-z]+\d*))?(?::(\d+))?\]/gi,
(all, hashType, digestType, maxLength) =>
getHashDigest(content, hashType, digestType, parseInt(maxLength, 10))
);
}
}
看这边用到了 getHashDigest 方法,我们可以不用关心内部实现。源码中的正则表达式,我们使用 "[hash].[ext]" 进行实验,发现能拿到 hash 或 contenthash 配置信息的,只有第一个参数 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
|