webpack-subresource-integrity
Version:
Webpack plugin for enabling Subresource Integrity
209 lines • 8.1 kB
JavaScript
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Plugin = void 0;
const path_1 = require("path");
const fs_1 = require("fs");
const util_1 = require("./util");
const manifest_1 = require("./manifest");
const integrity_1 = require("./integrity");
const assetTypeIntegrityKeys = [
["js", "jsIntegrity"],
["css", "cssIntegrity"],
];
class Plugin {
constructor(compilation, options, reporter) {
/**
* @internal
*/
this.hwpPublicPath = null;
/**
* @internal
*/
this.sortedSccChunks = [];
/**
* @internal
*/
this.chunkManifest = new Map();
/**
* @internal
*/
this.hashByPlaceholder = new Map();
/**
* @internal
*/
this.addMissingIntegrityHashes = (assets) => {
Object.entries(assets).forEach(([assetKey, asset]) => {
const source = (0, util_1.tryGetSource)(asset);
if (source && !this.assetIntegrity.has(assetKey)) {
this.assetIntegrity.updateFromSource(assetKey, source);
}
});
};
/**
* @internal
*/
this.replaceAsset = (compiler, assets, hashByPlaceholder, chunkFile) => {
const asset = assets[chunkFile];
(0, util_1.assert)(asset, `Missing asset for file ${chunkFile}`);
return (0, util_1.replaceInSource)(compiler, asset, chunkFile, hashByPlaceholder);
};
this.warnAboutLongTermCaching = (assetInfo) => {
if ((0, util_1.usesAnyHash)(assetInfo) &&
!(assetInfo.contenthash &&
this.compilation.compiler.options.optimization.realContentHash)) {
this.reporter.warnContentHash();
}
};
/**
* @internal
*/
this.processChunk = (chunk, assets) => {
Array.from((0, util_1.findChunks)(chunk))
.reverse()
.forEach((chunk) => this.processChunkAssets(chunk, assets));
};
this.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((0, util_1.makePlaceholder)(this.options.hashFuncNames, childChunk.id), integrity);
}
(0, util_1.updateAsset)(this.compilation, sourcePath, newAsset, integrity, this.warnAboutLongTermCaching);
}
else {
this.reporter.warnNoAssetsFound(sourcePath, Object.keys(assets));
}
});
};
/**
* @internal
*/
this.addAttribute = (elName, source) => {
if (!this.compilation.outputOptions.crossOriginLoading) {
this.reporter.errorCrossOriginLoadingNotSet();
}
return this.compilation.compiler.webpack.Template.asString([
source,
elName + `.integrity = ${util_1.sriHashVariableReference}[chunkId];`,
elName +
".crossOrigin = " +
JSON.stringify(this.compilation.outputOptions.crossOriginLoading) +
";",
]);
};
/**
* @internal
*/
this.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
*/
this.hwpAssetPath = (src) => {
(0, util_1.assert)(this.hwpPublicPath !== null, "Missing HtmlWebpackPlugin publicPath");
return (0, path_1.relative)(this.hwpPublicPath, src);
};
/**
* @internal
*/
this.getIntegrityChecksumForAsset = (assets, src) => {
if (this.assetIntegrity.has(src)) {
return this.assetIntegrity.get(src);
}
const normalizedSrc = (0, util_1.normalizePath)(src);
const normalizedKey = Object.keys(assets).find((assetKey) => (0, util_1.normalizePath)(assetKey) === normalizedSrc);
return normalizedKey ? this.assetIntegrity.get(normalizedKey) : undefined;
};
/**
* @internal
*/
this.processTag = (tag) => {
if (tag.attributes &&
Object.prototype.hasOwnProperty.call(tag.attributes, "integrity")) {
return;
}
const tagSrc = (0, util_1.getTagSrc)(tag);
if (!tagSrc) {
return;
}
const src = this.hwpAssetPath(tagSrc);
tag.attributes["integrity"] =
this.getIntegrityChecksumForAsset(this.compilation.assets, src) ||
(0, util_1.computeIntegrity)(this.options.hashFuncNames, (0, fs_1.readFileSync)((0, path_1.join)(this.compilation.compiler.outputPath, src)));
tag.attributes["crossorigin"] =
this.compilation.compiler.options.output.crossOriginLoading ||
"anonymous";
};
/**
* @internal
*/
this.beforeRuntimeRequirements = () => {
if (this.options.hashLoading === "lazy") {
const [sortedSccChunks, chunkManifest] = (0, manifest_1.getChunkToManifestMap)(this.compilation.chunks);
this.sortedSccChunks = sortedSccChunks;
this.chunkManifest = chunkManifest;
}
this.hashByPlaceholder.clear();
};
this.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(util_1.notNil);
}
});
};
this.handleHwpBodyTags = ({ headTags, bodyTags, }) => {
this.addMissingIntegrityHashes(this.compilation.assets);
headTags.concat(bodyTags).forEach(this.processTag);
};
this.compilation = compilation;
this.options = options;
this.reporter = reporter;
this.assetIntegrity = new integrity_1.AssetIntegrity(this.options.hashFuncNames);
}
/**
* @internal
*/
warnIfHotUpdate(source) {
if (source.indexOf("webpackHotUpdate") >= 0) {
this.reporter.warnHotReloading();
}
}
getChildChunksToAddToChunkManifest(chunk) {
var _a;
return (_a = this.chunkManifest.get(chunk)) !== null && _a !== void 0 ? _a : new Set();
}
updateHash(input, oldHash) {
return this.assetIntegrity.updateHash(input, oldHash);
}
}
exports.Plugin = Plugin;
//# sourceMappingURL=plugin.js.map