hard-source-webpack-plugin-fixed-hashbug
Version:
Hard cache the source of modules in webpack. Patched version fixing the hash bug
708 lines (643 loc) • 19.5 kB
JavaScript
const NormalModule = require('webpack/lib/NormalModule');
const Module = require('webpack/lib/Module');
const nodeObjectHash = require('node-object-hash');
const logMessages = require('./util/log-messages');
const {
relateNormalPath,
relateNormalRequest,
relateNormalPathSet,
relateNormalLoaders,
} = require('./util/relate-context');
const pluginCompat = require('./util/plugin-compat');
const serial = require('./util/serial');
const serialResolveRequest = serial.created({
context: serial.path,
request: serial.request,
});
const serialResolved = serial.created({
// context: serial.path,
// request: serial.request,
// userRequest: serial.request,
// rawRequest: serial.request,
resource: serial.request,
resolveOptions: serial.identity,
// loaders: serial.loaders,
});
const serialJson = {
freeze(arg, value, extra) {
return JSON.parse(arg);
},
thaw(arg, frozen, extra) {
return JSON.stringify(arg);
},
};
const serialMap = serial.map;
const serialResolvedMap = serial.map(
serial.pipe(
{ freeze: serialJson.freeze, thaw: serial.identity.thaw },
serialResolveRequest,
{ freeze: serial.identity.freeze, thaw: serialJson.thaw },
),
serialResolved,
);
const serialResourceHashMap = serial.map(serial.request, serial.identity);
const serialNormalConstructor4 = serial.constructed(NormalModule, {
data: serial.pipe(
{ freeze: (arg, module) => module, thaw: arg => arg },
serial.created({
type: serial.identity,
request: serial.request,
userRequest: serial.request,
rawRequest: serial.request,
loaders: serial.loaders,
resource: serial.path,
parser: serial.parser,
generator: serial.generator,
resolveOptions: serial.identity,
}),
),
});
const serialNormalModuleExtra4 = {
freeze() {},
thaw(arg, frozen, extra, methods) {
extra.module = arg;
return arg;
},
};
const serialNormalIdentifier4 = {
freeze(arg, module, extra, methods) {
return serial.request.freeze(module.identifier(), null, extra, methods);
},
thaw(arg) {
return arg;
},
};
const serialNormalAssigned4 = serial.assigned({
factoryMeta: serial.identity,
issuer: serial.pipe(
{
freeze(arg, { issuer }) {
return issuer && typeof issuer === 'object'
? issuer.identifier()
: issuer;
},
thaw(arg, frozen, extra) {
return arg;
},
},
serial.request,
{
freeze(arg) {
return arg;
},
thaw(arg, frozen, { compilation }) {
if (compilation.modules) {
for (const module of compilation.modules) {
if (
module &&
typeof module.identifier === 'function' &&
module.identifier() === arg
) {
return module;
}
}
for (const cacheId in compilation.cache) {
const module = compilation.cache[cacheId];
if (
module &&
typeof module.identifier === 'function' &&
module.identifier() === arg
) {
return module;
}
}
}
return arg;
},
},
),
useSourceMap: serial.identity,
lineToLine: serial.identity,
});
const serialNormalOriginExtra4 = {
freeze() {},
thaw(arg, frozen, extra) {
if (typeof arg.issuer === 'object') {
extra.origin = arg.issuer;
}
return arg;
},
};
const serialNormalBuild4 = serial.assigned({
built: serial.identity,
buildTimestamp: serial.identity,
buildMeta: serial.identity,
buildInfo: serial.created({
assets: serial.moduleAssets,
cacheable: serial.identity,
contextDependencies: serial.pathSet,
exportsArgument: serial.identity,
fileDependencies: serial.pathSet,
harmonyModule: serial.identity,
jsonData: serial.identity,
strict: serial.identity,
}),
warnings: serial.moduleWarning,
errors: serial.moduleError,
_source: serial.source,
_buildHash: serial.identity,
hash: serial.identity,
_lastSuccessfulBuildMeta: serial.identity,
__hardSource_resolved: serialResolvedMap,
__hardSource_oldHashes: serial.pipe(
{
freeze(arg, module, extra) {
const obj = {};
const cachedMd5s = extra.compilation.__hardSourceFileMd5s;
for (const file of module.buildInfo.fileDependencies) {
obj[file] = cachedMd5s[file];
}
for (const dir of module.buildInfo.contextDependencies) {
obj[dir] = cachedMd5s[dir];
}
return obj;
},
thaw: serial.identity.thaw,
},
serialResourceHashMap,
),
});
const serialNormalError4 = {
freeze() {},
thaw(arg, module, extra) {
arg.error = arg.errors[0] || null;
return arg;
},
};
const serialNormalSourceExtra4 = {
freeze() {},
thaw(arg, module, extra) {
extra.source = arg._source;
return arg;
},
};
const serialNormalSource4 = serial.assigned({
_cachedSource: serial.source,
_cachedSourceHash: serial.identity,
renderedHash: serial.identity,
});
const serialNormalModule4PreBuild = serial.serial('NormalModule', {
constructor: serialNormalConstructor4,
setModuleExtra: serialNormalModuleExtra4,
identifier: serialNormalIdentifier4,
assigned: serialNormalAssigned4,
setOriginExtra: serialNormalOriginExtra4,
});
const serialNormalModule4PostBuild = serial.serial('NormalModule', {
build: serialNormalBuild4,
dependencyBlock: serial.dependencyBlock,
setError: serialNormalError4,
setSourceExtra: serialNormalSourceExtra4,
source: serialNormalSource4,
});
const serialNormalModule4 = serial.serial('NormalModule', {
constructor: serialNormalConstructor4,
setModuleExtra: serialNormalModuleExtra4,
identifier: serialNormalIdentifier4,
assigned: serialNormalAssigned4,
setOriginExtra: serialNormalOriginExtra4,
build: serialNormalBuild4,
dependencyBlock: serial.dependencyBlock,
setError: serialNormalError4,
setSourceExtra: serialNormalSourceExtra4,
source: serialNormalSource4,
});
const needRebuild4 = function() {
if (this.error) {
this.cacheItem.invalid = true;
this.cacheItem.invalidReason = 'error building';
return true;
}
const fileHashes = this.__hardSourceFileMd5s;
const cachedHashes = this.__hardSourceCachedMd5s;
const resolvedLast = this.__hardSource_resolved;
const missingCache = this.__hardSource_missingCache;
for (const file of this.buildInfo.fileDependencies) {
if (!cachedHashes[file] || fileHashes[file] !== cachedHashes[file]) {
this.cacheItem.invalid = true;
this.cacheItem.invalidReason = 'md5 mismatch';
return true;
}
}
for (const dir of this.buildInfo.contextDependencies) {
if (!cachedHashes[dir] || fileHashes[dir] !== cachedHashes[dir]) {
this.cacheItem.invalid = true;
this.cacheItem.invalidReason = 'md5 mismatch';
return true;
}
}
let resolvedNeedRebuild = false;
for (const _resolveKey in resolvedLast) {
const resolveKey = JSON.parse(_resolveKey);
const resolved = resolvedLast[_resolveKey];
let normalId = 'normal';
if (resolved.resolveOptions) {
normalId = `normal-${new nodeObjectHash({ sort: false }).hash(
resolved.resolveOptions,
)}`;
}
const resolvedMissing =
missingCache[normalId] &&
missingCache[normalId][
JSON.stringify([resolveKey.context, resolved.resource.split('?')[0]])
];
if (!resolvedMissing || resolvedMissing.invalid) {
resolved.invalid = true;
resolved.invalidReason = `resolved normal invalid${
resolvedMissing
? ` ${resolvedMissing.invalidReason}`
: ': resolve entry not in cache'
}`;
resolvedNeedRebuild = true;
}
}
return resolvedNeedRebuild;
};
const serialNormalModule3 = serial.serial('NormalModule', {
constructor: serial.constructed(NormalModule, {
request: serial.request,
userRequest: serial.request,
rawRequest: serial.request,
loaders: serial.loaders,
resource: serial.path,
parser: serial.parser,
}),
setModuleExtra: serialNormalModuleExtra4,
// Used internally by HardSource
identifier: serialNormalIdentifier4,
assigned: serial.assigned({
issuer: serial.pipe(
{
freeze(arg, { issuer }) {
return issuer && typeof issuer === 'object'
? issuer.identifier()
: issuer;
},
thaw(arg, frozen, extra) {
return arg;
},
},
serial.request,
{
freeze(arg) {
return arg;
},
thaw(arg, frozen, { compilation }) {
if (compilation.modules) {
for (const module of compilation.modules) {
if (
module &&
typeof module.identifier === 'function' &&
module.identifier() === arg
) {
return module;
}
}
for (const cacheId in compilation.cache) {
const module = compilation.cache[cacheId];
if (
module &&
typeof module.identifier === 'function' &&
module.identifier() === arg
) {
return module;
}
}
}
return arg;
},
},
),
useSourceMap: serial.identity,
lineToLine: serial.identity,
}),
setOriginExtra: {
freeze() {},
thaw(arg, frozen, extra) {
if (typeof arg.issuer === 'object') {
extra.origin = arg.issuer;
}
return arg;
},
},
build: serial.assigned({
built: serial.identity,
buildTimestamp: serial.identity,
cacheable: serial.identity,
meta: serial.identity,
assets: serial.moduleAssets,
fileDependencies: serial.pathArray,
contextDependencies: serial.pathArray,
harmonyModule: serial.identity,
strict: serial.identity,
exportsArgument: serial.identity,
warnings: serial.moduleWarning,
errors: serial.moduleError,
_source: serial.source,
__hardSource_resolved: serialResolvedMap,
__hardSource_oldHashes: serial.pipe(
{
freeze(arg, module, extra) {
const obj = {};
const cachedMd5s = extra.compilation.__hardSourceCachedMd5s;
for (const file of module.fileDependencies) {
obj[file] = cachedMd5s[file];
}
for (const dir of module.contextDependencies) {
obj[dir] = cachedMd5s[dir];
}
return obj;
},
thaw: serial.identity.thaw,
},
serialResourceHashMap,
),
}),
hash: {
freeze(arg, module, { compilation }, methods) {
return module.getHashDigest(compilation.dependencyTemplates);
},
thaw(arg) {
return arg;
},
},
dependencyBlock: serial.dependencyBlock,
setError: {
freeze() {},
thaw(arg, module, extra) {
arg.error = arg.errors[0] || null;
return arg;
},
},
setSourceExtra: {
freeze() {},
thaw(arg, module, extra) {
extra.source = arg._source;
return arg;
},
},
source: serial.assigned({
_cachedSource: serial.created({
source: serial.source,
hash: serial.identity,
}),
}),
});
const needRebuild3 = function() {
if (this.error) {
this.cacheItem.invalid = true;
this.cacheItem.invalidReason = 'error building';
return true;
}
const fileHashes = this.__hardSourceFileMd5s;
const cachedHashes = this.__hardSourceCachedMd5s;
const resolvedLast = this.__hardSource_resolved;
const missingCache = this.__hardSource_missingCache;
for (const file of this.fileDependencies) {
if (!cachedHashes[file] || fileHashes[file] !== cachedHashes[file]) {
this.cacheItem.invalid = true;
this.cacheItem.invalidReason = 'md5 mismatch';
return true;
}
}
for (const dir of this.contextDependencies) {
if (!cachedHashes[dir] || fileHashes[dir] !== cachedHashes[dir]) {
this.cacheItem.invalid = true;
this.cacheItem.invalidReason = 'md5 mismatch';
return true;
}
}
let resolvedNeedRebuild = false;
for (const _resolveKey in resolvedLast) {
const resolveKey = JSON.parse(_resolveKey);
const resolved = resolvedLast[_resolveKey];
let normalId = 'normal';
if (resolved.resolveOptions) {
normalId = `normal-${new nodeObjectHash({ sort: false }).hash(
resolved.resolveOptions,
)}`;
}
const resolvedMissing =
missingCache[normalId] &&
missingCache[normalId][
JSON.stringify([resolveKey.context, resolved.resource.split('?')[0]])
];
if (!resolvedMissing || resolvedMissing.invalid) {
resolved.invalid = true;
resolved.invalidReason = `resolved normal invalid${
resolvedMissing
? ` ${resolvedMissing.invalidReason}`
: ': resolve entry not in cache'
}`;
resolvedNeedRebuild = true;
}
}
return resolvedNeedRebuild;
};
const cacheable = module =>
module.buildInfo ? module.buildInfo.cacheable : module.cacheable;
class TransformNormalModulePlugin {
constructor(options) {
this.options = options || {};
}
apply(compiler) {
const schema = this.options.schema;
let serialNormalModule = serialNormalModule4;
let needRebuild = needRebuild4;
if (schema < 4) {
serialNormalModule = serialNormalModule3;
needRebuild = needRebuild3;
}
let createHash;
if (schema >= 4) {
createHash = require('webpack/lib/util/createHash');
}
let freeze;
let mapFreeze;
let _methods;
pluginCompat.tap(
compiler,
'_hardSourceMethods',
'TransformNormalModulePlugin',
methods => {
_methods = methods;
// store = methods.store;
// fetch = methods.fetch;
freeze = methods.freeze;
// thaw = methods.thaw;
mapFreeze = methods.mapFreeze;
// mapThaw = methods.mapThaw;
},
);
pluginCompat.tap(
compiler,
'compilation',
'TransformNormalModulePlugin',
compilation => {
pluginCompat.tap(
compilation,
'succeedModule',
'TransformNormalModulePlugin',
module => {
if (module instanceof NormalModule) {
try {
module._dependencyBlock = freeze(
'DependencyBlock',
null,
module,
{
module,
parent: module,
compilation,
},
);
} catch (e) {
logMessages.moduleFreezeError(compilation, module, e);
}
}
},
);
},
);
pluginCompat.tap(
compiler,
'_hardSourceFreezeModule',
'TransformNormalModulePlugin',
(frozen, module, extra) => {
// Set hash if it was not set.
if (
schema === 4 &&
module instanceof NormalModule &&
module.buildTimestamp &&
!module.hash
) {
const outputOptions = extra.compilation.outputOptions;
const hashFunction = outputOptions.hashFunction;
const hashDigest = outputOptions.hashDigest;
const hashDigestLength = outputOptions.hashDigestLength;
if (module.buildInfo && module._initBuildHash) {
module._initBuildHash(extra.compilation);
}
const moduleHash = createHash(hashFunction);
module.updateHash(moduleHash);
module.hash = moduleHash.digest(hashDigest);
module.renderedHash = module.hash.substr(0, hashDigestLength);
if (module._cachedSource) {
module._cachedSourceHash = module.getHashDigest(
extra.compilation.dependencyTemplates,
);
}
}
if (
module.request &&
(cacheable(module) || !module.built) &&
module instanceof NormalModule &&
(!frozen ||
!frozen.build ||
(schema >= 4 && module.hash !== frozen.build.hash) ||
(schema < 4 &&
module.getHashDigest(extra.compilation.dependencyTemplates) !==
frozen.hash))
) {
const compilation = extra.compilation;
if (module.cacheItem) {
module.cacheItem.invalid = false;
module.cacheItem.invalidReason = null;
}
let serialModule = serialNormalModule;
if (!module.built) {
serialModule = serialNormalModule4PreBuild;
}
const f = serialModule.freeze(
null,
module,
{
module,
compilation,
},
_methods,
);
// The saved dependencies may not be the ones derived in the hash. This is
// alright, in such a case the dependencies were altered before the source
// was rendered. The dependencies should be modified a second time, if
// they are in the same way they'll match. If they are not modified in the
// same way, then it'll correctly rerender.
if (module._dependencyBlock) {
f.dependencyBlock = module._dependencyBlock;
}
return f;
}
return frozen;
},
);
pluginCompat.tap(
compiler,
'_hardSourceThawModule',
'TransformNormalModulePlugin thaw',
(module, frozen, { compilation, normalModuleFactory }) => {
if (frozen.type === 'NormalModule') {
let m;
if (module === null) {
let serialModule = serialNormalModule;
if (!frozen.build || !frozen.build.built) {
serialModule = serialNormalModule4PreBuild;
}
m = serialModule.thaw(
null,
frozen,
{
state: { imports: {} },
compilation: compilation,
normalModuleFactory: normalModuleFactory,
},
_methods,
);
} else {
m = serialNormalModule4PostBuild.thaw(
module,
frozen,
{
state: { imports: {} },
compilation: compilation,
normalModuleFactory: normalModuleFactory,
},
_methods,
);
}
m.cacheItem = frozen;
m.__hardSourceFileMd5s = compilation.__hardSourceFileMd5s;
m.__hardSourceCachedMd5s = compilation.__hardSourceCachedMd5s;
m.__hardSource_missingCache = compiler.__hardSource_missingCache;
m.needRebuild = needRebuild;
// Unbuild if there is no cache. The module will be rebuilt. Not
// unbuilding will lead to double dependencies.
if (m.built && schema === 4 && !compilation.cache) {
m.unbuild();
}
// Side load into the cache if something for this identifier isn't already
// there.
else if (
m.built &&
compilation.cache &&
!compilation.cache[`m${m.identifier()}`]
) {
compilation.cache[`m${m.identifier()}`] = m;
}
return m;
}
return module;
},
);
}
}
module.exports = TransformNormalModulePlugin;