@rolldown/plugin-babel 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,56 @@
1
+ import * as babel from "@babel/core";
2
+ import { GeneralHookFilter, ModuleTypeFilter, Plugin } from "rolldown";
3
+ import { Plugin as Plugin$1, ResolvedConfig } from "vite";
4
+
5
+ //#region src/rolldownPreset.d.ts
6
+ type PartialEnvironment = Parameters<NonNullable<Plugin$1['applyToEnvironment']>>[0];
7
+ type RolldownBabelPreset = {
8
+ preset: babel.PresetItem;
9
+ rolldown: {
10
+ filter?: {
11
+ id?: GeneralHookFilter;
12
+ moduleType?: ModuleTypeFilter;
13
+ code?: GeneralHookFilter;
14
+ };
15
+ applyToEnvironmentHook?: (environment: PartialEnvironment) => boolean;
16
+ configResolvedHook?: (config: ResolvedConfig) => boolean;
17
+ };
18
+ };
19
+ type RolldownBabelPresetItem = babel.PresetItem | RolldownBabelPreset;
20
+ declare function defineRolldownBabelPreset(preset: RolldownBabelPreset): RolldownBabelPreset;
21
+ //#endregion
22
+ //#region src/options.d.ts
23
+ interface InnerTransformOptions extends Pick<babel.InputOptions, 'assumptions' | 'auxiliaryCommentAfter' | 'auxiliaryCommentBefore' | 'exclude' | 'comments' | 'compact' | 'cwd' | 'generatorOpts' | 'include' | 'parserOpts' | 'plugins' | 'retainLines' | 'shouldPrintComment' | 'targets' | 'wrapPluginVisitorMethod'> {
24
+ /**
25
+ * List of presets (a set of plugins) to load and use
26
+ *
27
+ * Default: `[]`
28
+ */
29
+ presets?: RolldownBabelPresetItem[] | undefined;
30
+ }
31
+ interface PluginOptions extends Omit<InnerTransformOptions, 'include' | 'exclude'> {
32
+ /**
33
+ * If specified, only files matching the pattern will be processed by babel.
34
+ */
35
+ include?: InnerTransformOptions['include'];
36
+ /**
37
+ * If any of patterns match, babel will not process the file.
38
+ * @default `/[\/\\]node_modules[\/\\]/`
39
+ */
40
+ exclude?: InnerTransformOptions['exclude'];
41
+ /**
42
+ * If false, skips source map generation. This will improve performance.
43
+ * @default true
44
+ */
45
+ sourceMap?: boolean;
46
+ /**
47
+ * Allows users to provide an array of options that will be merged into the current configuration one at a time.
48
+ * This feature is best used alongside the "test"/"include"/"exclude" options to provide conditions for which an override should apply
49
+ */
50
+ overrides?: InnerTransformOptions[] | undefined;
51
+ }
52
+ //#endregion
53
+ //#region src/index.d.ts
54
+ declare function babelPlugin(rawOptions: PluginOptions): Promise<Plugin>;
55
+ //#endregion
56
+ export { type RolldownBabelPreset, babelPlugin as default, defineRolldownBabelPreset };
package/dist/index.mjs ADDED
@@ -0,0 +1,360 @@
1
+ import picomatch from "picomatch";
2
+ import * as babel from "@babel/core";
3
+
4
+ //#region src/utils.ts
5
+ function arrayify(value) {
6
+ return Array.isArray(value) ? value : [value];
7
+ }
8
+ function filterMap(array, predicate) {
9
+ const newArray = [];
10
+ for (const [index, item] of array.entries()) {
11
+ const result = predicate(item, index);
12
+ if (result) newArray.push(result.value);
13
+ }
14
+ return newArray;
15
+ }
16
+
17
+ //#endregion
18
+ //#region src/rolldownPreset.ts
19
+ function compilePattern(pattern) {
20
+ if (pattern instanceof RegExp) return (value) => pattern.test(value);
21
+ return picomatch(pattern);
22
+ }
23
+ function compilePatterns(patterns) {
24
+ const matchers = patterns.map(compilePattern);
25
+ return (value) => matchers.some((m) => m(value));
26
+ }
27
+ /**
28
+ * Pre-compile a GeneralHookFilter into a single matcher function.
29
+ * Returns undefined when the filter matches everything.
30
+ */
31
+ function compileGeneralHookFilter(filter) {
32
+ if (filter == null) return void 0;
33
+ let include;
34
+ let exclude;
35
+ if (typeof filter === "string" || filter instanceof RegExp) include = [filter];
36
+ else if (Array.isArray(filter)) include = filter;
37
+ else {
38
+ include = filter.include != null ? arrayify(filter.include) : void 0;
39
+ exclude = filter.exclude != null ? arrayify(filter.exclude) : void 0;
40
+ }
41
+ const includeMatcher = include ? compilePatterns(include) : void 0;
42
+ const excludeMatcher = exclude ? compilePatterns(exclude) : void 0;
43
+ if (includeMatcher && excludeMatcher) return (value) => !excludeMatcher(value) && includeMatcher(value);
44
+ if (excludeMatcher) return (value) => !excludeMatcher(value);
45
+ return includeMatcher;
46
+ }
47
+ function compileModuleTypeFilter(filter) {
48
+ if (filter == null) return void 0;
49
+ const types = Array.isArray(filter) ? filter : filter.include ?? [];
50
+ if (types.length === 0) return void 0;
51
+ const typeSet = new Set(types);
52
+ return (value) => typeSet.has(value);
53
+ }
54
+ /**
55
+ * Pre-compile a preset's filter into a single matcher function
56
+ * that checks all dimensions (id, moduleType, code) at once.
57
+ * Returns undefined when the filter matches everything.
58
+ */
59
+ function compilePresetFilter(filter) {
60
+ if (!filter) return void 0;
61
+ const matchId = compileGeneralHookFilter(filter.id);
62
+ const matchModuleType = compileModuleTypeFilter(filter.moduleType);
63
+ const matchCode = compileGeneralHookFilter(filter.code);
64
+ if (!matchId && !matchModuleType && !matchCode) return void 0;
65
+ return (ctx) => (!matchId || matchId(ctx.id)) && (!matchModuleType || matchModuleType(ctx.moduleType)) && (!matchCode || matchCode(ctx.code));
66
+ }
67
+ function defineRolldownBabelPreset(preset) {
68
+ return preset;
69
+ }
70
+ function convertToBabelPresetItem(ctx, preset, compiledFilter) {
71
+ if (typeof preset !== "object" || !("rolldown" in preset)) return { value: preset };
72
+ if (compiledFilter && !compiledFilter(ctx)) return void 0;
73
+ return { value: preset.preset };
74
+ }
75
+
76
+ //#endregion
77
+ //#region src/options.ts
78
+ function resolveOptions(options) {
79
+ return {
80
+ ...options,
81
+ exclude: options.exclude ?? [/[/\\]node_modules[/\\]/],
82
+ sourceMap: options.sourceMap ?? true
83
+ };
84
+ }
85
+ function compilePresetFilters(presets) {
86
+ return presets.map((preset) => typeof preset === "object" && "rolldown" in preset ? compilePresetFilter(preset.rolldown.filter) : void 0);
87
+ }
88
+ function filterPresetArrayWithEnvironment(presets, environment) {
89
+ return presets.filter((preset) => {
90
+ if (typeof preset !== "object" || !("rolldown" in preset)) return true;
91
+ if (!preset.rolldown.applyToEnvironmentHook) return true;
92
+ return preset.rolldown.applyToEnvironmentHook(environment);
93
+ });
94
+ }
95
+ function filterPresetsWithEnvironment(options, environment) {
96
+ return {
97
+ ...options,
98
+ presets: options.presets ? filterPresetArrayWithEnvironment(options.presets, environment) : void 0,
99
+ overrides: options.overrides?.map((override) => override.presets ? {
100
+ ...override,
101
+ presets: filterPresetArrayWithEnvironment(override.presets, environment)
102
+ } : override)
103
+ };
104
+ }
105
+ function filterPresetArray(presets, config) {
106
+ return presets.filter((preset) => {
107
+ if (typeof preset !== "object" || !("rolldown" in preset)) return true;
108
+ if (!preset.rolldown.configResolvedHook) return true;
109
+ return preset.rolldown.configResolvedHook(config);
110
+ });
111
+ }
112
+ function filterPresetsWithConfigResolved(options, config) {
113
+ return {
114
+ ...options,
115
+ presets: options.presets ? filterPresetArray(options.presets, config) : void 0,
116
+ overrides: options.overrides?.map((override) => override.presets ? {
117
+ ...override,
118
+ presets: filterPresetArray(override.presets, config)
119
+ } : override)
120
+ };
121
+ }
122
+ /**
123
+ * Pre-compile all preset filters and return a function that
124
+ * converts options to babel options for a given context.
125
+ */
126
+ function createBabelOptionsConverter(options) {
127
+ const presetFilters = options.presets ? compilePresetFilters(options.presets) : void 0;
128
+ const overridePresetFilters = options.overrides?.map((override) => override.presets ? compilePresetFilters(override.presets) : void 0);
129
+ return function(ctx) {
130
+ return {
131
+ ...options,
132
+ presets: options.presets ? filterMap(options.presets, (preset, i) => convertToBabelPresetItem(ctx, preset, presetFilters[i])) : void 0,
133
+ overrides: options.overrides?.map((override, i) => override.presets ? {
134
+ ...override,
135
+ presets: filterMap(override.presets, (preset, j) => convertToBabelPresetItem(ctx, preset, overridePresetFilters[i][j]))
136
+ } : override)
137
+ };
138
+ };
139
+ }
140
+
141
+ //#endregion
142
+ //#region src/filter.ts
143
+ /**
144
+ * Extract string/RegExp values from babel's ConfigApplicableTest,
145
+ * filtering out function entries which HookFilter can't represent.
146
+ * If any function entry is present, returns undefined because the
147
+ * function could match anything we can't predict at the HookFilter level.
148
+ */
149
+ function extractStringOrRegExp(test) {
150
+ if (test === void 0) return void 0;
151
+ const items = arrayify(test);
152
+ const result = [];
153
+ for (const item of items) {
154
+ if (typeof item === "function") return;
155
+ result.push(item);
156
+ }
157
+ return result.length > 0 ? result : void 0;
158
+ }
159
+ /**
160
+ * Normalize a GeneralHookFilter into { include?, exclude? } form.
161
+ */
162
+ function normalizeGeneralHookFilter(filter) {
163
+ if (filter == null) return {};
164
+ if (typeof filter === "string" || filter instanceof RegExp) return { include: [filter] };
165
+ if (Array.isArray(filter)) return { include: filter };
166
+ return {
167
+ include: filter.include != null ? arrayify(filter.include) : void 0,
168
+ exclude: filter.exclude != null ? arrayify(filter.exclude) : void 0
169
+ };
170
+ }
171
+ function isRolldownBabelPreset(preset) {
172
+ return typeof preset === "object" && preset !== null && "rolldown" in preset;
173
+ }
174
+ function normalizeModuleTypeFilter(filter) {
175
+ if (Array.isArray(filter)) return filter;
176
+ return filter.include ?? [];
177
+ }
178
+ function patternKey(pattern) {
179
+ return typeof pattern === "string" ? `s:${pattern}` : `r:${pattern.source}:${pattern.flags}`;
180
+ }
181
+ /**
182
+ * Compute the intersection of arrays by key.
183
+ * An item is kept only if it appears in every array.
184
+ * If any array is undefined, the intersection is empty.
185
+ */
186
+ function intersectArrays(arrays, keyFn) {
187
+ if (arrays.length === 0) return [];
188
+ const defined = arrays.filter((a) => a != null);
189
+ if (defined.length < arrays.length) return [];
190
+ let result = new Map(defined[0].map((p) => [keyFn(p), p]));
191
+ for (let i = 1; i < defined.length; i++) {
192
+ const keys = new Set(defined[i].map((p) => keyFn(p)));
193
+ for (const key of result.keys()) if (!keys.has(key)) result.delete(key);
194
+ }
195
+ return [...result.values()];
196
+ }
197
+ function concatArrays(a, b) {
198
+ if (!a) return b;
199
+ if (!b) return a;
200
+ return [...a, ...b];
201
+ }
202
+ /**
203
+ * Union filter values from multiple presets for a single dimension.
204
+ * Includes are unioned (OR). Excludes are intersected (only items in ALL presets are kept).
205
+ *
206
+ * @param rawFilters Per-preset filter values. undefined = no filter (matches everything).
207
+ * @param normalize Converts a raw filter into { include?, exclude? } arrays.
208
+ * @param keyFn Serializes an item for intersection comparison.
209
+ */
210
+ function unionFilters(rawFilters, normalize, keyFn) {
211
+ let matchAll = false;
212
+ const includes = [];
213
+ const excludeArrays = [];
214
+ for (const raw of rawFilters) {
215
+ if (raw === void 0) {
216
+ matchAll = true;
217
+ excludeArrays.push(void 0);
218
+ continue;
219
+ }
220
+ const n = normalize(raw);
221
+ if (!matchAll) if (n.include) includes.push(...n.include);
222
+ else matchAll = true;
223
+ excludeArrays.push(n.exclude);
224
+ }
225
+ return {
226
+ includes: matchAll ? void 0 : includes.length > 0 ? includes : void 0,
227
+ excludes: intersectArrays(excludeArrays, keyFn)
228
+ };
229
+ }
230
+ /**
231
+ * Build the transform hook filter by intersecting a baseFilter (from user
232
+ * include/exclude options) with a presetFilter (union of all RolldownBabelPreset
233
+ * filters).
234
+ *
235
+ * - baseFilter constrains by id only (include/exclude from user options).
236
+ * - presetFilter constrains by id, moduleType, and code. Includes are unioned
237
+ * across presets (OR), excludes are intersected (only patterns in ALL presets).
238
+ * - The result uses user includes when present, otherwise falls back to preset
239
+ * includes. Excludes are combined from both (excluded by either → excluded).
240
+ * - If the user has explicit plugins, presetFilter is skipped (plugins can match
241
+ * any file). Same if any preset is a plain babel preset without rolldown filters.
242
+ */
243
+ function calculateTransformFilter(options) {
244
+ const userInclude = extractStringOrRegExp(options.include);
245
+ const userExclude = extractStringOrRegExp(options.exclude);
246
+ const baseFilter = { id: {
247
+ include: userInclude,
248
+ exclude: userExclude
249
+ } };
250
+ if (options.plugins && options.plugins.length > 0) return baseFilter;
251
+ const presets = options.presets;
252
+ if (!presets || presets.length === 0) return baseFilter;
253
+ for (const preset of presets) if (!isRolldownBabelPreset(preset) || !preset.rolldown.filter) return baseFilter;
254
+ const filters = presets.map((p) => p.rolldown.filter);
255
+ const idUnion = unionFilters(filters.map((f) => f.id), normalizeGeneralHookFilter, patternKey);
256
+ const moduleTypeUnion = unionFilters(filters.map((f) => f.moduleType), (f) => {
257
+ const types = normalizeModuleTypeFilter(f);
258
+ return { include: types.length > 0 ? types : void 0 };
259
+ }, (s) => s);
260
+ const codeUnion = unionFilters(filters.map((f) => f.code), normalizeGeneralHookFilter, patternKey);
261
+ const finalFilter = {};
262
+ const finalFilterIdInclude = userInclude ?? idUnion.includes;
263
+ const finalFilterIdExclude = concatArrays(userExclude, idUnion.excludes.length > 0 ? idUnion.excludes : void 0);
264
+ if (finalFilterIdInclude || finalFilterIdExclude) finalFilter.id = {
265
+ include: finalFilterIdInclude,
266
+ exclude: finalFilterIdExclude
267
+ };
268
+ if (moduleTypeUnion.includes) finalFilter.moduleType = moduleTypeUnion.includes;
269
+ if (codeUnion.includes) {
270
+ const finalFilterCodeExclude = codeUnion.excludes.length > 0 ? codeUnion.excludes : void 0;
271
+ if (finalFilterCodeExclude) finalFilter.code = {
272
+ include: codeUnion.includes,
273
+ exclude: finalFilterCodeExclude
274
+ };
275
+ else finalFilter.code = codeUnion.includes;
276
+ }
277
+ return finalFilter;
278
+ }
279
+ /**
280
+ * Calculate the filters to apply to the plugin
281
+ */
282
+ function calculatePluginFilters(options) {
283
+ return { transformFilter: calculateTransformFilter(options) };
284
+ }
285
+
286
+ //#endregion
287
+ //#region src/index.ts
288
+ async function babelPlugin(rawOptions) {
289
+ let configFilteredOptions;
290
+ const envState = /* @__PURE__ */ new Map();
291
+ const plugin = {
292
+ name: "@rolldown/plugin-babel",
293
+ configResolved(config) {
294
+ configFilteredOptions = filterPresetsWithConfigResolved(rawOptions, config);
295
+ const resolved = resolveOptions(configFilteredOptions);
296
+ plugin.transform.filter = calculatePluginFilters(resolved).transformFilter;
297
+ },
298
+ applyToEnvironment(environment) {
299
+ const envOptions = filterPresetsWithEnvironment(configFilteredOptions, environment);
300
+ if (!envOptions.presets?.length && !envOptions.plugins?.length && !envOptions.overrides?.some((o) => o.presets?.length || o.plugins?.length)) return false;
301
+ const resolved = resolveOptions(envOptions);
302
+ envState.set(environment.name, createBabelOptionsConverter(resolved));
303
+ return true;
304
+ },
305
+ outputOptions() {
306
+ if (this.meta.viteVersion) return;
307
+ const resolved = resolveOptions(rawOptions);
308
+ envState.set(void 0, createBabelOptionsConverter(resolved));
309
+ plugin.transform.filter = calculatePluginFilters(resolved).transformFilter;
310
+ },
311
+ transform: {
312
+ filter: void 0,
313
+ async handler(code, id, opts) {
314
+ const convertToBabelOptions = envState.get(this.environment?.name);
315
+ if (!convertToBabelOptions) return;
316
+ const babelOptions = convertToBabelOptions({
317
+ id,
318
+ moduleType: opts?.moduleType ?? "js",
319
+ code
320
+ });
321
+ const loadedOptions = await babel.loadOptionsAsync({
322
+ ...babelOptions,
323
+ babelrc: false,
324
+ configFile: false,
325
+ parserOpts: {
326
+ sourceType: "module",
327
+ allowAwaitOutsideFunction: true,
328
+ ...babelOptions.parserOpts
329
+ },
330
+ overrides: [
331
+ {
332
+ test: "**/*.jsx",
333
+ parserOpts: { plugins: ["jsx"] }
334
+ },
335
+ {
336
+ test: "**/*.ts",
337
+ parserOpts: { plugins: ["typescript"] }
338
+ },
339
+ {
340
+ test: "**/*.tsx",
341
+ parserOpts: { plugins: ["typescript", "jsx"] }
342
+ },
343
+ ...babelOptions.overrides ?? []
344
+ ],
345
+ filename: id
346
+ });
347
+ if (!loadedOptions || loadedOptions.plugins.length === 0) return;
348
+ const result = await babel.transformAsync(code, loadedOptions);
349
+ if (result) return {
350
+ code: result.code ?? void 0,
351
+ map: result.map
352
+ };
353
+ }
354
+ }
355
+ };
356
+ return plugin;
357
+ }
358
+
359
+ //#endregion
360
+ export { babelPlugin as default, defineRolldownBabelPreset };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rolldown/plugin-babel",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Rolldown plugin for Babel",
5
5
  "keywords": [
6
6
  "babel",