From 3942862a2aa4de13959ccc9ce45da0a68142f853 Mon Sep 17 00:00:00 2001 From: Logan Smyth Date: Tue, 14 Nov 2017 17:19:38 -0800 Subject: [PATCH] Expose more caching flexibility for Babel's internal use. --- packages/babel-core/src/config/caching.js | 311 ++++++++++-------- .../src/config/loading/files/configuration.js | 2 +- .../babel-core/src/config/option-manager.js | 2 +- packages/babel-core/test/caching-api.js | 162 ++++----- 4 files changed, 254 insertions(+), 223 deletions(-) diff --git a/packages/babel-core/src/config/caching.js b/packages/babel-core/src/config/caching.js index bd12bc31ac..1f409fa7ea 100644 --- a/packages/babel-core/src/config/caching.js +++ b/packages/babel-core/src/config/caching.js @@ -1,12 +1,13 @@ // @flow -type CacheConfigurator = CacheConfiguratorFn & CacheConfiguratorObj; +type SimpleCacheConfigurator = SimpleCacheConfiguratorFn & + SimpleCacheConfiguratorObj; -type CacheConfiguratorFn = { +type SimpleCacheConfiguratorFn = { (boolean): void, (handler: () => T): T, }; -type CacheConfiguratorObj = { +type SimpleCacheConfiguratorObj = { forever: () => void, never: () => void, using: (handler: () => T) => T, @@ -56,165 +57,187 @@ function makeCachedFunction>( } } - const { cache, result, deactivate } = makeCacheConfig(); + const cache = new CacheConfigurator(); const value = handler(arg, cache); - if (autoPermacache && !result.configured) cache.forever(); + if (autoPermacache && !cache.configured()) cache.forever(); - deactivate(); + cache.deactivate(); - if (!result.configured) { - // eslint-disable-next-line max-len - throw new Error( - [ - "Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured", - "for various types of caching, using the first param of their handler functions:", - "", - "module.exports = function(api) {", - " // The API exposes the following:", - "", - " // Cache the returned value forever and don't call this function again.", - " api.cache(true);", - "", - " // Don't cache at all. Not recommended because it will be very slow.", - " api.cache(false);", - "", - " // Cached based on the value of some function. If this function returns a value different from", - " // a previously-encountered value, the plugins will re-evaluate.", - " var env = api.cache(() => process.env.NODE_ENV);", - "", - " // If testing for a specific env, we recommend specifics to avoid instantiating a plugin for", - " // any possible NODE_ENV value that might come up during plugin execution.", - ' var isProd = api.cache(() => process.env.NODE_ENV === "production");', - "", - " // .cache(fn) will perform a linear search though instances to find the matching plugin based", - " // based on previous instantiated plugins. If you want to recreate the plugin and discard the", - " // previous instance whenever something changes, you may use:", - ' var isProd = api.cache.invalidate(() => process.env.NODE_ENV === "production");', - "", - " // Note, we also expose the following more-verbose versions of the above examples:", - " api.cache.forever(); // api.cache(true)", - " api.cache.never(); // api.cache(false)", - " api.cache.using(fn); // api.cache(fn)", - "", - " // Return the value that will be cached.", - " return { };", - "};", - ].join("\n"), - ); - } + cache.assertConfigured(); - if (!result.never) { - if (result.forever) { + switch (cache.mode()) { + case "forever": cachedValue = [[value, () => true]]; - } else if (result.invalidate) { - cachedValue = [[value, result.valid]]; - } else { - cachedValue = cachedValue || []; - cachedValue.push([value, result.valid]); - } - callCache.set(arg, cachedValue); + callCache.set(arg, cachedValue); + break; + case "invalidate": + cachedValue = [[value, cache.validator()]]; + callCache.set(arg, cachedValue); + break; + case "valid": + if (cachedValue) { + cachedValue.push([value, cache.validator()]); + } else { + cachedValue = [[value, cache.validator()]]; + callCache.set(arg, cachedValue); + } } return value; }; } -function makeCacheConfig(): { - cache: CacheConfigurator, - result: *, - deactivate: () => void, -} { - const pairs = []; +class CacheConfigurator { + _active: boolean = true; + _never: boolean = false; + _forever: boolean = false; + _invalidate: boolean = false; - const result = { - configured: false, - never: false, - forever: false, - invalidate: false, - valid: () => pairs.every(([key, fn]) => key === fn()), - }; + _configured: boolean = false; - let active = true; - const deactivate = () => { - active = false; - }; + _pairs: Array<[mixed, () => mixed]> = []; - const cache: CacheConfigurator = Object.assign( - (function cacheFn(val) { - if (typeof val === "boolean") { - if (val) cache.forever(); - else cache.never(); - return; - } + simple() { + return makeSimpleConfigurator(this); + } - return cache.using(val); - }: any), - ({ - forever() { - if (!active) { - throw new Error( - "Cannot change caching after evaluation has completed.", - ); - } - if (result.never) { - throw new Error("Caching has already been configured with .never()"); - } - result.forever = true; - result.configured = true; - }, - never() { - if (!active) { - throw new Error( - "Cannot change caching after evaluation has completed.", - ); - } - if (result.forever) { - throw new Error( - "Caching has already been configured with .forever()", - ); - } - result.never = true; - result.configured = true; - }, - using(handler: () => T): T { - if (!active) { - throw new Error( - "Cannot change caching after evaluation has completed.", - ); - } - if (result.never || result.forever) { - throw new Error( - "Caching has already been configured with .never or .forever()", - ); - } - result.configured = true; + mode() { + if (this._never) return "never"; + if (this._forever) return "forever"; + if (this._invalidate) return "invalidate"; + return "valid"; + } - const key = handler(); - pairs.push([key, handler]); - return key; - }, - invalidate(handler: () => T): T { - if (!active) { - throw new Error( - "Cannot change caching after evaluation has completed.", - ); - } - if (result.never || result.forever) { - throw new Error( - "Caching has already been configured with .never or .forever()", - ); - } - result.invalidate = true; - result.configured = true; + forever() { + if (!this._active) { + throw new Error("Cannot change caching after evaluation has completed."); + } + if (this._never) { + throw new Error("Caching has already been configured with .never()"); + } + this._forever = true; + this._configured = true; + } - const key = handler(); - pairs.push([key, handler]); - return key; - }, - }: CacheConfiguratorObj), - ); + never() { + if (!this._active) { + throw new Error("Cannot change caching after evaluation has completed."); + } + if (this._forever) { + throw new Error("Caching has already been configured with .forever()"); + } + this._never = true; + this._configured = true; + } - return { cache, result, deactivate }; + using(handler: () => T): T { + if (!this._active) { + throw new Error("Cannot change caching after evaluation has completed."); + } + if (this._never || this._forever) { + throw new Error( + "Caching has already been configured with .never or .forever()", + ); + } + this._configured = true; + + const key = handler(); + this._pairs.push([key, handler]); + return key; + } + + invalidate(handler: () => T): T { + if (!this._active) { + throw new Error("Cannot change caching after evaluation has completed."); + } + if (this._never || this._forever) { + throw new Error( + "Caching has already been configured with .never or .forever()", + ); + } + this._invalidate = true; + this._configured = true; + + const key = handler(); + this._pairs.push([key, handler]); + return key; + } + + validator(): () => boolean { + const pairs = this._pairs; + return () => pairs.every(([key, fn]) => key === fn()); + } + + deactivate() { + this._active = false; + } + + configured() { + return this._configured; + } + + assertConfigured() { + if (this.configured()) return; + + // eslint-disable-next-line max-len + throw new Error( + [ + "Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured", + "for various types of caching, using the first param of their handler functions:", + "", + "module.exports = function(api) {", + " // The API exposes the following:", + "", + " // Cache the returned value forever and don't call this function again.", + " api.cache(true);", + "", + " // Don't cache at all. Not recommended because it will be very slow.", + " api.cache(false);", + "", + " // Cached based on the value of some function. If this function returns a value different from", + " // a previously-encountered value, the plugins will re-evaluate.", + " var env = api.cache(() => process.env.NODE_ENV);", + "", + " // If testing for a specific env, we recommend specifics to avoid instantiating a plugin for", + " // any possible NODE_ENV value that might come up during plugin execution.", + ' var isProd = api.cache(() => process.env.NODE_ENV === "production");', + "", + " // .cache(fn) will perform a linear search though instances to find the matching plugin based", + " // based on previous instantiated plugins. If you want to recreate the plugin and discard the", + " // previous instance whenever something changes, you may use:", + ' var isProd = api.cache.invalidate(() => process.env.NODE_ENV === "production");', + "", + " // Note, we also expose the following more-verbose versions of the above examples:", + " api.cache.forever(); // api.cache(true)", + " api.cache.never(); // api.cache(false)", + " api.cache.using(fn); // api.cache(fn)", + "", + " // Return the value that will be cached.", + " return { };", + "};", + ].join("\n"), + ); + } +} + +function makeSimpleConfigurator( + cache: CacheConfigurator, +): SimpleCacheConfigurator { + function cacheFn(val) { + if (typeof val === "boolean") { + if (val) cache.forever(); + else cache.never(); + return; + } + + return cache.using(val); + } + cacheFn.forever = () => cache.forever(); + cacheFn.never = () => cache.never(); + cacheFn.using = cb => cache.using(cb); + cacheFn.invalidate = cb => cache.invalidate(cb); + + return (cacheFn: any); } diff --git a/packages/babel-core/src/config/loading/files/configuration.js b/packages/babel-core/src/config/loading/files/configuration.js index 1c44439092..d9341390a3 100644 --- a/packages/babel-core/src/config/loading/files/configuration.js +++ b/packages/babel-core/src/config/loading/files/configuration.js @@ -139,7 +139,7 @@ const readConfigJS = makeStrongCache((filepath, cache) => { if (typeof options === "function") { options = options({ - cache, + cache: cache.simple(), // Expose ".env()" so people can easily get the same env that we expose using the "env" key. env: () => cache.using(() => getEnv()), async: () => false, diff --git a/packages/babel-core/src/config/option-manager.js b/packages/babel-core/src/config/option-manager.js index b9ea81c796..7d7ec5d978 100644 --- a/packages/babel-core/src/config/option-manager.js +++ b/packages/babel-core/src/config/option-manager.js @@ -187,7 +187,7 @@ const loadDescriptor = makeWeakCache( let item = value; if (typeof value === "function") { const api = Object.assign(Object.create(context), { - cache, + cache: cache.simple(), env: () => cache.using(() => getEnv()), async: () => false, }); diff --git a/packages/babel-core/test/caching-api.js b/packages/babel-core/test/caching-api.js index d846fd85bb..07781d73c1 100644 --- a/packages/babel-core/test/caching-api.js +++ b/packages/babel-core/test/caching-api.js @@ -19,23 +19,6 @@ describe("caching API", () => { assert.notEqual(fn("one"), fn("two")); }); - it("should allow permacaching with cache(true)", () => { - let count = 0; - - const fn = makeStrongCache((arg, cache) => { - cache(true); - return { arg, count: count++ }; - }); - - assert.deepEqual(fn("one"), { arg: "one", count: 0 }); - assert.equal(fn("one"), fn("one")); - - assert.deepEqual(fn("two"), { arg: "two", count: 1 }); - assert.equal(fn("two"), fn("two")); - - assert.notEqual(fn("one"), fn("two")); - }); - it("should allow disabling caching with .never()", () => { let count = 0; @@ -55,25 +38,6 @@ describe("caching API", () => { assert.notEqual(fn("one"), fn("two")); }); - it("should allow disabling caching with cache(false)", () => { - let count = 0; - - const fn = makeStrongCache((arg, cache) => { - cache(false); - return { arg, count: count++ }; - }); - - assert.deepEqual(fn("one"), { arg: "one", count: 0 }); - assert.deepEqual(fn("one"), { arg: "one", count: 1 }); - assert.notEqual(fn("one"), fn("one")); - - assert.deepEqual(fn("two"), { arg: "two", count: 4 }); - assert.deepEqual(fn("two"), { arg: "two", count: 5 }); - assert.notEqual(fn("two"), fn("two")); - - assert.notEqual(fn("one"), fn("two")); - }); - it("should allow caching based on a value with .using(fn)", () => { let count = 0; let other = "default"; @@ -115,47 +79,6 @@ describe("caching API", () => { assert.equal(fn("two"), fn("two")); }); - it("should allow caching based on a value with cache(fn)", () => { - let count = 0; - let other = "default"; - - const fn = makeStrongCache((arg, cache) => { - const val = cache(() => other); - - return { arg, val, count: count++ }; - }); - - assert.deepEqual(fn("one"), { arg: "one", val: "default", count: 0 }); - assert.equal(fn("one"), fn("one")); - - assert.deepEqual(fn("two"), { arg: "two", val: "default", count: 1 }); - assert.equal(fn("two"), fn("two")); - - other = "new"; - - assert.deepEqual(fn("one"), { arg: "one", val: "new", count: 2 }); - assert.equal(fn("one"), fn("one")); - - assert.deepEqual(fn("two"), { arg: "two", val: "new", count: 3 }); - assert.equal(fn("two"), fn("two")); - - other = "default"; - - assert.deepEqual(fn("one"), { arg: "one", val: "default", count: 0 }); - assert.equal(fn("one"), fn("one")); - - assert.deepEqual(fn("two"), { arg: "two", val: "default", count: 1 }); - assert.equal(fn("two"), fn("two")); - - other = "new"; - - assert.deepEqual(fn("one"), { arg: "one", val: "new", count: 2 }); - assert.equal(fn("one"), fn("one")); - - assert.deepEqual(fn("two"), { arg: "two", val: "new", count: 3 }); - assert.equal(fn("two"), fn("two")); - }); - it("should allow invalidation based on a value with .invalidate(fn)", () => { let count = 0; let other = "default"; @@ -410,4 +333,89 @@ describe("caching API", () => { /Cannot change caching after evaluation/, ); }); + + describe("simple", () => { + it("should allow permacaching with cache(true)", () => { + let count = 0; + + const fn = makeStrongCache((arg, cache) => { + cache = cache.simple(); + + cache(true); + return { arg, count: count++ }; + }); + + assert.deepEqual(fn("one"), { arg: "one", count: 0 }); + assert.equal(fn("one"), fn("one")); + + assert.deepEqual(fn("two"), { arg: "two", count: 1 }); + assert.equal(fn("two"), fn("two")); + + assert.notEqual(fn("one"), fn("two")); + }); + + it("should allow disabling caching with cache(false)", () => { + let count = 0; + + const fn = makeStrongCache((arg, cache) => { + cache = cache.simple(); + + cache(false); + return { arg, count: count++ }; + }); + + assert.deepEqual(fn("one"), { arg: "one", count: 0 }); + assert.deepEqual(fn("one"), { arg: "one", count: 1 }); + assert.notEqual(fn("one"), fn("one")); + + assert.deepEqual(fn("two"), { arg: "two", count: 4 }); + assert.deepEqual(fn("two"), { arg: "two", count: 5 }); + assert.notEqual(fn("two"), fn("two")); + + assert.notEqual(fn("one"), fn("two")); + }); + + it("should allow caching based on a value with cache(fn)", () => { + let count = 0; + let other = "default"; + + const fn = makeStrongCache((arg, cache) => { + cache = cache.simple(); + + const val = cache(() => other); + + return { arg, val, count: count++ }; + }); + + assert.deepEqual(fn("one"), { arg: "one", val: "default", count: 0 }); + assert.equal(fn("one"), fn("one")); + + assert.deepEqual(fn("two"), { arg: "two", val: "default", count: 1 }); + assert.equal(fn("two"), fn("two")); + + other = "new"; + + assert.deepEqual(fn("one"), { arg: "one", val: "new", count: 2 }); + assert.equal(fn("one"), fn("one")); + + assert.deepEqual(fn("two"), { arg: "two", val: "new", count: 3 }); + assert.equal(fn("two"), fn("two")); + + other = "default"; + + assert.deepEqual(fn("one"), { arg: "one", val: "default", count: 0 }); + assert.equal(fn("one"), fn("one")); + + assert.deepEqual(fn("two"), { arg: "two", val: "default", count: 1 }); + assert.equal(fn("two"), fn("two")); + + other = "new"; + + assert.deepEqual(fn("one"), { arg: "one", val: "new", count: 2 }); + assert.equal(fn("one"), fn("one")); + + assert.deepEqual(fn("two"), { arg: "two", val: "new", count: 3 }); + assert.equal(fn("two"), fn("two")); + }); + }); });