Expose more caching flexibility for Babel's internal use.
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
// @flow
|
||||
|
||||
type CacheConfigurator = CacheConfiguratorFn & CacheConfiguratorObj;
|
||||
type SimpleCacheConfigurator = SimpleCacheConfiguratorFn &
|
||||
SimpleCacheConfiguratorObj;
|
||||
|
||||
type CacheConfiguratorFn = {
|
||||
type SimpleCacheConfiguratorFn = {
|
||||
(boolean): void,
|
||||
<T>(handler: () => T): T,
|
||||
};
|
||||
type CacheConfiguratorObj = {
|
||||
type SimpleCacheConfiguratorObj = {
|
||||
forever: () => void,
|
||||
never: () => void,
|
||||
using: <T>(handler: () => T) => T,
|
||||
@@ -56,165 +57,187 @@ function makeCachedFunction<ArgT, ResultT, Cache: CacheMap<ArgT, ResultT>>(
|
||||
}
|
||||
}
|
||||
|
||||
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<T>(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<T>(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<T>(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<T>(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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user