Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support configuring cache in ESM configs #15850

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
100 changes: 66 additions & 34 deletions packages/babel-core/src/config/files/configuration.ts
Original file line number Diff line number Diff line change
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(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diff is quite big, but the main logical difference is that now this cache is initialized before calling the function rather than before importing the module, since caches must be configured synchronously.

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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function (api) {
api.cache.never();

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