webpack-subresource-integrity
Version:
Webpack plugin for enabling Subresource Integrity
220 lines • 7.35 kB
JavaScript
/**
* Copyright (c) 2015-present, Waysact Pty Ltd
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { relative, join } from "path";
import { readFileSync } from "fs";
import { assert, computeIntegrity, makePlaceholder, findChunks, normalizePath, getTagSrc, notNil, sriHashVariableReference, updateAsset, tryGetSource, replaceInSource, usesAnyHash, } from "./util";
import { getChunkToManifestMap } from "./manifest";
import { AssetIntegrity } from "./integrity";
const assetTypeIntegrityKeys = [
["js", "jsIntegrity"],
["css", "cssIntegrity"],
];
export class Plugin {
/**
* @internal
*/
compilation;
/**
* @internal
*/
options;
/**
* @internal
*/
reporter;
/**
* @internal
*/
assetIntegrity;
/**
* @internal
*/
hwpPublicPath = null;
/**
* @internal
*/
sortedSccChunks = [];
/**
* @internal
*/
chunkManifest = new Map();
/**
* @internal
*/
hashByPlaceholder = new Map();
constructor(compilation, options, reporter) {
this.compilation = compilation;
this.options = options;
this.reporter = reporter;
this.assetIntegrity = new AssetIntegrity(this.options.hashFuncNames);
}
/**
* @internal
*/
warnIfHotUpdate(source) {
if (source.indexOf("webpackHotUpdate") >= 0) {
this.reporter.warnHotReloading();
}
}
/**
* @internal
*/
addMissingIntegrityHashes = (assets) => {
Object.entries(assets).forEach(([assetKey, asset]) => {
const source = tryGetSource(asset);
if (source && !this.assetIntegrity.has(assetKey)) {
this.assetIntegrity.updateFromSource(assetKey, source);
}
});
};
/**
* @internal
*/
replaceAsset = (compiler, assets, hashByPlaceholder, chunkFile) => {
const asset = assets[chunkFile];
assert(asset, `Missing asset for file ${chunkFile}`);
return replaceInSource(compiler, asset, chunkFile, hashByPlaceholder);
};
warnAboutLongTermCaching = (assetInfo) => {
if (usesAnyHash(assetInfo) &&
!(assetInfo.contenthash &&
this.compilation.compiler.options.optimization.realContentHash)) {
this.reporter.warnContentHash();
}
};
/**
* @internal
*/
processChunk = (chunk, assets) => {
Array.from(findChunks(chunk))
.reverse()
.forEach((chunk) => this.processChunkAssets(chunk, assets));
};
processChunkAssets = (childChunk, assets) => {
Array.from(childChunk.files).forEach((sourcePath) => {
const asset = assets[sourcePath];
if (asset) {
this.warnIfHotUpdate(asset.source());
const newAsset = this.replaceAsset(this.compilation.compiler, assets, this.hashByPlaceholder, sourcePath);
const integrity = this.assetIntegrity.updateFromSource(sourcePath, newAsset.source());
if (childChunk.id !== null) {
this.hashByPlaceholder.set(makePlaceholder(this.options.hashFuncNames, childChunk.id), integrity);
}
updateAsset(this.compilation, sourcePath, newAsset, integrity, this.warnAboutLongTermCaching);
}
else {
this.reporter.warnNoAssetsFound(sourcePath, Object.keys(assets));
}
});
};
/**
* @internal
*/
addAttribute = (elName, source) => {
if (!this.compilation.outputOptions.crossOriginLoading) {
this.reporter.errorCrossOriginLoadingNotSet();
}
return this.compilation.compiler.webpack.Template.asString([
source,
elName + `.integrity = ${sriHashVariableReference}[chunkId];`,
elName +
".crossOrigin = " +
JSON.stringify(this.compilation.outputOptions.crossOriginLoading) +
";",
]);
};
/**
* @internal
*/
processAssets = (assets) => {
if (this.options.hashLoading === "lazy") {
for (const scc of this.sortedSccChunks) {
for (const chunk of scc.nodes) {
this.processChunkAssets(chunk, assets);
}
}
}
else {
Array.from(this.compilation.chunks)
.filter((chunk) => chunk.hasRuntime())
.forEach((chunk) => {
this.processChunk(chunk, assets);
});
}
this.addMissingIntegrityHashes(assets);
};
/**
* @internal
*/
hwpAssetPath = (src) => {
assert(this.hwpPublicPath !== null, "Missing HtmlWebpackPlugin publicPath");
return relative(this.hwpPublicPath, src);
};
/**
* @internal
*/
getIntegrityChecksumForAsset = (assets, src) => {
if (this.assetIntegrity.has(src)) {
return this.assetIntegrity.get(src);
}
const normalizedSrc = normalizePath(src);
const normalizedKey = Object.keys(assets).find((assetKey) => normalizePath(assetKey) === normalizedSrc);
return normalizedKey ? this.assetIntegrity.get(normalizedKey) : undefined;
};
/**
* @internal
*/
processTag = (tag) => {
if (tag.attributes &&
Object.prototype.hasOwnProperty.call(tag.attributes, "integrity")) {
return;
}
const tagSrc = getTagSrc(tag);
if (!tagSrc) {
return;
}
const src = this.hwpAssetPath(tagSrc);
tag.attributes["integrity"] =
this.getIntegrityChecksumForAsset(this.compilation.assets, src) ||
computeIntegrity(this.options.hashFuncNames, readFileSync(join(this.compilation.compiler.outputPath, src)));
tag.attributes["crossorigin"] =
this.compilation.compiler.options.output.crossOriginLoading ||
"anonymous";
};
/**
* @internal
*/
beforeRuntimeRequirements = () => {
if (this.options.hashLoading === "lazy") {
const [sortedSccChunks, chunkManifest] = getChunkToManifestMap(this.compilation.chunks);
this.sortedSccChunks = sortedSccChunks;
this.chunkManifest = chunkManifest;
}
this.hashByPlaceholder.clear();
};
getChildChunksToAddToChunkManifest(chunk) {
return this.chunkManifest.get(chunk) ?? new Set();
}
handleHwpPluginArgs = ({ assets }) => {
this.hwpPublicPath = assets.publicPath;
assetTypeIntegrityKeys.forEach(([a, b]) => {
if (b) {
assets[b] = assets[a]
.map((filePath) => this.getIntegrityChecksumForAsset(this.compilation.assets, this.hwpAssetPath(filePath)))
.filter(notNil);
}
});
};
handleHwpBodyTags = ({ headTags, bodyTags, }) => {
this.addMissingIntegrityHashes(this.compilation.assets);
headTags.concat(bodyTags).forEach(this.processTag);
};
updateHash(input, oldHash) {
return this.assetIntegrity.updateHash(input, oldHash);
}
}
//# sourceMappingURL=plugin.js.map