123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- const path = require('path');
- const lodash = require('lodash');
- const nodeObjectHash = require('node-object-hash');
- const parseJson = require('parse-json');
- const pluginCompat = require('./util/plugin-compat');
- const promisify = require('./util/promisify');
- const relateContext = require('./util/relate-context');
- const serial = require('./util/serial');
- const values = require('./util/Object.values');
- const bulkFsTask = require('./util/bulk-fs-task');
- const { parityCacheFromCache, pushParityWriteOps } = require('./util/parity');
- const serialNormalResolved = serial.created({
- result: serial.path,
- resourceResolveData: serial.objectAssign({
- context: serial.created({
- issuer: serial.request,
- resolveOptions: serial.identity,
- }),
- path: serial.path,
- descriptionFilePath: serial.path,
- descriptionFileRoot: serial.path,
- }),
- });
- class EnhancedResolveCache {
- apply(compiler) {
- let missingCacheSerializer;
- let resolverCacheSerializer;
- let missingCache = { normal: {}, loader: {}, context: {} };
- let resolverCache = { normal: {}, loader: {}, context: {} };
- let parityCache = {};
- const compilerHooks = pluginCompat.hooks(compiler);
- compilerHooks._hardSourceCreateSerializer.tap(
- 'HardSource - EnhancedResolveCache',
- (cacheSerializerFactory, cacheDirPath) => {
- missingCacheSerializer = cacheSerializerFactory.create({
- name: 'missing-resolve',
- type: 'data',
- autoParse: true,
- cacheDirPath,
- });
- resolverCacheSerializer = cacheSerializerFactory.create({
- name: 'resolver',
- type: 'data',
- autoParse: true,
- cacheDirPath,
- });
- },
- );
- compilerHooks._hardSourceResetCache.tap(
- 'HardSource - EnhancedResolveCache',
- () => {
- missingCache = { normal: {}, loader: {}, context: {} };
- resolverCache = { normal: {}, loader: {}, context: {} };
- parityCache = {};
- compiler.__hardSource_missingCache = missingCache;
- },
- );
- compilerHooks._hardSourceReadCache.tapPromise(
- 'HardSource - EnhancedResolveCache',
- ({ contextNormalPath, contextNormalRequest }) => {
- return Promise.all([
- missingCacheSerializer.read().then(_missingCache => {
- missingCache = { normal: {}, loader: {}, context: {} };
- compiler.__hardSource_missingCache = missingCache;
- function contextNormalMissingKey(compiler, key) {
- const parsed = parseJson(key);
- return JSON.stringify([
- contextNormalPath(compiler, parsed[0]),
- contextNormalPath(compiler, parsed[1]),
- ]);
- }
- function contextNormalMissing(compiler, missing) {
- return missing.map(missed =>
- contextNormalRequest(compiler, missed),
- );
- }
- Object.keys(_missingCache).forEach(key => {
- let item = _missingCache[key];
- if (typeof item === 'string') {
- item = parseJson(item);
- }
- const splitIndex = key.indexOf('/');
- const group = key.substring(0, splitIndex);
- const keyName = contextNormalMissingKey(
- compiler,
- key.substring(splitIndex + 1),
- );
- missingCache[group] = missingCache[group] || {};
- missingCache[group][keyName] = contextNormalMissing(
- compiler,
- item,
- );
- });
- }),
- resolverCacheSerializer.read().then(_resolverCache => {
- resolverCache = { normal: {}, loader: {}, context: {} };
- parityCache = {};
- function contextNormalResolvedKey(compiler, key) {
- const parsed = parseJson(key);
- return JSON.stringify([
- contextNormalPath(compiler, parsed[0]),
- parsed[1],
- ]);
- }
- function contextNormalResolved(compiler, resolved) {
- return serialNormalResolved.thaw(resolved, resolved, {
- compiler,
- });
- }
- Object.keys(_resolverCache).forEach(key => {
- let item = _resolverCache[key];
- if (typeof item === 'string') {
- item = parseJson(item);
- }
- if (key.startsWith('__hardSource_parityToken')) {
- parityCache[key] = item;
- return;
- }
- const splitIndex = key.indexOf('/');
- const group = key.substring(0, splitIndex);
- const keyName = contextNormalResolvedKey(
- compiler,
- key.substring(splitIndex + 1),
- );
- resolverCache[group] = resolverCache[group] || {};
- resolverCache[group][keyName] = contextNormalResolved(
- compiler,
- item,
- );
- });
- }),
- ]);
- },
- );
- compilerHooks._hardSourceParityCache.tap(
- 'HardSource - EnhancedResolveCache',
- parityRoot => {
- parityCacheFromCache('EnhancedResolve', parityRoot, parityCache);
- },
- );
- let missingVerifyResolve;
- compiler.__hardSource_missingVerify = new Promise(resolve => {
- missingVerifyResolve = resolve;
- });
- compilerHooks._hardSourceVerifyCache.tapPromise(
- 'HardSource - EnhancedResolveCache',
- () =>
- (() => {
- compiler.__hardSource_missingVerify = new Promise(resolve => {
- missingVerifyResolve = resolve;
- });
- const bulk = lodash.flatten(
- Object.keys(missingCache).map(group =>
- lodash.flatten(
- Object.keys(missingCache[group])
- .map(key => {
- const missingItem = missingCache[group][key];
- if (!missingItem) {
- return;
- }
- return missingItem.map((missed, index) => [
- group,
- key,
- missed,
- index,
- ]);
- })
- .filter(Boolean),
- ),
- ),
- );
- return bulkFsTask(bulk, (item, task) => {
- const group = item[0];
- const key = item[1];
- const missingItem = missingCache[group][key];
- const missed = item[2];
- const missedPath = missed.split('?')[0];
- const missedIndex = item[3];
- // The missed index is the resolved item. Invalidate if it does not
- // exist.
- if (missedIndex === missingItem.length - 1) {
- compiler.inputFileSystem.stat(
- missed,
- task((err, stat) => {
- if (err) {
- missingItem.invalid = true;
- missingItem.invalidReason = 'resolved now missing';
- }
- }),
- );
- } else {
- compiler.inputFileSystem.stat(
- missed,
- task((err, stat) => {
- if (err) {
- return;
- }
- if (stat.isDirectory()) {
- if (group === 'context') {
- missingItem.invalid = true;
- }
- }
- if (stat.isFile()) {
- if (group === 'loader' || group.startsWith('normal')) {
- missingItem.invalid = true;
- missingItem.invalidReason = 'missing now found';
- }
- }
- }),
- );
- }
- });
- })().then(missingVerifyResolve),
- );
- function bindResolvers() {
- function configureMissing(key, resolver) {
- // missingCache[key] = missingCache[key] || {};
- // resolverCache[key] = resolverCache[key] || {};
- const _resolve = resolver.resolve;
- resolver.resolve = function(info, context, request, cb, cb2) {
- let numArgs = 4;
- if (!cb) {
- numArgs = 3;
- cb = request;
- request = context;
- context = info;
- }
- let resolveContext;
- if (cb2) {
- numArgs = 5;
- resolveContext = cb;
- cb = cb2;
- }
- if (info && info.resolveOptions) {
- key = `normal-${new nodeObjectHash({ sort: false }).hash(
- info.resolveOptions,
- )}`;
- resolverCache[key] = resolverCache[key] || {};
- missingCache[key] = missingCache[key] || {};
- }
- const resolveId = JSON.stringify([context, request]);
- const absResolveId = JSON.stringify([
- context,
- relateContext.relateAbsolutePath(context, request),
- ]);
- const resolve =
- resolverCache[key][resolveId] || resolverCache[key][absResolveId];
- if (resolve && !resolve.invalid) {
- const missingId = JSON.stringify([context, resolve.result]);
- const missing = missingCache[key][missingId];
- if (missing && !missing.invalid) {
- return cb(
- null,
- [resolve.result].concat(request.split('?').slice(1)).join('?'),
- resolve.resourceResolveData,
- );
- } else {
- resolve.invalid = true;
- resolve.invalidReason = 'out of date';
- }
- }
- let localMissing = [];
- const callback = (err, result, result2) => {
- if (result) {
- const inverseId = JSON.stringify([context, result.split('?')[0]]);
- const resolveId = JSON.stringify([context, request]);
- // Skip recording missing for any dependency in node_modules.
- // Changes to them will be handled by the environment hash. If we
- // tracked the stuff in node_modules too, we'd be adding a whole
- // bunch of reduntant work.
- if (result.includes('node_modules')) {
- localMissing = localMissing.filter(
- missed => !missed.includes('node_modules'),
- );
- }
- // In case of other cache layers, if we already have missing
- // recorded and we get a new empty array of missing, keep the old
- // value.
- if (localMissing.length === 0 && missingCache[key][inverseId]) {
- return cb(err, result, result2);
- }
- missingCache[key][inverseId] = localMissing
- .filter((missed, missedIndex) => {
- const index = localMissing.indexOf(missed);
- if (index === -1 || index < missedIndex) {
- return false;
- }
- if (missed === result) {
- return false;
- }
- return true;
- })
- .concat(result.split('?')[0]);
- missingCache[key][inverseId].new = true;
- resolverCache[key][resolveId] = {
- result: result.split('?')[0],
- resourceResolveData: result2,
- new: true,
- };
- }
- cb(err, result, result2);
- };
- const _missing =
- cb.missing || (resolveContext && resolveContext.missing);
- if (_missing) {
- callback.missing = {
- push(path) {
- localMissing.push(path);
- _missing.push(path);
- },
- add(path) {
- localMissing.push(path);
- _missing.add(path);
- },
- };
- if (resolveContext) {
- resolveContext.missing = callback.missing;
- }
- } else {
- callback.missing = Object.assign(localMissing, {
- add(path) {
- localMissing.push(path);
- },
- });
- if (resolveContext) {
- resolveContext.missing = callback.missing;
- }
- }
- if (numArgs === 3) {
- _resolve.call(this, context, request, callback);
- } else if (numArgs === 5) {
- _resolve.call(
- this,
- info,
- context,
- request,
- resolveContext,
- callback,
- );
- } else {
- _resolve.call(this, info, context, request, callback);
- }
- };
- }
- if (compiler.resolverFactory) {
- compiler.resolverFactory.hooks.resolver
- .for('normal')
- .tap('HardSource resolve cache', (resolver, options) => {
- const normalCacheId = `normal-${new nodeObjectHash({
- sort: false,
- }).hash(Object.assign({}, options, { fileSystem: null }))}`;
- resolverCache[normalCacheId] = resolverCache[normalCacheId] || {};
- missingCache[normalCacheId] = missingCache[normalCacheId] || {};
- configureMissing(normalCacheId, resolver);
- return resolver;
- });
- compiler.resolverFactory.hooks.resolver
- .for('loader')
- .tap('HardSource resolve cache', resolver => {
- configureMissing('loader', resolver);
- return resolver;
- });
- compiler.resolverFactory.hooks.resolver
- .for('context')
- .tap('HardSource resolve cache', resolver => {
- configureMissing('context', resolver);
- return resolver;
- });
- } else {
- configureMissing('normal', compiler.resolvers.normal);
- configureMissing('loader', compiler.resolvers.loader);
- configureMissing('context', compiler.resolvers.context);
- }
- }
- compilerHooks.afterPlugins.tap('HardSource - EnhancedResolveCache', () => {
- if (compiler.resolvers.normal) {
- bindResolvers();
- } else {
- compilerHooks.afterResolvers.tap(
- 'HardSource - EnhancedResolveCache',
- bindResolvers,
- );
- }
- });
- compilerHooks._hardSourceWriteCache.tapPromise(
- 'HardSource - EnhancedResolveCache',
- (compilation, { relateNormalPath, relateNormalRequest }) => {
- if (compilation.compiler.parentCompilation) {
- const resolverOps = [];
- pushParityWriteOps(compilation, resolverOps);
- return resolverCacheSerializer.write(resolverOps);
- }
- const missingOps = [];
- const resolverOps = [];
- function relateNormalMissingKey(compiler, key) {
- const parsed = parseJson(key);
- return JSON.stringify([
- relateNormalPath(compiler, parsed[0]),
- relateNormalPath(compiler, parsed[1]),
- ]);
- }
- function relateNormalMissing(compiler, missing) {
- return missing.map(missed => relateNormalRequest(compiler, missed));
- }
- Object.keys(missingCache).forEach(group => {
- Object.keys(missingCache[group]).forEach(key => {
- if (!missingCache[group][key]) {
- return;
- }
- if (missingCache[group][key].new) {
- missingCache[group][key].new = false;
- missingOps.push({
- key: `${group}/${relateNormalMissingKey(compiler, key)}`,
- value: JSON.stringify(
- relateNormalMissing(compiler, missingCache[group][key]),
- ),
- });
- } else if (missingCache[group][key].invalid) {
- missingCache[group][key] = null;
- missingOps.push({
- key: `${group}/${relateNormalMissingKey(compiler, key)}`,
- value: null,
- });
- }
- });
- });
- function relateNormalResolvedKey(compiler, key) {
- const parsed = parseJson(key);
- return JSON.stringify([
- relateNormalPath(compiler, parsed[0]),
- relateContext.relateAbsolutePath(parsed[0], parsed[1]),
- ]);
- }
- function relateNormalResolved(compiler, resolved) {
- return serialNormalResolved.freeze(resolved, resolved, {
- compiler,
- });
- }
- Object.keys(resolverCache).forEach(group => {
- Object.keys(resolverCache[group]).forEach(key => {
- if (!resolverCache[group][key]) {
- return;
- }
- if (resolverCache[group][key].new) {
- resolverCache[group][key].new = false;
- resolverOps.push({
- key: `${group}/${relateNormalResolvedKey(compiler, key)}`,
- value: JSON.stringify(
- relateNormalResolved(compiler, resolverCache[group][key]),
- ),
- });
- } else if (resolverCache[group][key].invalid) {
- resolverCache[group][key] = null;
- resolverOps.push({
- key: `${group}/${relateNormalResolvedKey(compiler, key)}`,
- value: null,
- });
- }
- });
- });
- pushParityWriteOps(compilation, resolverOps);
- return Promise.all([
- missingCacheSerializer.write(missingOps),
- resolverCacheSerializer.write(resolverOps),
- ]);
- },
- );
- }
- }
- module.exports = EnhancedResolveCache;
|