From 2163acd6c263cec0229e277e26671126a1318c7e Mon Sep 17 00:00:00 2001 From: Logan Smyth Date: Thu, 13 Apr 2017 13:23:36 -0700 Subject: [PATCH 1/5] Refactor static file processing with a helper wrapper. --- .../src/config/loading/files/configuration.js | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/babel-core/src/config/loading/files/configuration.js b/packages/babel-core/src/config/loading/files/configuration.js index d9a5867ab0..ba51499ef6 100644 --- a/packages/babel-core/src/config/loading/files/configuration.js +++ b/packages/babel-core/src/config/loading/files/configuration.js @@ -96,23 +96,6 @@ function readConfig(filepath) { return (path.extname(filepath) === ".js") ? readConfigJS(filepath) : readConfigFile(filepath); } -function readIgnoreConfig(filepath) { - if (!exists(filepath)) return null; - - const file = fs.readFileSync(filepath, "utf8"); - let lines = file.split("\n"); - - lines = lines - .map((line) => line.replace(/#(.*?)$/, "").trim()) - .filter((line) => !!line); - - return { - filepath, - dirname: path.dirname(filepath), - options: { ignore: lines }, - }; -} - function readConfigJS(filepath) { if (!exists(filepath)) return null; @@ -137,11 +120,7 @@ function readConfigJS(filepath) { }; } -function readConfigFile(filepath) { - if (!exists(filepath)) return null; - - const content = fs.readFileSync(filepath, "utf8"); - +const readConfigFile = makeStaticFileHandler((filepath, content) => { let options; if (path.basename(filepath) === PACKAGE_FILENAME) { try { @@ -172,4 +151,25 @@ function readConfigFile(filepath) { dirname: path.dirname(filepath), options, }; +}); + +const readIgnoreConfig = makeStaticFileHandler((filepath, content) => { + const ignore = content + .split("\n") + .map((line) => line.replace(/#(.*?)$/, "").trim()) + .filter((line) => !!line); + + return { + filepath, + dirname: path.dirname(filepath), + options: { ignore }, + }; +}); + +function makeStaticFileHandler(fn: (string, string) => T): (string) => T|null { + return (filepath) => { + if (!exists(filepath)) return null; + + return fn(filepath, fs.readFileSync(filepath, "utf8")); + }; } From 911027f289e8969ea294de33b19c3e8cadd37dce Mon Sep 17 00:00:00 2001 From: Logan Smyth Date: Tue, 28 Mar 2017 18:19:07 -0700 Subject: [PATCH 2/5] Cache config files based on mtime rather than file content. --- packages/babel-core/src/config/caching.js | 176 ++++++++++++++++++ .../src/config/loading/files/configuration.js | 53 +++--- 2 files changed, 204 insertions(+), 25 deletions(-) create mode 100644 packages/babel-core/src/config/caching.js diff --git a/packages/babel-core/src/config/caching.js b/packages/babel-core/src/config/caching.js new file mode 100644 index 0000000000..317e1e3a8a --- /dev/null +++ b/packages/babel-core/src/config/caching.js @@ -0,0 +1,176 @@ +// @flow + +type CacheConfigurator = CacheConfiguratorFn & CacheConfiguratorObj; + +type CacheConfiguratorFn = { + (boolean): void, + (handler: () => T): T, +}; +type CacheConfiguratorObj = { + forever: () => void, + never: () => void, + using: (handler: () => T) => T, + invalidate: (handler: () => T) => T, +}; + +type CacheEntry = Array<[ ResultT, () => boolean ]>; + +/** + * Given a function with a single argument, cache its results based on its argument and how it + * configures its caching behavior. Cached values are stored strongly. + */ +export function makeStrongCache( + handler: (ArgT, CacheConfigurator) => ResultT, + autoPermacache?: boolean, +): (ArgT) => ResultT { + return makeCachedFunction(new Map(), handler, autoPermacache); +} + +/** + * Given a function with a single argument, cache its results based on its argument and how it + * configures its caching behavior. Cached values are stored weakly and the function argument must be + * an object type. + */ +export function makeWeakCache( + handler: (ArgT, CacheConfigurator) => ResultT, + autoPermacache?: boolean, +): (ArgT) => ResultT { + return makeCachedFunction(new WeakMap(), handler, autoPermacache); +} + +type CacheMap = Map>|WeakMap>; + +function makeCachedFunction>( + callCache: Cache, + handler: (ArgT, CacheConfigurator) => ResultT, + autoPermacache: boolean = true, +): (ArgT) => ResultT { + return function cachedFunction(arg) { + let cachedValue: CacheEntry|void = callCache.get(arg); + + if (cachedValue) { + for (const [ value, valid ] of cachedValue) { + if (valid()) return value; + } + } + + const { cache, result } = makeCachePair(); + + const value = handler(arg, cache); + + if (autoPermacache && !result.configured) cache.forever(); + + 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")); + } + + if (!result.never) { + if (result.forever) { + cachedValue = [ + [value, () => true], + ]; + } else if (result.invalidate) { + cachedValue = [ + [value, result.valid], + ]; + } else { + cachedValue = cachedValue || []; + cachedValue.push([ value, result.valid ]); + } + callCache.set(arg, cachedValue); + } + + return value; + }; +} + +function makeCachePair(): { cache: CacheConfigurator, result: * } { + const pairs = []; + + const result = { + configured: false, + never: false, + forever: false, + invalidate: false, + valid: () => pairs.every(([key, fn]) => key === fn()), + }; + + const cache: CacheConfigurator = Object.assign((function cacheFn(val) { + if (typeof val === "boolean") { + if (val) cache.forever(); + else cache.never(); + return; + } + + return cache.using(val); + }: any), ({ + forever() { + if (result.never) throw new Error("Caching has already been configured with .never()"); + result.forever = true; + result.configured = true; + }, + never() { + if (result.forever) throw new Error("Caching has already been configured with .forever()"); + result.never = true; + result.configured = true; + }, + using(handler: () => T): T { + if (result.never || result.forever) { + throw new Error("Caching has already been configured with .never or .forever()"); + } + result.configured = true; + + const key = handler(); + pairs.push([ key, handler ]); + return key; + }, + invalidate(handler: () => T): T { + if (result.never || result.forever) { + throw new Error("Caching has already been configured with .never or .forever()"); + } + result.invalidate = true; + result.configured = true; + + const key = handler(); + pairs.push([ key, handler ]); + return key; + }, + }: CacheConfiguratorObj)); + + return { cache, result }; +} diff --git a/packages/babel-core/src/config/loading/files/configuration.js b/packages/babel-core/src/config/loading/files/configuration.js index ba51499ef6..aa47e374f5 100644 --- a/packages/babel-core/src/config/loading/files/configuration.js +++ b/packages/babel-core/src/config/loading/files/configuration.js @@ -4,6 +4,7 @@ import path from "path"; import fs from "fs"; import json5 from "json5"; import resolve from "resolve"; +import { makeStrongCache } from "../../caching"; type ConfigFile = { filepath: string, @@ -11,23 +12,11 @@ type ConfigFile = { options: Object, }; -const existsCache = {}; -const jsonCache = {}; - const BABELRC_FILENAME = ".babelrc"; const BABELRC_JS_FILENAME = ".babelrc.js"; const PACKAGE_FILENAME = "package.json"; const BABELIGNORE_FILENAME = ".babelignore"; -function exists(filename) { - const cached = existsCache[filename]; - if (cached == null) { - return existsCache[filename] = fs.existsSync(filename); - } else { - return cached; - } -} - export function findConfigs(dirname: string): Array { let foundConfig = false; let foundIgnore = false; @@ -96,8 +85,11 @@ function readConfig(filepath) { return (path.extname(filepath) === ".js") ? readConfigJS(filepath) : readConfigFile(filepath); } -function readConfigJS(filepath) { - if (!exists(filepath)) return null; +const readConfigJS = makeStrongCache((filepath, cache) => { + if (!fs.existsSync(filepath)) { + cache.forever(); + return null; + } let options; try { @@ -118,15 +110,13 @@ function readConfigJS(filepath) { dirname: path.dirname(filepath), options, }; -} +}); -const readConfigFile = makeStaticFileHandler((filepath, content) => { +const readConfigFile = makeStaticFileCache((filepath, content) => { let options; if (path.basename(filepath) === PACKAGE_FILENAME) { try { - const json = jsonCache[content] = jsonCache[content] || JSON.parse(content); - - options = json.babel; + options = JSON.parse(content).babel; } catch (err) { err.message = `${filepath}: Error while parsing JSON - ${err.message}`; throw err; @@ -134,7 +124,7 @@ const readConfigFile = makeStaticFileHandler((filepath, content) => { if (!options) return null; } else { try { - options = jsonCache[content] = jsonCache[content] || json5.parse(content); + options = json5.parse(content); } catch (err) { err.message = `${filepath}: Error while parsing config - ${err.message}`; throw err; @@ -153,7 +143,7 @@ const readConfigFile = makeStaticFileHandler((filepath, content) => { }; }); -const readIgnoreConfig = makeStaticFileHandler((filepath, content) => { +const readIgnoreConfig = makeStaticFileCache((filepath, content) => { const ignore = content .split("\n") .map((line) => line.replace(/#(.*?)$/, "").trim()) @@ -166,10 +156,23 @@ const readIgnoreConfig = makeStaticFileHandler((filepath, content) => { }; }); -function makeStaticFileHandler(fn: (string, string) => T): (string) => T|null { - return (filepath) => { - if (!exists(filepath)) return null; +function makeStaticFileCache(fn: (string, string) => T): (string) => T|null { + return makeStrongCache((filepath, cache) => { + if (cache.invalidate(() => fileMtime(filepath)) === null) { + cache.forever(); + return null; + } return fn(filepath, fs.readFileSync(filepath, "utf8")); - }; + }); +} + +function fileMtime(filepath: string): number|null { + try { + return +fs.statSync(filepath).mtime; + } catch (e) { + if (e.code !== "ENOENT") throw e; + } + + return null; } From 2774cb7d4243027c7c7730a84cd1efc5f3f83c8c Mon Sep 17 00:00:00 2001 From: Logan Smyth Date: Thu, 13 Apr 2017 13:39:41 -0700 Subject: [PATCH 3/5] Allow function-based .babelrc.js files. --- .../src/config/loading/files/configuration.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/babel-core/src/config/loading/files/configuration.js b/packages/babel-core/src/config/loading/files/configuration.js index aa47e374f5..4827f7300d 100644 --- a/packages/babel-core/src/config/loading/files/configuration.js +++ b/packages/babel-core/src/config/loading/files/configuration.js @@ -4,6 +4,7 @@ import path from "path"; import fs from "fs"; import json5 from "json5"; import resolve from "resolve"; +import { getEnv } from "../../helpers/environment"; import { makeStrongCache } from "../../caching"; type ConfigFile = { @@ -101,6 +102,16 @@ const readConfigJS = makeStrongCache((filepath, cache) => { throw err; } + if (typeof options === "function") { + options = options({ + cache, + // Expose ".env()" so people can easily get the same env that we expose using the "env" key. + env: () => cache.using(() => getEnv()), + }); + } else { + cache.forever(); + } + if (!options || typeof options !== "object" || Array.isArray(options)) { throw new Error(`${filepath}: Configuration should be an exported JavaScript object.`); } @@ -110,7 +121,7 @@ const readConfigJS = makeStrongCache((filepath, cache) => { dirname: path.dirname(filepath), options, }; -}); +}, false /* autoPermacache */); const readConfigFile = makeStaticFileCache((filepath, content) => { let options; From ccbb2eb8d443a108932ac6aec5e1695fc49c0dd3 Mon Sep 17 00:00:00 2001 From: Logan Smyth Date: Fri, 14 Apr 2017 14:14:13 -0700 Subject: [PATCH 4/5] Disallow cache configuration after handler evaluation. --- packages/babel-core/src/config/caching.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/babel-core/src/config/caching.js b/packages/babel-core/src/config/caching.js index 317e1e3a8a..219cf304d4 100644 --- a/packages/babel-core/src/config/caching.js +++ b/packages/babel-core/src/config/caching.js @@ -54,12 +54,14 @@ function makeCachedFunction>( } } - const { cache, result } = makeCachePair(); + const { cache, result, deactivate } = makeCacheConfig(); const value = handler(arg, cache); if (autoPermacache && !result.configured) cache.forever(); + deactivate(); + if (!result.configured) { // eslint-disable-next-line max-len throw new Error([ @@ -119,7 +121,7 @@ function makeCachedFunction>( }; } -function makeCachePair(): { cache: CacheConfigurator, result: * } { +function makeCacheConfig(): { cache: CacheConfigurator, result: *, deactivate: () => void } { const pairs = []; const result = { @@ -130,6 +132,11 @@ function makeCachePair(): { cache: CacheConfigurator, result: * } { valid: () => pairs.every(([key, fn]) => key === fn()), }; + let active = true; + const deactivate = () => { + active = false; + }; + const cache: CacheConfigurator = Object.assign((function cacheFn(val) { if (typeof val === "boolean") { if (val) cache.forever(); @@ -140,16 +147,19 @@ function makeCachePair(): { cache: CacheConfigurator, result: * } { 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()"); } @@ -160,6 +170,7 @@ function makeCachePair(): { cache: CacheConfigurator, result: * } { 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()"); } @@ -172,5 +183,5 @@ function makeCachePair(): { cache: CacheConfigurator, result: * } { }, }: CacheConfiguratorObj)); - return { cache, result }; + return { cache, result, deactivate }; } From 1c078e5a7683049e76630559db0f83b7e8faad38 Mon Sep 17 00:00:00 2001 From: Logan Smyth Date: Mon, 17 Apr 2017 11:14:29 -0700 Subject: [PATCH 5/5] Add tests for caching API and .babelrc.js functions. --- packages/babel-core/test/caching-api.js | 356 ++++++++++++++++++ packages/babel-core/test/config-chain.js | 40 ++ .../config/js-config-function/.babelrc.js | 7 + 3 files changed, 403 insertions(+) create mode 100644 packages/babel-core/test/caching-api.js create mode 100644 packages/babel-core/test/fixtures/config/js-config-function/.babelrc.js diff --git a/packages/babel-core/test/caching-api.js b/packages/babel-core/test/caching-api.js new file mode 100644 index 0000000000..914214540b --- /dev/null +++ b/packages/babel-core/test/caching-api.js @@ -0,0 +1,356 @@ +import assert from "assert"; +import { makeStrongCache } from "../lib/config/caching"; + +describe("caching API", () => { + it("should allow permacaching with .forever()", () => { + let count = 0; + + const fn = makeStrongCache((arg, cache) => { + cache.forever(); + 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 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; + + const fn = makeStrongCache((arg, cache) => { + cache.never(); + 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 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"; + + const fn = makeStrongCache((arg, cache) => { + const val = cache.using(() => 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 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"; + + const fn = makeStrongCache((arg, cache) => { + const val = cache.invalidate(() => 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: 4 }); + assert.equal(fn("one"), fn("one")); + + assert.deepEqual(fn("two"), { arg: "two", val: "default", count: 5 }); + assert.equal(fn("two"), fn("two")); + + other = "new"; + + assert.deepEqual(fn("one"), { arg: "one", val: "new", count: 6 }); + assert.equal(fn("one"), fn("one")); + + assert.deepEqual(fn("two"), { arg: "two", val: "new", count: 7 }); + assert.equal(fn("two"), fn("two")); + }); + + it("should allow invalidation with .using and .invalidate", () => { + let count = 0; + let other = "default"; + let another = "another"; + + const fn = makeStrongCache((arg, cache) => { + const val = cache.using(() => other); + const val2 = cache.invalidate(() => another); + + return { arg, val, val2, count: count++ }; + }); + + assert.deepEqual(fn("one"), { arg: "one", val: "default", val2: "another", count: 0 }); + assert.equal(fn("one"), fn("one")); + + assert.deepEqual(fn("two"), { arg: "two", val: "default", val2: "another", count: 1 }); + assert.equal(fn("two"), fn("two")); + + other = "new"; + + assert.deepEqual(fn("one"), { arg: "one", val: "new", val2: "another", count: 2 }); + assert.equal(fn("one"), fn("one")); + + assert.deepEqual(fn("two"), { arg: "two", val: "new", val2: "another", count: 3 }); + assert.equal(fn("two"), fn("two")); + + other = "default"; + + assert.deepEqual(fn("one"), { arg: "one", val: "default", val2: "another", count: 4 }); + assert.equal(fn("one"), fn("one")); + + assert.deepEqual(fn("two"), { arg: "two", val: "default", val2: "another", count: 5 }); + assert.equal(fn("two"), fn("two")); + + other = "new"; + + assert.deepEqual(fn("one"), { arg: "one", val: "new", val2: "another", count: 6 }); + assert.equal(fn("one"), fn("one")); + + assert.deepEqual(fn("two"), { arg: "two", val: "new", val2: "another", count: 7 }); + assert.equal(fn("two"), fn("two")); + + another = "second"; + + assert.deepEqual(fn("one"), { arg: "one", val: "new", val2: "second", count: 8 }); + assert.equal(fn("one"), fn("one")); + + assert.deepEqual(fn("two"), { arg: "two", val: "new", val2: "second", count: 9 }); + assert.equal(fn("two"), fn("two")); + + }); + + it("should throw if caching is never configured and not defaulting", () => { + const fn = makeStrongCache(() => { }, false /* autoPermacache */); + + assert.throws(() => fn(), /Error: Caching was left unconfigured./); + }); + + it("should auto-permacache by default", () => { + let count = 0; + + const fn = makeStrongCache((arg) => ({ 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 throw if you set permacaching and use .using", () => { + const fn = makeStrongCache((arg, cache) => { + cache.forever(); + + cache.using(() => null); + }); + + assert.throws(() => fn(), /Caching has already been configured/); + }); + + it("should throw if you set permacaching and use .invalidate", () => { + const fn = makeStrongCache((arg, cache) => { + cache.forever(); + + cache.invalidate(() => null); + }); + + assert.throws(() => fn(), /Caching has already been configured/); + }); + + it("should throw if you set permacaching and use .never", () => { + const fn = makeStrongCache((arg, cache) => { + cache.forever(); + + cache.never(); + }); + + assert.throws(() => fn(), /Caching has already been configured/); + }); + + it("should throw if you set no caching and use .using", () => { + const fn = makeStrongCache((arg, cache) => { + cache.never(); + + cache.using(() => null); + }); + + assert.throws(() => fn(), /Caching has already been configured/); + }); + + it("should throw if you set no caching and use .invalidate", () => { + const fn = makeStrongCache((arg, cache) => { + cache.never(); + + cache.invalidate(() => null); + }); + + assert.throws(() => fn(), /Caching has already been configured/); + }); + + it("should throw if you set no caching and use .never", () => { + const fn = makeStrongCache((arg, cache) => { + cache.never(); + + cache.using(() => null); + }); + + assert.throws(() => fn(), /Caching has already been configured/); + }); + + it("should throw if you configure .forever after exiting", () => { + const fn = makeStrongCache((arg, cache) => cache); + + assert.throws(() => fn().forever(), /Cannot change caching after evaluation/); + }); + + it("should throw if you configure .never after exiting", () => { + const fn = makeStrongCache((arg, cache) => cache); + + assert.throws(() => fn().never(), /Cannot change caching after evaluation/); + }); + + it("should throw if you configure .using after exiting", () => { + const fn = makeStrongCache((arg, cache) => cache); + + assert.throws(() => fn().using(() => null), /Cannot change caching after evaluation/); + }); + + it("should throw if you configure .invalidate after exiting", () => { + const fn = makeStrongCache((arg, cache) => cache); + + assert.throws(() => fn().invalidate(() => null), /Cannot change caching after evaluation/); + }); +}); diff --git a/packages/babel-core/test/config-chain.js b/packages/babel-core/test/config-chain.js index 1aeed5f4d0..453e3e86e6 100644 --- a/packages/babel-core/test/config-chain.js +++ b/packages/babel-core/test/config-chain.js @@ -418,6 +418,46 @@ describe("buildConfigChain", function () { assert.deepEqual(chain, expected); }); + it("js-config-function", function () { + const chain = buildConfigChain({ + filename: fixture("js-config-function", "src.js"), + }); + + const expected = [ + { + type: "options", + options: { + ignore: [ + "root-ignore", + ], + }, + alias: fixture(".babelignore"), + loc: fixture(".babelignore"), + dirname: fixture(), + }, + { + type: "options", + options: { + compact: true, + }, + alias: fixture("js-config-function", ".babelrc.js"), + loc: fixture("js-config-function", ".babelrc.js"), + dirname: fixture("js-config-function"), + }, + { + type: "arguments", + options: { + filename: fixture("js-config-function", "src.js"), + }, + alias: "base", + loc: "base", + dirname: base(), + }, + ]; + + assert.deepEqual(chain, expected); + }); + it("js-config-default - should read transpiled export default", function () { const chain = buildConfigChain({ filename: fixture("js-config-default", "src.js"), diff --git a/packages/babel-core/test/fixtures/config/js-config-function/.babelrc.js b/packages/babel-core/test/fixtures/config/js-config-function/.babelrc.js new file mode 100644 index 0000000000..73ace51a11 --- /dev/null +++ b/packages/babel-core/test/fixtures/config/js-config-function/.babelrc.js @@ -0,0 +1,7 @@ +module.exports = function(api) { + api.cache(true); + + return { + compact: true, + }; +};