const pluginCompat = require('./util/plugin-compat'); const relateContext = require('./util/relate-context'); const { parityCacheFromCache, pushParityWriteOps } = require('./util/parity'); const relateNormalPath = relateContext.relateNormalPath; function relateNormalRequest(compiler, key) { return key .split('!') .map(subkey => relateNormalPath(compiler, subkey)) .join('!'); } function relateNormalModuleId(compiler, id) { return id.substring(0, 24) + relateNormalRequest(compiler, id.substring(24)); } class ModuleCache { apply(compiler) { const compilerHooks = pluginCompat.hooks(compiler); let moduleCache = {}; let parityCache = {}; const moduleArchetypeCache = { _ops: [], get(id) { if (moduleCache[id] && !moduleCache[id].invalid) { if (typeof moduleCache[id] === 'string') { moduleCache[id] = JSON.parse(moduleCache[id]); } return moduleCache[id]; } }, set(id, item) { moduleCache[id] = item; if (item) { this._ops.push(id); } else if (moduleCache[id]) { if (typeof moduleCache[id] === 'string') { moduleCache[id] = JSON.parse(moduleCache[id]); } moduleCache[id].invalid = true; moduleCache[id].invalidReason = 'overwritten'; this._ops.push(id); } }, operations() { const _this = this; const ops = this._ops.map(id => ({ key: relateNormalModuleId(compiler, id), value: _this.get(id) || null, })); this._ops.length = 0; return ops; }, }; compilerHooks._hardSourceArchetypeRegister.call( 'Module', moduleArchetypeCache, ); let moduleCacheSerializer; compilerHooks._hardSourceCreateSerializer.tap( 'HardSource - ModuleCache', (cacheSerializerFactory, cacheDirPath) => { moduleCacheSerializer = cacheSerializerFactory.create({ name: 'module', type: 'data', cacheDirPath, autoParse: true, }); }, ); compilerHooks._hardSourceResetCache.tap('HardSource - ModuleCache', () => { moduleCache = {}; }); compilerHooks._hardSourceReadCache.tapPromise( 'HardSource - ModuleCache', ({ contextKeys, contextNormalModuleId, copyWithDeser }) => moduleCacheSerializer .read() .then(_moduleCache => { Object.keys(_moduleCache).forEach(key => { if (key.startsWith('__hardSource_parityToken')) { parityCache[key] = _moduleCache[key]; delete _moduleCache[key]; } }); return _moduleCache; }) .then(contextKeys(compiler, contextNormalModuleId)) .then(copyWithDeser.bind(null, moduleCache)), ); compilerHooks._hardSourceParityCache.tap( 'HardSource - ModuleCache', parityRoot => { parityCacheFromCache('Module', parityRoot, parityCache); }, ); compilerHooks.compilation.tap('HardSource - ModuleCache', compilation => { compilation.__hardSourceModuleCache = moduleCache; }); compilerHooks._hardSourceWriteCache.tapPromise( 'HardSource - ModuleCache', compilation => { const moduleOps = moduleArchetypeCache.operations(); if (!compilation.compiler.parentCompilation) { // Add ops to remove no longer valid modules. If they were replaced with a // up to date module, they will already have replaced this item so we // won't accidentally delete up to date modules. Object.keys(moduleCache).forEach(key => { const cacheItem = moduleCache[key]; if (cacheItem && cacheItem.invalid) { // console.log('invalid', cacheItem.invalidReason); moduleCache[key] = null; moduleOps.push({ key, value: null, }); } }); } pushParityWriteOps(compilation, moduleOps); return moduleCacheSerializer.write(moduleOps); }, ); } } module.exports = ModuleCache;