Merge pull request #6326 from loganfsmyth/preserve-config-identity
Preserve object identity when loading config, for improved future caching.
This commit is contained in:
commit
828aec757a
@ -7,7 +7,9 @@ import buildDebug from "debug";
|
||||
|
||||
const debug = buildDebug("babel:config:config-chain");
|
||||
|
||||
import { findConfigs, loadConfig } from "./loading/files";
|
||||
import { findConfigs, loadConfig, type ConfigFile } from "./loading/files";
|
||||
|
||||
import { makeWeakCache, makeStrongCache } from "./caching";
|
||||
|
||||
type ConfigItem = {
|
||||
type: "options" | "arguments",
|
||||
@ -17,25 +19,32 @@ type ConfigItem = {
|
||||
loc: string,
|
||||
};
|
||||
|
||||
type ConfigRaw = {
|
||||
type: "options" | "arguments",
|
||||
options: {},
|
||||
alias: string,
|
||||
dirname: string,
|
||||
};
|
||||
|
||||
export default function buildConfigChain(opts: {}): Array<ConfigItem> | null {
|
||||
if (typeof opts.filename !== "string" && opts.filename != null) {
|
||||
throw new Error(".filename must be a string, null, or undefined");
|
||||
}
|
||||
|
||||
const filename = opts.filename ? path.resolve(opts.filename) : null;
|
||||
const builder = new ConfigChainBuilder(filename);
|
||||
const builder = new ConfigChainBuilder(
|
||||
filename ? new LoadedFile(filename) : null,
|
||||
);
|
||||
|
||||
const envKey = getEnv();
|
||||
try {
|
||||
builder.mergeConfig({
|
||||
type: "arguments",
|
||||
options: opts,
|
||||
alias: "base",
|
||||
dirname: process.cwd(),
|
||||
});
|
||||
builder.mergeConfigArguments(opts, process.cwd(), envKey);
|
||||
|
||||
// resolve all .babelrc files
|
||||
if (opts.babelrc !== false && filename) {
|
||||
builder.findConfigs(filename);
|
||||
findConfigs(path.dirname(filename)).forEach(configFile =>
|
||||
builder.mergeConfigFile(configFile, envKey),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.code !== "BABEL_IGNORED_FILE") throw e;
|
||||
@ -47,30 +56,346 @@ export default function buildConfigChain(opts: {}): Array<ConfigItem> | null {
|
||||
}
|
||||
|
||||
class ConfigChainBuilder {
|
||||
filename: string | null;
|
||||
configs: Array<ConfigItem>;
|
||||
possibleDirs: null | Array<string>;
|
||||
file: LoadedFile | null;
|
||||
configs: Array<ConfigItem> = [];
|
||||
|
||||
constructor(file: LoadedFile | null) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
mergeConfigArguments(opts: {}, dirname: string, envKey: string) {
|
||||
flattenArgumentsOptionsParts(opts, dirname, envKey).forEach(part =>
|
||||
this._processConfigPart(part, envKey),
|
||||
);
|
||||
}
|
||||
|
||||
mergeConfigFile(file: ConfigFile, envKey: string) {
|
||||
flattenFileOptionsParts(file)(envKey).forEach(part =>
|
||||
this._processConfigPart(part, envKey),
|
||||
);
|
||||
}
|
||||
|
||||
_processConfigPart(part: ConfigPart, envKey: string) {
|
||||
if (part.part === "config") {
|
||||
const { ignore, only } = part;
|
||||
|
||||
// Bail out ASAP if this file is ignored so that we run as little logic as possible on ignored files.
|
||||
if (
|
||||
this.file &&
|
||||
this.file.shouldIgnore(ignore, only, part.config.dirname)
|
||||
) {
|
||||
// TODO(logan): This is a really gross way to bail out. Avoid this in rewrite.
|
||||
throw Object.assign((new Error("This file has been ignored."): any), {
|
||||
code: "BABEL_IGNORED_FILE",
|
||||
});
|
||||
}
|
||||
|
||||
this.configs.push(part.config);
|
||||
} else {
|
||||
const extendsConfig = loadConfig(part.path, part.dirname);
|
||||
|
||||
const existingConfig = this.configs.some(config => {
|
||||
return config.alias === extendsConfig.filepath;
|
||||
});
|
||||
if (!existingConfig) {
|
||||
this.mergeConfigFile(extendsConfig, envKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the root config object passed to Babel, split it into the separate
|
||||
* config parts. The resulting config objects in the 'ConfigPart' have their
|
||||
* object identity preserved between calls so that they can be used for caching.
|
||||
*/
|
||||
function flattenArgumentsOptionsParts(
|
||||
opts: {},
|
||||
dirname: string,
|
||||
envKey: string,
|
||||
): Array<ConfigPart> {
|
||||
const raw = [];
|
||||
|
||||
const env = typeof opts.env === "object" ? opts.env : null;
|
||||
const plugins = Array.isArray(opts.plugins) ? opts.plugins : null;
|
||||
const presets = Array.isArray(opts.presets) ? opts.presets : null;
|
||||
const passPerPreset =
|
||||
typeof opts.passPerPreset === "boolean" ? opts.passPerPreset : false;
|
||||
|
||||
if (env) {
|
||||
raw.push(...flattenArgumentsEnvOptionsParts(env)(dirname)(envKey));
|
||||
}
|
||||
|
||||
const innerOpts = Object.assign({}, opts);
|
||||
// If the env, plugins, and presets values on the object aren't arrays or
|
||||
// objects, leave them in the base opts so that normal options validation
|
||||
// will throw errors on them later.
|
||||
if (env) delete innerOpts.env;
|
||||
if (plugins) delete innerOpts.plugins;
|
||||
if (presets) {
|
||||
delete innerOpts.presets;
|
||||
delete innerOpts.passPerPreset;
|
||||
}
|
||||
delete innerOpts.extends;
|
||||
|
||||
if (Object.keys(innerOpts).length > 0) {
|
||||
raw.push(
|
||||
...flattenOptionsParts({
|
||||
type: "arguments",
|
||||
options: innerOpts,
|
||||
alias: "base",
|
||||
dirname,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (plugins) {
|
||||
raw.push(...flattenArgumentsPluginsOptionsParts(plugins)(dirname));
|
||||
}
|
||||
if (presets) {
|
||||
raw.push(
|
||||
...flattenArgumentsPresetsOptionsParts(presets)(passPerPreset)(dirname),
|
||||
);
|
||||
}
|
||||
|
||||
if (opts.extends != null) {
|
||||
raw.push(
|
||||
...flattenOptionsParts(
|
||||
buildArgumentsRawConfig({ extends: opts.extends }, dirname),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* For the top-level 'options' object, we cache the env list based on
|
||||
* the object identity of the 'env' object.
|
||||
*/
|
||||
const flattenArgumentsEnvOptionsParts = makeWeakCache((env: {}) => {
|
||||
const options = { env };
|
||||
|
||||
return makeStrongCache((dirname: string) =>
|
||||
flattenOptionsPartsLookup(buildArgumentsRawConfig(options, dirname)),
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* For the top-level 'options' object, we cache the plugin list based on
|
||||
* the object identity of the 'plugins' object.
|
||||
*/
|
||||
const flattenArgumentsPluginsOptionsParts = makeWeakCache(
|
||||
(plugins: Array<mixed>) => {
|
||||
const options = { plugins };
|
||||
|
||||
return makeStrongCache((dirname: string) =>
|
||||
flattenOptionsParts(buildArgumentsRawConfig(options, dirname)),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* For the top-level 'options' object, we cache the preset list based on
|
||||
* the object identity of the 'presets' object.
|
||||
*/
|
||||
const flattenArgumentsPresetsOptionsParts = makeWeakCache(
|
||||
(presets: Array<mixed>) =>
|
||||
makeStrongCache((passPerPreset: ?boolean) => {
|
||||
// The concept of passPerPreset is integrally tied to the preset list
|
||||
// so unfortunately we need to copy both values here, adding an extra
|
||||
// layer of caching functions.
|
||||
const options = { presets, passPerPreset };
|
||||
|
||||
return makeStrongCache((dirname: string) =>
|
||||
flattenOptionsParts(buildArgumentsRawConfig(options, dirname)),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
function buildArgumentsRawConfig(options: {}, dirname: string): ConfigRaw {
|
||||
return {
|
||||
type: "arguments",
|
||||
options,
|
||||
alias: "base",
|
||||
dirname,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a config from a specific file, return a list of ConfigPart objects
|
||||
* with object identity preserved for all 'config' part objects for use
|
||||
* with caching later in config processing.
|
||||
*/
|
||||
const flattenFileOptionsParts = makeWeakCache((file: ConfigFile) => {
|
||||
return flattenOptionsPartsLookup({
|
||||
type: "options",
|
||||
options: file.options,
|
||||
alias: file.filepath,
|
||||
dirname: file.dirname,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Given a config, create a function that will return the config parts for
|
||||
* the environment passed as the first argument.
|
||||
*/
|
||||
function flattenOptionsPartsLookup(
|
||||
config: ConfigRaw,
|
||||
): (string | null) => Array<ConfigPart> {
|
||||
const parts = flattenOptionsParts(config);
|
||||
|
||||
const def = parts.filter(part => part.activeEnv === null);
|
||||
const lookup = new Map();
|
||||
|
||||
parts.forEach(part => {
|
||||
if (part.activeEnv !== null) lookup.set(part.activeEnv, []);
|
||||
});
|
||||
|
||||
for (const [activeEnv, values] of lookup) {
|
||||
parts.forEach(part => {
|
||||
if (part.activeEnv === null || part.activeEnv === activeEnv) {
|
||||
values.push(part);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return envKey => lookup.get(envKey) || def;
|
||||
}
|
||||
|
||||
type ConfigPart =
|
||||
| {
|
||||
part: "config",
|
||||
config: ConfigItem,
|
||||
ignore: ?Array<mixed>,
|
||||
only: ?Array<mixed>,
|
||||
activeEnv: string | null,
|
||||
}
|
||||
| {
|
||||
part: "extends",
|
||||
path: string,
|
||||
dirname: string,
|
||||
activeEnv: string | null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a generic config object, flatten it into its various parts so that
|
||||
* then can be cached and processed later.
|
||||
*/
|
||||
function flattenOptionsParts(
|
||||
rawConfig: ConfigRaw,
|
||||
activeEnv: string | null = null,
|
||||
): Array<ConfigPart> {
|
||||
const { type, options: rawOpts, alias, dirname } = rawConfig;
|
||||
|
||||
if (rawOpts.ignore != null && !Array.isArray(rawOpts.ignore)) {
|
||||
throw new Error(
|
||||
`.ignore should be an array, ${JSON.stringify(rawOpts.ignore)} given`,
|
||||
);
|
||||
}
|
||||
if (rawOpts.only != null && !Array.isArray(rawOpts.only)) {
|
||||
throw new Error(
|
||||
`.only should be an array, ${JSON.stringify(rawOpts.only)} given`,
|
||||
);
|
||||
}
|
||||
const ignore = rawOpts.ignore || null;
|
||||
const only = rawOpts.only || null;
|
||||
|
||||
const parts = [];
|
||||
|
||||
if (
|
||||
rawOpts.env != null &&
|
||||
(typeof rawOpts.env !== "object" || Array.isArray(rawOpts.env))
|
||||
) {
|
||||
throw new Error(".env block must be an object, null, or undefined");
|
||||
}
|
||||
|
||||
const rawEnv = rawOpts.env || {};
|
||||
|
||||
Object.keys(rawEnv).forEach(envKey => {
|
||||
const envOpts = rawEnv[envKey];
|
||||
|
||||
if (envOpts !== undefined && activeEnv !== null && activeEnv !== envKey) {
|
||||
throw new Error(`Unreachable .env[${envKey}] block detected`);
|
||||
}
|
||||
|
||||
if (
|
||||
envOpts != null &&
|
||||
(typeof envOpts !== "object" || Array.isArray(envOpts))
|
||||
) {
|
||||
throw new Error(".env[...] block must be an object, null, or undefined");
|
||||
}
|
||||
|
||||
if (envOpts) {
|
||||
parts.push(
|
||||
...flattenOptionsParts(
|
||||
{
|
||||
type,
|
||||
options: envOpts,
|
||||
alias: alias + `.env.${envKey}`,
|
||||
dirname,
|
||||
},
|
||||
envKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const options = Object.assign({}, rawOpts);
|
||||
delete options.env;
|
||||
delete options.extends;
|
||||
|
||||
parts.push({
|
||||
part: "config",
|
||||
config: {
|
||||
type,
|
||||
options,
|
||||
alias,
|
||||
loc: alias,
|
||||
dirname,
|
||||
},
|
||||
ignore,
|
||||
only,
|
||||
activeEnv,
|
||||
});
|
||||
|
||||
if (rawOpts.extends != null) {
|
||||
if (typeof rawOpts.extends !== "string") {
|
||||
throw new Error(".extends must be a string");
|
||||
}
|
||||
|
||||
parts.push({
|
||||
part: "extends",
|
||||
path: rawOpts.extends,
|
||||
dirname,
|
||||
activeEnv,
|
||||
});
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track a given file and expose function to check if it should be ignored.
|
||||
*/
|
||||
class LoadedFile {
|
||||
filename: string;
|
||||
possibleDirs: null | Array<string> = null;
|
||||
|
||||
constructor(filename) {
|
||||
this.configs = [];
|
||||
this.filename = filename;
|
||||
this.possibleDirs = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a filename should be ignored based on "ignore" and "only" options.
|
||||
*/
|
||||
shouldIgnore(ignore: mixed, only: mixed, dirname: string): boolean {
|
||||
if (!this.filename) return false;
|
||||
|
||||
shouldIgnore(
|
||||
ignore: ?Array<mixed>,
|
||||
only: ?Array<mixed>,
|
||||
dirname: string,
|
||||
): boolean {
|
||||
if (ignore) {
|
||||
if (!Array.isArray(ignore)) {
|
||||
throw new Error(
|
||||
`.ignore should be an array, ${JSON.stringify(ignore)} given`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.matchesPatterns(ignore, dirname)) {
|
||||
if (this._matchesPatterns(ignore, dirname)) {
|
||||
debug(
|
||||
"Ignored %o because it matched one of %O from %o",
|
||||
this.filename,
|
||||
@ -82,13 +407,7 @@ class ConfigChainBuilder {
|
||||
}
|
||||
|
||||
if (only) {
|
||||
if (!Array.isArray(only)) {
|
||||
throw new Error(
|
||||
`.only should be an array, ${JSON.stringify(only)} given`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.matchesPatterns(only, dirname)) {
|
||||
if (!this._matchesPatterns(only, dirname)) {
|
||||
debug(
|
||||
"Ignored %o because it failed to match one of %O from %o",
|
||||
this.filename,
|
||||
@ -106,12 +425,7 @@ class ConfigChainBuilder {
|
||||
* Returns result of calling function with filename if pattern is a function.
|
||||
* Otherwise returns result of matching pattern Regex with filename.
|
||||
*/
|
||||
matchesPatterns(patterns: Array<mixed>, dirname: string) {
|
||||
const filename = this.filename;
|
||||
if (!filename) {
|
||||
throw new Error("Assertion failure: .filename should always exist here");
|
||||
}
|
||||
|
||||
_matchesPatterns(patterns: Array<mixed>, dirname: string): boolean {
|
||||
const res = [];
|
||||
const strings = [];
|
||||
const fns = [];
|
||||
@ -127,6 +441,7 @@ class ConfigChainBuilder {
|
||||
}
|
||||
});
|
||||
|
||||
const filename = this.filename;
|
||||
if (res.some(re => re.test(filename))) return true;
|
||||
if (fns.some(fn => fn(filename))) return true;
|
||||
|
||||
@ -165,87 +480,4 @@ class ConfigChainBuilder {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
findConfigs(loc: string) {
|
||||
findConfigs(path.dirname(loc)).forEach(({ filepath, dirname, options }) => {
|
||||
this.mergeConfig({
|
||||
type: "options",
|
||||
options,
|
||||
alias: filepath,
|
||||
dirname,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
mergeConfig({ type, options: rawOpts, alias, dirname }) {
|
||||
// Bail out ASAP if this file is ignored so that we run as little logic as possible on ignored files.
|
||||
if (
|
||||
this.filename &&
|
||||
this.shouldIgnore(rawOpts.ignore || null, rawOpts.only || null, dirname)
|
||||
) {
|
||||
// TODO(logan): This is a really cross way to bail out. Avoid this in rewrite.
|
||||
throw Object.assign((new Error("This file has been ignored."): any), {
|
||||
code: "BABEL_IGNORED_FILE",
|
||||
});
|
||||
}
|
||||
|
||||
const options = Object.assign({}, rawOpts);
|
||||
delete options.env;
|
||||
delete options.extends;
|
||||
|
||||
const envKey = getEnv();
|
||||
|
||||
if (
|
||||
rawOpts.env != null &&
|
||||
(typeof rawOpts.env !== "object" || Array.isArray(rawOpts.env))
|
||||
) {
|
||||
throw new Error(".env block must be an object, null, or undefined");
|
||||
}
|
||||
|
||||
const envOpts = rawOpts.env && rawOpts.env[envKey];
|
||||
|
||||
if (
|
||||
envOpts != null &&
|
||||
(typeof envOpts !== "object" || Array.isArray(envOpts))
|
||||
) {
|
||||
throw new Error(".env[...] block must be an object, null, or undefined");
|
||||
}
|
||||
|
||||
if (envOpts) {
|
||||
this.mergeConfig({
|
||||
type,
|
||||
options: envOpts,
|
||||
alias: `${alias}.env.${envKey}`,
|
||||
dirname: dirname,
|
||||
});
|
||||
}
|
||||
|
||||
this.configs.push({
|
||||
type,
|
||||
options,
|
||||
alias,
|
||||
loc: alias,
|
||||
dirname,
|
||||
});
|
||||
|
||||
if (rawOpts.extends) {
|
||||
if (typeof rawOpts.extends !== "string") {
|
||||
throw new Error(".extends must be a string");
|
||||
}
|
||||
|
||||
const extendsConfig = loadConfig(rawOpts.extends, dirname);
|
||||
|
||||
const existingConfig = this.configs.some(config => {
|
||||
return config.alias === extendsConfig.filepath;
|
||||
});
|
||||
if (!existingConfig) {
|
||||
this.mergeConfig({
|
||||
type: "options",
|
||||
alias: extendsConfig.filepath,
|
||||
options: extendsConfig.options,
|
||||
dirname: extendsConfig.dirname,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ export function makeStrongCache<ArgT, ResultT>(
|
||||
* configures its caching behavior. Cached values are stored weakly and the function argument must be
|
||||
* an object type.
|
||||
*/
|
||||
export function makeWeakCache<ArgT: {}, ResultT>(
|
||||
export function makeWeakCache<ArgT: {} | Array<*>, ResultT>(
|
||||
handler: (ArgT, CacheConfigurator) => ResultT,
|
||||
autoPermacache?: boolean,
|
||||
): ArgT => ResultT {
|
||||
|
||||
@ -10,7 +10,7 @@ import { makeStrongCache } from "../../caching";
|
||||
|
||||
const debug = buildDebug("babel:config:loading:files:configuration");
|
||||
|
||||
type ConfigFile = {
|
||||
export type ConfigFile = {
|
||||
filepath: string,
|
||||
dirname: string,
|
||||
options: {},
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
type ConfigFile = {
|
||||
export type ConfigFile = {
|
||||
filepath: string,
|
||||
dirname: string,
|
||||
options: {},
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import assert from "assert";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import buildConfigChain from "../lib/config/build-config-chain";
|
||||
|
||||
@ -52,6 +53,234 @@ describe("buildConfigChain", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("caching", function() {
|
||||
describe("programmatic options", function() {
|
||||
it("should not cache the input options by identity", () => {
|
||||
const comments = false;
|
||||
|
||||
const chain1 = buildConfigChain({ comments });
|
||||
const chain2 = buildConfigChain({ comments });
|
||||
|
||||
assert.equal(chain1.length, 1);
|
||||
assert.equal(chain2.length, 1);
|
||||
assert.notStrictEqual(chain1[0], chain2[0]);
|
||||
});
|
||||
|
||||
it("should cache the env options by identity", () => {
|
||||
process.env.NODE_ENV = "foo";
|
||||
const env = {
|
||||
foo: {
|
||||
comments: false,
|
||||
},
|
||||
};
|
||||
|
||||
const chain1 = buildConfigChain({ env });
|
||||
const chain2 = buildConfigChain({ env });
|
||||
|
||||
assert.equal(chain1.length, 2);
|
||||
assert.equal(chain2.length, 2);
|
||||
assert.strictEqual(chain1[0], chain2[0]);
|
||||
assert.strictEqual(chain1[1], chain2[1]);
|
||||
});
|
||||
|
||||
it("should cache the plugin options by identity", () => {
|
||||
const plugins = [];
|
||||
|
||||
const chain1 = buildConfigChain({ plugins });
|
||||
const chain2 = buildConfigChain({ plugins });
|
||||
|
||||
assert.equal(chain1.length, 1);
|
||||
assert.equal(chain2.length, 1);
|
||||
assert.strictEqual(chain1[0], chain2[0]);
|
||||
});
|
||||
|
||||
it("should cache the presets options by identity", () => {
|
||||
const presets = [];
|
||||
|
||||
const chain1 = buildConfigChain({ presets });
|
||||
const chain2 = buildConfigChain({ presets });
|
||||
|
||||
assert.equal(chain1.length, 1);
|
||||
assert.equal(chain2.length, 1);
|
||||
assert.strictEqual(chain1[0], chain2[0]);
|
||||
});
|
||||
|
||||
it("should not cache the presets options with passPerPreset", () => {
|
||||
const presets = [];
|
||||
|
||||
const chain1 = buildConfigChain({ presets });
|
||||
const chain2 = buildConfigChain({ presets, passPerPreset: true });
|
||||
const chain3 = buildConfigChain({ presets, passPerPreset: false });
|
||||
|
||||
assert.equal(chain1.length, 1);
|
||||
assert.equal(chain2.length, 1);
|
||||
assert.equal(chain3.length, 1);
|
||||
assert.notStrictEqual(chain1[0], chain2[0]);
|
||||
assert.strictEqual(chain1[0], chain3[0]);
|
||||
assert.notStrictEqual(chain2[0], chain3[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("config file options", function() {
|
||||
function touch(filepath) {
|
||||
const s = fs.statSync(filepath);
|
||||
fs.utimesSync(
|
||||
filepath,
|
||||
s.atime,
|
||||
s.mtime + Math.random() > 0.5 ? 1 : -1,
|
||||
);
|
||||
}
|
||||
|
||||
it("should cache package.json files by mtime", () => {
|
||||
const filename = fixture(
|
||||
"complex-plugin-config",
|
||||
"config-identity",
|
||||
"pkg",
|
||||
"src.js",
|
||||
);
|
||||
const pkgJSON = fixture(
|
||||
"complex-plugin-config",
|
||||
"config-identity",
|
||||
"pkg",
|
||||
"package.json",
|
||||
);
|
||||
|
||||
const chain1 = buildConfigChain({ filename });
|
||||
const chain2 = buildConfigChain({ filename });
|
||||
|
||||
touch(pkgJSON);
|
||||
|
||||
const chain3 = buildConfigChain({ filename });
|
||||
const chain4 = buildConfigChain({ filename });
|
||||
|
||||
assert.equal(chain1.length, 3);
|
||||
assert.equal(chain2.length, 3);
|
||||
assert.equal(chain3.length, 3);
|
||||
assert.equal(chain4.length, 3);
|
||||
assert.equal(chain1[1].alias, pkgJSON);
|
||||
assert.equal(chain2[1].alias, pkgJSON);
|
||||
assert.equal(chain3[1].alias, pkgJSON);
|
||||
assert.equal(chain4[1].alias, pkgJSON);
|
||||
assert.strictEqual(chain1[1], chain2[1]);
|
||||
|
||||
// Identity changed after touch().
|
||||
assert.notStrictEqual(chain3[1], chain1[1]);
|
||||
assert.strictEqual(chain3[1], chain4[1]);
|
||||
});
|
||||
|
||||
it("should cache .babelrc files by mtime", () => {
|
||||
const filename = fixture(
|
||||
"complex-plugin-config",
|
||||
"config-identity",
|
||||
"babelrc",
|
||||
"src.js",
|
||||
);
|
||||
const babelrcFile = fixture(
|
||||
"complex-plugin-config",
|
||||
"config-identity",
|
||||
"babelrc",
|
||||
".babelrc",
|
||||
);
|
||||
|
||||
const chain1 = buildConfigChain({ filename });
|
||||
const chain2 = buildConfigChain({ filename });
|
||||
|
||||
touch(babelrcFile);
|
||||
|
||||
const chain3 = buildConfigChain({ filename });
|
||||
const chain4 = buildConfigChain({ filename });
|
||||
|
||||
assert.equal(chain1.length, 3);
|
||||
assert.equal(chain2.length, 3);
|
||||
assert.equal(chain3.length, 3);
|
||||
assert.equal(chain4.length, 3);
|
||||
assert.equal(chain1[1].alias, babelrcFile);
|
||||
assert.equal(chain2[1].alias, babelrcFile);
|
||||
assert.equal(chain3[1].alias, babelrcFile);
|
||||
assert.equal(chain4[1].alias, babelrcFile);
|
||||
assert.strictEqual(chain1[1], chain2[1]);
|
||||
|
||||
// Identity changed after touch().
|
||||
assert.notStrictEqual(chain3[1], chain1[1]);
|
||||
assert.strictEqual(chain3[1], chain4[1]);
|
||||
});
|
||||
|
||||
it("should cache .babelignore files by mtime", () => {
|
||||
const filename = fixture(
|
||||
"complex-plugin-config",
|
||||
"config-identity",
|
||||
"babelignore",
|
||||
"src.js",
|
||||
);
|
||||
const babelignoreFile = fixture(
|
||||
"complex-plugin-config",
|
||||
"config-identity",
|
||||
"babelignore",
|
||||
".babelignore",
|
||||
);
|
||||
|
||||
const chain1 = buildConfigChain({ filename });
|
||||
const chain2 = buildConfigChain({ filename });
|
||||
|
||||
touch(babelignoreFile);
|
||||
|
||||
const chain3 = buildConfigChain({ filename });
|
||||
const chain4 = buildConfigChain({ filename });
|
||||
|
||||
assert.equal(chain1.length, 6);
|
||||
assert.equal(chain2.length, 6);
|
||||
assert.equal(chain3.length, 6);
|
||||
assert.equal(chain4.length, 6);
|
||||
assert.equal(chain1[4].alias, babelignoreFile);
|
||||
assert.equal(chain2[4].alias, babelignoreFile);
|
||||
assert.equal(chain3[4].alias, babelignoreFile);
|
||||
assert.equal(chain4[4].alias, babelignoreFile);
|
||||
assert.strictEqual(chain1[4], chain2[4]);
|
||||
|
||||
// Identity changed after touch().
|
||||
assert.notStrictEqual(chain3[4], chain1[4]);
|
||||
assert.strictEqual(chain3[4], chain4[4]);
|
||||
});
|
||||
|
||||
it("should cache .babelrc.js files programmable behavior", () => {
|
||||
const filename = fixture(
|
||||
"complex-plugin-config",
|
||||
"config-identity",
|
||||
"babelrc-js",
|
||||
"src.js",
|
||||
);
|
||||
const babelrcFile = fixture(
|
||||
"complex-plugin-config",
|
||||
"config-identity",
|
||||
"babelrc-js",
|
||||
".babelrc.js",
|
||||
);
|
||||
|
||||
const chain1 = buildConfigChain({ filename });
|
||||
const chain2 = buildConfigChain({ filename });
|
||||
|
||||
process.env.NODE_ENV = "new-env";
|
||||
|
||||
const chain3 = buildConfigChain({ filename });
|
||||
const chain4 = buildConfigChain({ filename });
|
||||
|
||||
assert.equal(chain1.length, 3);
|
||||
assert.equal(chain2.length, 3);
|
||||
assert.equal(chain3.length, 3);
|
||||
assert.equal(chain4.length, 3);
|
||||
assert.equal(chain1[1].alias, babelrcFile);
|
||||
assert.equal(chain2[1].alias, babelrcFile);
|
||||
assert.equal(chain3[1].alias, babelrcFile);
|
||||
assert.equal(chain4[1].alias, babelrcFile);
|
||||
assert.strictEqual(chain1[1], chain2[1]);
|
||||
|
||||
// Identity changed after changing the NODE_ENV.
|
||||
assert.notStrictEqual(chain3[1], chain1[1]);
|
||||
assert.strictEqual(chain3[1], chain4[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("dir1", function() {
|
||||
const chain = buildConfigChain({
|
||||
filename: fixture("dir1", "src.js"),
|
||||
|
||||
@ -0,0 +1 @@
|
||||
fake-file.js
|
||||
@ -0,0 +1,7 @@
|
||||
module.exports = function(api) {
|
||||
api.env();
|
||||
|
||||
return {
|
||||
comments: false,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
comments: false,
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"babel": {
|
||||
"comments": false
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user