hard-source-webpack-plugin
Version:
Hard cache the source of modules in webpack.
409 lines (374 loc) • 13.1 kB
JavaScript
const nodeObjectHash = require('node-object-hash');
const parseJson = require('parse-json');
const serial = require('./util/serial');
const pluginCompat = require('./util/plugin-compat');
const relateContext = require('./util/relate-context');
const { parityCacheFromCache, pushParityWriteOps } = require('./util/parity');
const serialJsonKey = {
freeze(arg, value, extra) {
return JSON.parse(arg);
},
thaw(arg, frozen, extra) {
return JSON.stringify(arg);
},
};
const serialJson = {
freeze(arg, value, extra) {
return JSON.stringify(arg);
},
thaw(arg, frozen, extra) {
return JSON.parse(arg);
},
};
const serialObjectAssign = serial.objectAssign;
const serialResolveOptionsKey = serialObjectAssign({
context: serial.path,
userRequest: serial.request,
options: serialObjectAssign({
request: serial.request,
}),
});
const serialResolveKey = serialObjectAssign({
context: serial.path,
request: serial.request,
});
const serialNormalModuleResolveKey = serial.pipe(
serialJsonKey,
{
freeze(arg, key, extra) {
if (Array.isArray(arg)) {
return [
arg[0],
serial.path.freeze(arg[1], arg[1], extra),
serial.request.freeze(arg[2], arg[2], extra),
];
} else if (!arg.request) {
return serialResolveOptionsKey.freeze(arg, arg, extra);
} else {
return serialResolveKey.freeze(arg, arg, extra);
}
},
thaw(arg, frozen, extra) {
if (Array.isArray(arg)) {
return [
arg[0],
serial.path.thaw(arg[1], arg[1], extra),
serial.request.thaw(arg[2], arg[2], extra),
];
} else if (!arg.request) {
return serialResolveOptionsKey.thaw(arg, arg, extra);
} else {
return serialResolveKey.thaw(arg, arg, extra);
}
},
},
serialJson,
);
const serialNormalModuleId = {
freeze(arg, module, extra) {
return (
id.substring(0, 24) + serial.request.freeze(id.substring(24), id, extra)
);
},
thaw(arg, frozen, extra) {
return (
id.substring(0, 24) + serial.request.thaw(id.substring(24), id, extra)
);
},
};
const serialResolveContext = serialObjectAssign({
identifier: serialNormalModuleId,
resource: serialNormalModuleId,
});
const serialResolveNormal = serialObjectAssign({
context: serial.path,
request: serial.request,
userRequest: serial.request,
rawRequest: serial.request,
resource: serial.request,
loaders: serial.loaders,
resourceResolveData: serial.objectAssign({
context: serial.created({
issuer: serial.request,
resolveOptions: serial.identity,
}),
path: serial.path,
request: serial.request,
descriptionFilePath: serial.path,
descriptionFileRoot: serial.path,
}),
});
const serialResolve = {
freeze(arg, module, extra) {
if (arg.type === 'context') {
return serialResolveContext.freeze(arg, arg, extra);
}
return serialResolveNormal.freeze(arg, arg, extra);
},
thaw(arg, frozen, extra) {
if (arg.type === 'context') {
return serialResolveContext.thaw(arg, arg, extra);
}
return serialResolveNormal.thaw(arg, arg, extra);
},
};
class ModuleResolverCache {
apply(compiler) {
let moduleResolveCache = {};
let parityCache = {};
let moduleResolveCacheChange = [];
let moduleResolveCacheSerializer;
const compilerHooks = pluginCompat.hooks(compiler);
compilerHooks._hardSourceCreateSerializer.tap(
'HardSource - ModuleResolverCache',
(cacheSerializerFactory, cacheDirPath) => {
moduleResolveCacheSerializer = cacheSerializerFactory.create({
name: 'module-resolve',
type: 'data',
autoParse: true,
cacheDirPath,
});
},
);
compilerHooks._hardSourceResetCache.tap(
'HardSource - ModuleResolverCache',
() => {
moduleResolveCache = {};
parityCache = {};
},
);
compilerHooks._hardSourceReadCache.tapPromise(
'HardSource - ModuleResolverCache',
({
contextKeys,
contextValues,
contextNormalPath,
contextNormalRequest,
contextNormalModuleId,
copyWithDeser,
}) => {
function contextNormalModuleResolveKey(compiler, key) {
if (key.startsWith('__hardSource_parityToken')) {
return key;
}
const parsed = parseJson(key);
if (Array.isArray(parsed)) {
return JSON.stringify([
parsed[0],
contextNormalPath(compiler, parsed[1]),
parsed[2],
]);
} else {
return JSON.stringify(
Object.assign({}, parsed, {
context: contextNormalPath(compiler, parsed.context),
}),
);
}
}
function contextNormalModuleResolve(compiler, resolved, key) {
if (key.startsWith('__hardSource_parityToken')) {
parityCache[key] = resolved;
return;
}
if (typeof resolved === 'string') {
resolved = parseJson(resolved);
}
if (resolved.type === 'context') {
return Object.assign({}, resolved, {
identifier: contextNormalModuleId(compiler, resolved.identifier),
resource: contextNormalRequest(compiler, resolved.resource),
});
}
return serialResolveNormal.thaw(resolved, resolved, {
compiler,
});
}
return moduleResolveCacheSerializer
.read()
.then(contextKeys(compiler, contextNormalModuleResolveKey))
.then(contextValues(compiler, contextNormalModuleResolve))
.then(copyWithDeser.bind(null, moduleResolveCache));
},
);
compilerHooks._hardSourceParityCache.tap(
'HardSource - ModuleResolverCache',
parityRoot => {
parityCacheFromCache('ModuleResolver', parityRoot, parityCache);
},
);
compilerHooks._hardSourceVerifyCache.tapPromise(
'HardSource - ModuleResolverCache',
() =>
compiler.__hardSource_missingVerify.then(() => {
const missingCache = compiler.__hardSource_missingCache;
// Invalidate resolve cache items.
Object.keys(moduleResolveCache).forEach(key => {
const resolveKey = parseJson(key);
const resolveItem = moduleResolveCache[key];
let normalId = 'normal';
if (resolveItem.resolveOptions) {
normalId = `normal-${new nodeObjectHash({ sort: false }).hash(
resolveItem.resolveOptions,
)}`;
}
if (resolveItem.type === 'context') {
const contextMissing =
missingCache.context[
JSON.stringify([
resolveKey.context,
resolveItem.resource.split('?')[0],
])
];
if (!contextMissing || contextMissing.invalid) {
resolveItem.invalid = true;
resolveItem.invalidReason = 'resolved context invalid';
}
} else {
const normalMissing =
missingCache[normalId] &&
missingCache[normalId][
JSON.stringify([
resolveKey[1],
resolveItem.resource.split('?')[0],
])
];
if (!normalMissing || normalMissing.invalid) {
resolveItem.invalid = true;
resolveItem.invalidReason = `resolved normal invalid${
normalMissing
? ` ${normalMissing.invalidReason}`
: ': resolve entry not in cache'
}`;
}
resolveItem.loaders.forEach(loader => {
if (typeof loader === 'object') {
if (loader.loader != null) {
loader = loader.loader;
} else {
// Convert { "0": "b", "1": "a", "2": "r" } into "bar"
loader = Object.assign([], loader).join('');
}
}
// Loaders specified in a dependency are searched for from the
// context of the module containing that dependency.
let loaderMissing =
missingCache.loader[
JSON.stringify([resolveKey[1], loader.split('?')[0]])
];
if (!loaderMissing) {
// webpack searches for rule based loaders from the project
// context.
loaderMissing =
missingCache.loader[
JSON.stringify([
// compiler may be a Watching instance, which refers to the
// compiler
(compiler.options || compiler.compiler.options).context,
loader.split('?')[0],
])
];
}
if (!loaderMissing || loaderMissing.invalid) {
resolveItem.invalid = true;
resolveItem.invalidReason = 'resolved loader invalid';
}
});
}
});
}),
);
compilerHooks.compilation.tap(
'HardSource - ModuleResolverCache',
compilation => {
compilation.__hardSourceModuleResolveCache = moduleResolveCache;
compilation.__hardSourceModuleResolveCacheChange = moduleResolveCacheChange;
},
);
compilerHooks._hardSourceWriteCache.tapPromise(
'HardSource - ModuleResolverCache',
(
compilation,
{ relateNormalPath, relateNormalModuleId, relateNormalRequest },
) => {
if (compilation.compiler.parentCompilation) {
const moduleResolveOps = [];
pushParityWriteOps(compilation, moduleResolveOps);
return moduleResolveCacheSerializer.write(moduleResolveOps);
}
const moduleResolveOps = [];
function relateNormalModuleResolveKey(compiler, key) {
const parsed = parseJson(key);
if (Array.isArray(parsed)) {
return JSON.stringify([
parsed[0],
relateNormalPath(compiler, parsed[1]),
relateContext.relateAbsoluteRequest(parsed[1], parsed[2]),
]);
} else {
if (!parsed.request) {
return JSON.stringify(
Object.assign({}, parsed, {
context: relateNormalPath(compiler, parsed.context),
userRequest: relateContext.relateAbsoluteRequest(
parsed.context,
parsed.userRequest,
),
options: Object.assign({}, parsed.options, {
request: relateContext.relateAbsoluteRequest(
parsed.context,
parsed.options.request,
),
}),
}),
);
} else {
return JSON.stringify(
Object.assign({}, parsed, {
context: relateNormalPath(compiler, parsed.context),
request: relateContext.relateAbsoluteRequest(
parsed.context,
parsed.request,
),
}),
);
}
}
}
function relateNormalModuleResolve(compiler, resolved) {
if (resolved.type === 'context') {
return Object.assign({}, resolved, {
identifier: relateNormalModuleId(compiler, resolved.identifier),
resource: relateNormalRequest(compiler, resolved.resource),
});
}
return serialResolveNormal.freeze(resolved, resolved, {
compiler,
});
}
moduleResolveCacheChange
.reduce((carry, value) => {
if (!carry.includes(value)) {
carry.push(value);
}
return carry;
}, [])
.forEach(key => {
// console.log(key, moduleResolveCache[key]);
// moduleResolveCache[key] && console.log(relateNormalModuleResolveKey(compiler, key));
// moduleResolveCache[key] && console.log(relateNormalModuleResolve(compiler, moduleResolveCache[key]));
moduleResolveOps.push({
key: relateNormalModuleResolveKey(compiler, key),
value: moduleResolveCache[key]
? relateNormalModuleResolve(compiler, moduleResolveCache[key])
: null,
});
});
moduleResolveCacheChange = [];
pushParityWriteOps(compilation, moduleResolveOps);
return moduleResolveCacheSerializer.write(moduleResolveOps);
},
);
}
}
module.exports = ModuleResolverCache;