Skip to content

Commit

Permalink
plugin order
Browse files Browse the repository at this point in the history
  • Loading branch information
liuxingbaoyu committed Apr 24, 2024
1 parent 4d8b2d0 commit d0b198e
Show file tree
Hide file tree
Showing 51 changed files with 625 additions and 41 deletions.
88 changes: 87 additions & 1 deletion packages/babel-core/src/config/full.ts
Expand Up @@ -56,6 +56,88 @@ export type { Plugin };
export type PluginPassList = Array<Plugin>;
export type PluginPasses = Array<PluginPassList>;

function sortPlugins(plugins: Plugin[]) {
function stableSort(plugins: Plugin[], orderMap: Map<string, number>) {
const buckets = Object.create(null);

// By collecting into buckets, we can guarantee a stable sort.
for (let i = 0; i < plugins.length; i++) {
const n = plugins[i];
const p = 1000 - orderMap.get(n.key);

// In case some plugin is setting an unexpected priority.
const bucket = buckets[p] || (buckets[p] = []);
bucket.push(n);
}

// Sort our keys in descending order. Keys are unique, so we don't have to
// worry about stability.
const keys = Object.keys(buckets)
.map(k => +k)
.sort((a, b) => b - a);

let index = 0;
for (const key of keys) {
const bucket = buckets[key];
for (const n of bucket) {
plugins[index++] = n;
}
}
return plugins;
}

const orderDataListMap: Map<
string,
{
version: number;
data: () => string[];
plugins: Plugin[];
}
> = new Map();

const pluginsWithPadding: (Plugin | string)[] = [];

for (let i = plugins.length - 1; i >= 0; i--) {
const plugin = plugins[i];
const { orderData } = plugin;
if (orderData) {
let orderData2 = orderDataListMap.get(orderData.id);
if ((orderData2?.version || 0) < orderData.version) {
if (orderData2 == null) {
pluginsWithPadding.unshift(orderData.id);
}
orderDataListMap.set(
orderData.id,
(orderData2 = {
version: orderData.version,
data: () => orderData.data(),
plugins: [],
}),
);
}
orderData2.plugins.unshift(plugin);
} else {
pluginsWithPadding.unshift(plugin);
}
}

const newPlugins: Plugin[] = [];

for (const value of pluginsWithPadding) {
if (typeof value === "string") {
const orderData = orderDataListMap.get(value);
const map = new Map<string, number>(
orderData.data().map((key, i) => [key, i]),
);
newPlugins.push(...stableSort(orderData.plugins, map));
} else {
newPlugins.push(value);
}
}

return newPlugins;
}

export default gensync(function* loadFullConfig(
inputOpts: unknown,
): Handler<ResolvedConfig | null> {
Expand Down Expand Up @@ -168,7 +250,7 @@ export default gensync(function* loadFullConfig(

if (ignored) return null;

const opts: any = optionDefaults;
const opts: ValidatedOptions = optionDefaults;
mergeOptions(opts, options);

const pluginContext: Context.FullPlugin = {
Expand Down Expand Up @@ -211,6 +293,10 @@ export default gensync(function* loadFullConfig(
.map(plugins => ({ plugins }));
opts.passPerPreset = opts.presets.length > 0;

if (opts.sortPlugins && !opts.passPerPreset) {
opts.plugins = passes[0] = sortPlugins(opts.plugins as Plugin[]);
}

return {
options: opts,
passes: passes,
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-core/src/config/partial.ts
Expand Up @@ -112,6 +112,9 @@ export default function* loadPrivatePartialConfig(
const merged: ValidatedOptions = {
assumptions: {},
};

if (process.env.BABEL_8_BREAKING) merged.sortPlugins = true;

configChain.options.forEach(opts => {
mergeOptions(merged as any, opts);
});
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-core/src/config/plugin.ts
Expand Up @@ -16,6 +16,8 @@ export default class Plugin {

externalDependencies: ReadonlyDeepArray<string>;

orderData?: PluginObject["orderData"];

constructor(
plugin: PluginObject,
options: {},
Expand All @@ -30,6 +32,7 @@ export default class Plugin {
this.visitor = plugin.visitor || {};
this.parserOverride = plugin.parserOverride;
this.generatorOverride = plugin.generatorOverride;
this.orderData = plugin.orderData;

this.options = options;
this.externalDependencies = externalDependencies;
Expand Down
Expand Up @@ -219,7 +219,7 @@ export function assertBoolean(
export function assertObject(
loc: GeneralPath,
value: unknown,
): { readonly [key: string]: unknown } | void {
): { readonly [key: string]: unknown } | undefined {
if (
value !== undefined &&
(typeof value !== "object" || Array.isArray(value) || !value)
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-core/src/config/validation/options.ts
Expand Up @@ -56,6 +56,8 @@ const ROOT_VALIDATORS: ValidatorSet = {
cloneInputAst: assertBoolean as Validator<ValidatedOptions["cloneInputAst"]>,

envName: assertString as Validator<ValidatedOptions["envName"]>,

sortPlugins: assertBoolean as Validator<ValidatedOptions["sortPlugins"]>,
};

const BABELRC_VALIDATORS: ValidatorSet = {
Expand Down Expand Up @@ -192,6 +194,7 @@ export type ValidatedOptions = {
parserOpts?: ParserOptions;
// Deprecate top level generatorOpts
generatorOpts?: GeneratorOptions;
sortPlugins?: boolean;
};

export type NormalizedOptions = {
Expand Down
26 changes: 26 additions & 0 deletions packages/babel-core/src/config/validation/plugins.ts
Expand Up @@ -33,8 +33,27 @@ const VALIDATORS: ValidatorSet = {
generatorOverride: assertFunction as Validator<
PluginObject["generatorOverride"]
>,

orderData: assertOrderData as Validator<PluginObject["orderData"]>,
};

function assertOrderData(loc: OptionPath, value: unknown) {
const obj = assertObject(loc, value);
if (obj) {
if (typeof obj.id !== "string") {
throw new Error(
`${msg(loc)} must have an "id" property that is a string`,
);
}
if (typeof obj.version !== "number") {
throw new Error(
`${msg(loc)} must have a "version" property that is a number`,
);
}
}
return value as PluginObject["orderData"];
}

function assertVisitorMap(loc: OptionPath, value: unknown): Visitor {
const obj = assertObject(loc, value);
if (obj) {
Expand Down Expand Up @@ -95,6 +114,13 @@ export type PluginObject<S extends PluginPass = PluginPass> = {
visitor?: Visitor<S>;
parserOverride?: Function;
generatorOverride?: Function;
orderData?: {
id: string;
version: number;
} & {
type: "list";
data: () => string[];
};
};

export function validatePluginObject(obj: {
Expand Down
10 changes: 1 addition & 9 deletions packages/babel-core/src/transformation/index.ts
Expand Up @@ -15,6 +15,7 @@ import generateCode from "./file/generate.ts";
import type File from "./file/file.ts";

import { flattenToSet } from "../config/helpers/deep-array.ts";
import { isThenable } from "../gensync-utils/async.ts";

export type FileResultCallback = {
(err: Error, file: null): void;
Expand Down Expand Up @@ -145,12 +146,3 @@ function* transformFile(file: File, pluginPasses: PluginPasses): Handler<void> {
}
}
}

function isThenable<T extends PromiseLike<any>>(val: any): val is T {
return (
!!val &&
(typeof val === "object" || typeof val === "function") &&
!!val.then &&
typeof val.then === "function"
);
}
8 changes: 5 additions & 3 deletions packages/babel-core/test/api.js
Expand Up @@ -366,9 +366,11 @@ describe("api", function () {
plugins: ["@babel/plugin-syntax-jsx"],
});

expect(result.options.plugins[0].manipulateOptions.toString()).toEqual(
expect.stringContaining("jsx"),
);
expect(
result.options.plugins
.find(v => v.key === "syntax-jsx")
.manipulateOptions.toString(),
).toEqual(expect.stringContaining("jsx"));
});

it("option wrapPluginVisitorMethod", function () {
Expand Down
2 changes: 2 additions & 0 deletions packages/babel-core/test/config-chain.js
Expand Up @@ -4,6 +4,7 @@ import path from "path";
import { fileURLToPath } from "url";
import * as babel from "../lib/index.js";
import rimraf from "rimraf";
import { IS_BABEL_8 } from "$repo-utils";

import _getTargets from "@babel/helper-compilation-targets";
const getTargets = _getTargets.default || _getTargets;
Expand Down Expand Up @@ -1128,6 +1129,7 @@ describe("buildConfigChain", function () {
cloneInputAst: true,
targets: defaultTargets,
assumptions: {},
...(IS_BABEL_8() ? { sortPlugins: true } : {}),
});
const realEnv = process.env.NODE_ENV;
const realBabelEnv = process.env.BABEL_ENV;
Expand Down
@@ -0,0 +1,3 @@
declare class Foo {
static bar: string;
}
@@ -0,0 +1,6 @@
{
"presets": ["typescript"],
"plugins": ["transform-class-properties"],
"throws": "TypeScript 'declare' fields must first be transformed by @babel/plugin-transform-typescript.",
"BABEL_8_BREAKING": false
}
@@ -1,5 +1,7 @@
{
"presets": ["typescript"],
"plugins": ["transform-class-properties"],
"throws": "TypeScript 'declare' fields must first be transformed by @babel/plugin-transform-typescript."
"throws": "TypeScript 'declare' fields must first be transformed by @babel/plugin-transform-typescript.",
"sortPlugins": false,
"BABEL_8_BREAKING": true
}
104 changes: 102 additions & 2 deletions packages/babel-helper-plugin-utils/src/index.ts
Expand Up @@ -58,8 +58,108 @@ export function declare<State = {}, Option = {}>(
clonedApi[name] = apiPolyfills[name](clonedApi);
}

// @ts-expect-error options || {} may not be assigned to Options
return builder(clonedApi ?? api, options || {}, dirname);
const pluginObject = builder(
clonedApi ?? api,
options || ({} as Option),
dirname,
);

const orderList = [
"transform-modules-commonjs",
"transform-modules-amd",
"transform-modules-systemjs",
"transform-modules-umd",

"transform-typescript",
"transform-flow",

"proposal-async-do-expressions",
"proposal-decorators",
"proposal-destructuring-private",
"proposal-do-expressions",
"proposal-duplicate-named-capturing-groups-regex",
"proposal-explicit-resource-management",
"proposal-export-default-from",
"proposal-function-bind",
"proposal-function-sent",
"proposal-import-attributes-to-assertions",
"proposal-import-defer",
"proposal-import-wasm-source",
"proposal-json-modules",
"proposal-optional-chaining-assign",
"proposal-partial-application",
"proposal-pipeline-operator",
"proposal-record-and-tuple",
"proposal-regexp-modifiers",
"proposal-throw-expressions",

"transform-unicode-sets-regex",
"bugfix/transform-v8-static-class-fields-redefine-readonly",
"bugfix/transform-firefox-class-in-computed-class-key",
"transform-class-static-block",
"transform-private-property-in-object",
"transform-class-properties",
"transform-private-methods",
"transform-numeric-separator",
"transform-logical-assignment-operators",
"transform-nullish-coalescing-operator",
"transform-optional-chaining",
"transform-json-strings",
"transform-optional-catch-binding",
"transform-parameters",
"transform-async-generator-functions",
"transform-object-rest-spread",
"transform-dotall-regex",
"transform-unicode-property-regex",
"transform-named-capturing-groups-regex",
"transform-async-to-generator",
"transform-exponentiation-operator",
"transform-template-literals",
"transform-literals",
"transform-function-name",
"transform-arrow-functions",
"transform-block-scoped-functions",
"transform-classes",
"transform-object-super",
"transform-shorthand-properties",
"transform-duplicate-keys",
"transform-computed-properties",
"transform-for-of",
"transform-sticky-regex",
"transform-unicode-escapes",
"transform-unicode-regex",
"transform-spread",
"transform-destructuring",
"transform-block-scoping",
"transform-typeof-symbol",
"transform-new-target",
"transform-regenerator",
"transform-member-expression-literals",
"transform-property-literals",
"transform-reserved-words",
"transform-export-namespace-from",
"bugfix/transform-async-arrows-in-class",
"bugfix/transform-edge-default-parameters",
"bugfix/transform-edge-function-name",
"bugfix/transform-safari-block-shadowing",
"bugfix/transform-safari-for-shadowing",
"bugfix/transform-safari-id-destructuring-collision-in-function-expression",
"bugfix/transform-tagged-template-caching",
"bugfix/transform-v8-spread-parameters-in-optional-chaining",
];

if (process.env.BABEL_8_BREAKING && orderList.includes(pluginObject.name)) {
pluginObject.orderData = {
id: "@babel/helper-plugin-order-data",
version: 1,
type: "list",
data: () => {
return orderList;
},
};
}

return pluginObject;
};
}

Expand Down
@@ -0,0 +1,4 @@
class C {
#x;
constructor(public foo, { #x: x}) {}
}
@@ -0,0 +1,7 @@
{
"plugins": ["proposal-destructuring-private", "transform-typescript"],
"throws": [
"TypeScript features must first be transformed by @babel/plugin-transform-typescript."
],
"BABEL_8_BREAKING": false
}

0 comments on commit d0b198e

Please sign in to comment.