Skip to content

Commit

Permalink
Support configuring cache in ESM configs (#15850)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Aug 8, 2023
1 parent e7b83da commit 7aa23aa
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 36 deletions.
100 changes: 66 additions & 34 deletions packages/babel-core/src/config/files/configuration.ts
Expand Up @@ -4,15 +4,15 @@ import path from "path";
import json5 from "json5";
import gensync from "gensync";
import type { Handler } from "gensync";
import { makeStrongCache, makeWeakCacheSync } from "../caching";
import { makeWeakCache, makeWeakCacheSync } from "../caching";
import type { CacheConfigurator } from "../caching";
import { makeConfigAPI } from "../helpers/config-api";
import type { ConfigAPI } from "../helpers/config-api";
import { makeStaticFileCache } from "./utils";
import loadCodeDefault from "./module-types";
import pathPatternToRegex from "../pattern-to-regex";
import type { FilePackageData, RelativeConfig, ConfigFile } from "./types";
import type { CallerMetadata } from "../validation/options";
import type { CallerMetadata, InputOptions } from "../validation/options";
import ConfigError from "../../errors/config-error";

import * as fs from "../../gensync-utils/fs";
Expand Down Expand Up @@ -43,30 +43,41 @@ const BABELIGNORE_FILENAME = ".babelignore";

const LOADING_CONFIGS = new Set();

const readConfigCode = makeStrongCache(function* readConfigCode(
type ConfigCacheData = {
envName: string;
caller: CallerMetadata | undefined;
};

const runConfig = makeWeakCache(function* runConfig(
options: Function,
cache: CacheConfigurator<ConfigCacheData>,
): Handler<{
options: InputOptions | null;
cacheNeedsConfiguration: boolean;
}> {
// @ts-expect-error - if we want to make it possible to use async configs
yield* [];

return {
options: endHiddenCallStack(options as any as (api: ConfigAPI) => {})(
makeConfigAPI(cache),
),
cacheNeedsConfiguration: !cache.configured(),
};
});

function* readConfigCode(
filepath: string,
cache: CacheConfigurator<{
envName: string;
caller: CallerMetadata | undefined;
}>,
data: ConfigCacheData,
): Handler<ConfigFile | null> {
if (!nodeFs.existsSync(filepath)) {
cache.never();
return null;
}
if (!nodeFs.existsSync(filepath)) return null;

// The `require()` call below can make this code reentrant if a require hook like @babel/register has been
// loaded into the system. That would cause Babel to attempt to compile the `.babelrc.js` file as it loads
// below. To cover this case, we auto-ignore re-entrant config processing.
if (LOADING_CONFIGS.has(filepath)) {
cache.never();

debug("Auto-ignoring usage of config %o.", filepath);
return {
filepath,
dirname: path.dirname(filepath),
options: {},
};
return buildConfigFileObject({}, filepath);
}

let options: unknown;
Expand All @@ -81,16 +92,9 @@ const readConfigCode = makeStrongCache(function* readConfigCode(
LOADING_CONFIGS.delete(filepath);
}

let assertCache = false;
let cacheNeedsConfiguration = false;
if (typeof options === "function") {
// @ts-expect-error - if we want to make it possible to use async configs
yield* [];

options = endHiddenCallStack(options as any as (api: ConfigAPI) => {})(
makeConfigAPI(cache),
);

assertCache = true;
({ options, cacheNeedsConfiguration } = yield* runConfig(options, data));
}

if (!options || typeof options !== "object" || Array.isArray(options)) {
Expand All @@ -102,6 +106,10 @@ const readConfigCode = makeStrongCache(function* readConfigCode(

// @ts-expect-error todo(flow->ts)
if (typeof options.then === "function") {
// @ts-expect-error We use ?. in case options is a thenable
// but not a promise
options.catch?.(() => {});

throw new ConfigError(
`You appear to be using an async configuration, ` +
`which your current version of Babel does not support. ` +
Expand All @@ -112,14 +120,38 @@ const readConfigCode = makeStrongCache(function* readConfigCode(
);
}

if (assertCache && !cache.configured()) throwConfigError(filepath);
if (cacheNeedsConfiguration) throwConfigError(filepath);

return {
filepath,
dirname: path.dirname(filepath),
options,
};
});
return buildConfigFileObject(options, filepath);
}

// We cache the generated ConfigFile object rather than creating a new one
// every time, so that it can be used as a cache key in other functions.
const cfboaf /* configFilesByOptionsAndFilepath */ = new WeakMap<
InputOptions,
Map<string, ConfigFile>
>();
function buildConfigFileObject(
options: InputOptions,
filepath: string,
): ConfigFile {
let configFilesByFilepath = cfboaf.get(options);
if (!configFilesByFilepath) {
cfboaf.set(options, (configFilesByFilepath = new Map()));
}

let configFile = configFilesByFilepath.get(filepath);
if (!configFile) {
configFile = {
filepath,
dirname: path.dirname(filepath),
options,
};
configFilesByFilepath.set(filepath, configFile);
}

return configFile;
}

const packageToBabelConfig = makeWeakCacheSync(
(file: ConfigFile): ConfigFile | null => {
Expand Down
9 changes: 9 additions & 0 deletions packages/babel-core/test/async.js
Expand Up @@ -6,6 +6,7 @@ import {
spawnTransformAsync,
spawnTransformSync,
supportsESM,
itESM,
} from "./helpers/esm.js";

const nodeGte8 = (...args) => {
Expand Down Expand Up @@ -115,6 +116,14 @@ describe("asynchronicity", () => {
);
});
});

itESM("mjs configuring cache", async () => {
process.chdir("config-file-mjs-cache");

const { code } = await babel.transformAsync("");

expect(code).toBe(`"success"`);
});
});

describe("plugin", () => {
Expand Down
Expand Up @@ -3,8 +3,6 @@ const wait = t => new Promise(r => setTimeout(r, t));
module.exports = async function(api) {
await wait(50);

api.cache.never();

return {
plugins: ["./plugin"],
};
Expand Down
@@ -0,0 +1,7 @@
export default function (api) {
api.cache.never();

return {
plugins: ["./plugin"],
};
}
@@ -0,0 +1,9 @@
module.exports = function plugin({ types: t }) {
return {
visitor: {
Program(path) {
path.pushContainer("body", t.stringLiteral("success"));
},
},
};
};

0 comments on commit 7aa23aa

Please sign in to comment.