Cache individual programmatic descriptors along with the overall list. (#8494)

This commit is contained in:
Logan Smyth 2018-08-20 10:08:21 -07:00 committed by GitHub
parent c2a2e24965
commit 3a399d1eb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 112 additions and 8 deletions

View File

@ -41,6 +41,22 @@ export type UnloadedDescriptor = {
} | void, } | void,
}; };
function isEqualDescriptor(
a: UnloadedDescriptor,
b: UnloadedDescriptor,
): boolean {
return (
a.name === b.name &&
a.value === b.value &&
a.options === b.options &&
a.dirname === b.dirname &&
a.alias === b.alias &&
a.ownPass === b.ownPass &&
(a.file && a.file.request) === (b.file && b.file.request) &&
(a.file && a.file.resolved) === (b.file && b.file.resolved)
);
}
export type ValidatedFile = { export type ValidatedFile = {
filepath: string, filepath: string,
dirname: string, dirname: string,
@ -50,7 +66,7 @@ export type ValidatedFile = {
/** /**
* Create a set of descriptors from a given options object, preserving * Create a set of descriptors from a given options object, preserving
* descriptor identity based on the identity of the plugin/preset arrays * descriptor identity based on the identity of the plugin/preset arrays
* themselves. * themselves, and potentially on the identity of the plugins/presets + options.
*/ */
export function createCachedDescriptors( export function createCachedDescriptors(
dirname: string, dirname: string,
@ -113,26 +129,82 @@ export function createUncachedDescriptors(
}; };
} }
const PRESET_DESCRIPTOR_CACHE = new WeakMap();
const createCachedPresetDescriptors = makeWeakCache( const createCachedPresetDescriptors = makeWeakCache(
(items: PluginList, cache: CacheConfigurator<string>) => { (items: PluginList, cache: CacheConfigurator<string>) => {
const dirname = cache.using(dir => dir); const dirname = cache.using(dir => dir);
return makeStrongCache((alias: string) => return makeStrongCache((alias: string) =>
makeStrongCache((passPerPreset: boolean) => makeStrongCache((passPerPreset: boolean) =>
createPresetDescriptors(items, dirname, alias, passPerPreset), createPresetDescriptors(items, dirname, alias, passPerPreset).map(
// Items are cached using the overall preset array identity when
// possibly, but individual descriptors are also cached if a match
// can be found in the previously-used descriptor lists.
desc => loadCachedDescriptor(PRESET_DESCRIPTOR_CACHE, desc),
),
), ),
); );
}, },
); );
const PLUGIN_DESCRIPTOR_CACHE = new WeakMap();
const createCachedPluginDescriptors = makeWeakCache( const createCachedPluginDescriptors = makeWeakCache(
(items: PluginList, cache: CacheConfigurator<string>) => { (items: PluginList, cache: CacheConfigurator<string>) => {
const dirname = cache.using(dir => dir); const dirname = cache.using(dir => dir);
return makeStrongCache((alias: string) => return makeStrongCache((alias: string) =>
createPluginDescriptors(items, dirname, alias), createPluginDescriptors(items, dirname, alias).map(
// Items are cached using the overall plugin array identity when
// possibly, but individual descriptors are also cached if a match
// can be found in the previously-used descriptor lists.
desc => loadCachedDescriptor(PLUGIN_DESCRIPTOR_CACHE, desc),
),
); );
}, },
); );
/**
* When no options object is given in a descriptor, this object is used
* as a WeakMap key in order to have consistent identity.
*/
const DEFAULT_OPTIONS = {};
/**
* Given the cache and a descriptor, returns a matching descriptor from the
* cache, or else returns the input descriptor and adds it to the cache for
* next time.
*/
function loadCachedDescriptor(
cache: WeakMap<{} | Function, WeakMap<{}, Array<UnloadedDescriptor>>>,
desc: UnloadedDescriptor,
) {
const { value, options = DEFAULT_OPTIONS } = desc;
if (options === false) return desc;
let cacheByOptions = cache.get(value);
if (!cacheByOptions) {
cacheByOptions = new WeakMap();
cache.set(value, cacheByOptions);
}
let possibilities = cacheByOptions.get(options);
if (!possibilities) {
possibilities = [];
cacheByOptions.set(options, possibilities);
}
if (possibilities.indexOf(desc) === -1) {
const matches = possibilities.filter(possibility =>
isEqualDescriptor(possibility, desc),
);
if (matches.length > 0) {
return matches[0];
}
possibilities.push(desc);
}
return desc;
}
function createPresetDescriptors( function createPresetDescriptors(
items: PluginList, items: PluginList,
dirname: string, dirname: string,

View File

@ -29,10 +29,10 @@ describe("@babel/core config loading", () => {
filename: FILEPATH, filename: FILEPATH,
presets: skipProgrammatic presets: skipProgrammatic
? null ? null
: [require("./fixtures/config-loading/preset3")], : [[require("./fixtures/config-loading/preset3"), {}]],
plugins: skipProgrammatic plugins: skipProgrammatic
? null ? null
: [require("./fixtures/config-loading/plugin6")], : [[require("./fixtures/config-loading/plugin6"), {}]],
}; };
} }
@ -213,7 +213,7 @@ describe("@babel/core config loading", () => {
} }
}); });
it("should invalidate the plugins when given a fresh arrays", () => { it("should not invalidate the plugins when given a fresh arrays", () => {
const opts = makeOpts(); const opts = makeOpts();
const options1 = loadConfig(opts).options; const options1 = loadConfig(opts).options;
@ -224,6 +224,38 @@ describe("@babel/core config loading", () => {
}).options; }).options;
expect(options2.plugins.length).toBe(options1.plugins.length); expect(options2.plugins.length).toBe(options1.plugins.length);
for (let i = 0; i < options2.plugins.length; i++) {
expect(options2.plugins[i]).toBe(options1.plugins[i]);
}
});
it("should not invalidate the presets when given a fresh arrays", () => {
const opts = makeOpts();
const options1 = loadConfig(opts).options;
const options2 = loadConfig({
...opts,
presets: opts.presets.slice(),
}).options;
expect(options2.plugins.length).toBe(options1.plugins.length);
for (let i = 0; i < options2.plugins.length; i++) {
expect(options2.plugins[i]).toBe(options1.plugins[i]);
}
});
it("should invalidate the plugins when given a fresh options", () => {
const opts = makeOpts();
const options1 = loadConfig(opts).options;
const options2 = loadConfig({
...opts,
plugins: opts.plugins.map(([plg, opt]) => [plg, { ...opt }]),
}).options;
expect(options2.plugins.length).toBe(options1.plugins.length);
for (let i = 0; i < options2.plugins.length; i++) { for (let i = 0; i < options2.plugins.length; i++) {
if (i === 2) { if (i === 2) {
expect(options2.plugins[i]).not.toBe(options1.plugins[i]); expect(options2.plugins[i]).not.toBe(options1.plugins[i]);
@ -233,14 +265,14 @@ describe("@babel/core config loading", () => {
} }
}); });
it("should invalidate the presets when given a fresh arrays", () => { it("should invalidate the presets when given a fresh options", () => {
const opts = makeOpts(); const opts = makeOpts();
const options1 = loadConfig(opts).options; const options1 = loadConfig(opts).options;
const options2 = loadConfig({ const options2 = loadConfig({
...opts, ...opts,
presets: opts.presets.slice(), presets: opts.presets.map(([plg, opt]) => [plg, { ...opt }]),
}).options; }).options;
expect(options2.plugins.length).toBe(options1.plugins.length); expect(options2.plugins.length).toBe(options1.plugins.length);