diff --git a/.changeset/odd-lies-know.md b/.changeset/odd-lies-know.md new file mode 100644 index 0000000000..e44dde9b26 --- /dev/null +++ b/.changeset/odd-lies-know.md @@ -0,0 +1,5 @@ +--- +'@rsbuild/plugin-babel': patch +--- + +feat(plugin-babel): add include and exclude option diff --git a/e2e/cases/babel/build.test.ts b/e2e/cases/babel/build.test.ts index 7c380ed766..e902932f8c 100644 --- a/e2e/cases/babel/build.test.ts +++ b/e2e/cases/babel/build.test.ts @@ -26,11 +26,15 @@ rspackOnlyTest('babel exclude', async ({ page }) => { const rsbuild = await build({ cwd: __dirname, runServer: true, + rsbuildConfig: { + source: { + exclude: [/aa/], + }, + }, plugins: [ pluginBabel({ - babelLoaderOptions: (_, { addPlugins, addExcludes }) => { + babelLoaderOptions: (_, { addPlugins }) => { addPlugins([require('./plugins/myBabelPlugin')]); - addExcludes(/aa/); }, }), ], diff --git a/e2e/cases/vue/jsx-basic/rsbuild.config.ts b/e2e/cases/vue/jsx-basic/rsbuild.config.ts index 3c933b2d01..509ee4c1a0 100644 --- a/e2e/cases/vue/jsx-basic/rsbuild.config.ts +++ b/e2e/cases/vue/jsx-basic/rsbuild.config.ts @@ -4,5 +4,11 @@ import { pluginVueJsx } from '@rsbuild/plugin-vue-jsx'; import { pluginBabel } from '@rsbuild/plugin-babel'; export default defineConfig({ - plugins: [pluginVue(), pluginVueJsx(), pluginBabel()], + plugins: [ + pluginVue(), + pluginVueJsx(), + pluginBabel({ + include: /\.(jsx|tsx)$/, + }), + ], }); diff --git a/packages/plugin-babel/src/helper.ts b/packages/plugin-babel/src/helper.ts index f25e8c81ef..70352ee807 100644 --- a/packages/plugin-babel/src/helper.ts +++ b/packages/plugin-babel/src/helper.ts @@ -12,9 +12,12 @@ import type { BabelConfigUtils, PresetEnvOptions, PresetReactOptions, - BabelPluginOptions, BabelTransformOptions, + PluginBabelOptions, } from './types'; +import type { PluginOptions as BabelPluginOptions } from '@babel/core'; + +export const BABEL_JS_RULE = 'babel-js'; const normalizeToPosixPath = (p: string | undefined) => upath @@ -126,6 +129,7 @@ export const getBabelUtils = ( ): BabelConfigUtils => { // eslint-disable-next-line @typescript-eslint/no-empty-function const noop = () => {}; + return { addPlugins: (plugins: BabelPlugin[]) => addPlugins(plugins, config), addPresets: (presets: BabelPlugin[]) => addPresets(presets, config), @@ -137,7 +141,7 @@ export const getBabelUtils = ( // It can be overridden by `extraBabelUtils`. addIncludes: noop, addExcludes: noop, - // Compat `presetEnvOptions` and `presetReactOptions` in Eden. + // Compat `presetEnvOptions` and `presetReactOptions` in Modern.js modifyPresetEnvOptions: (options: PresetEnvOptions) => modifyPresetOptions('@babel/preset-env', options, config.presets || []), modifyPresetReactOptions: (options: PresetReactOptions) => @@ -145,16 +149,9 @@ export const getBabelUtils = ( }; }; -export type BabelConfig = - | BabelTransformOptions - | (( - config: BabelTransformOptions, - utils: BabelConfigUtils, - ) => BabelTransformOptions | void); - export const applyUserBabelConfig = ( defaultOptions: BabelTransformOptions, - userBabelConfig?: BabelConfig | BabelConfig[], + userBabelConfig?: PluginBabelOptions['babelLoaderOptions'], extraBabelUtils?: Partial, ): BabelTransformOptions => { if (userBabelConfig) { @@ -190,7 +187,7 @@ export const modifyBabelLoaderOptions = ({ CHAIN_ID: ChainIdentifier; modifier: (config: BabelTransformOptions) => BabelTransformOptions; }) => { - const ruleIds = [CHAIN_ID.RULE.JS, CHAIN_ID.RULE.JS_DATA_URI]; + const ruleIds = [CHAIN_ID.RULE.JS, CHAIN_ID.RULE.JS_DATA_URI, BABEL_JS_RULE]; ruleIds.forEach((ruleId) => { if (chain.module.rules.has(ruleId)) { diff --git a/packages/plugin-babel/src/plugin.ts b/packages/plugin-babel/src/plugin.ts index dc86e6ccf9..06835a6ab5 100644 --- a/packages/plugin-babel/src/plugin.ts +++ b/packages/plugin-babel/src/plugin.ts @@ -1,7 +1,7 @@ import path from 'path'; import type { RsbuildPlugin } from '@rsbuild/core'; -import { cloneDeep, SCRIPT_REGEX } from '@rsbuild/shared'; -import { applyUserBabelConfig, type BabelConfig } from './helper'; +import { castArray, cloneDeep, SCRIPT_REGEX } from '@rsbuild/shared'; +import { applyUserBabelConfig, BABEL_JS_RULE } from './helper'; import type { PluginBabelOptions } from './types'; /** @@ -25,27 +25,6 @@ export const pluginBabel = ( setup(api) { api.modifyBundlerChain(async (chain, { CHAIN_ID, isProd }) => { const getBabelOptions = () => { - // 1. Create babel utils function about include/exclude, - const includes: Array = []; - const excludes: Array = []; - - const babelUtils = { - addIncludes(items: string | RegExp | Array) { - if (Array.isArray(items)) { - includes.push(...items); - } else { - includes.push(items); - } - }, - addExcludes(items: string | RegExp | Array) { - if (Array.isArray(items)) { - excludes.push(...items); - } else { - excludes.push(items); - } - }, - }; - const baseConfig = { plugins: [], presets: [ @@ -60,42 +39,62 @@ export const pluginBabel = ( const userBabelConfig = applyUserBabelConfig( cloneDeep(baseConfig), options.babelLoaderOptions, - babelUtils, ); - const babelOptions: BabelConfig = { + return { babelrc: false, configFile: false, compact: isProd, ...userBabelConfig, }; - - return { - babelOptions, - includes, - excludes, - }; }; - const { babelOptions, includes = [], excludes = [] } = getBabelOptions(); + const babelOptions = getBabelOptions(); + const babelLoader = path.resolve( + __dirname, + '../compiled/babel-loader/index.js', + ); + const { include, exclude } = options; - // already set source.include / exclude in plugin-swc - const rule = chain.module.rule(CHAIN_ID.RULE.JS); + if (include || exclude) { + const rule = chain.module.rule(BABEL_JS_RULE); - includes.forEach((condition) => { - rule.include.add(condition); - }); + if (include) { + castArray(include).forEach((condition) => { + rule.include.add(condition); + }); + } + if (exclude) { + castArray(exclude).forEach((condition) => { + rule.exclude.add(condition); + }); + } - excludes.forEach((condition) => { - rule.exclude.add(condition); - }); + const swcRule = chain.module.rules + .get(CHAIN_ID.RULE.JS) + .use(CHAIN_ID.USE.SWC); + const swcLoader = swcRule.get('loader'); + const swcOptions = swcRule.get('options'); - rule - .test(SCRIPT_REGEX) - .use(CHAIN_ID.USE.BABEL) - .after(CHAIN_ID.USE.SWC) - .loader(path.resolve(__dirname, '../compiled/babel-loader/index.js')) - .options(babelOptions); + rule + .test(SCRIPT_REGEX) + .use(CHAIN_ID.USE.SWC) + .loader(swcLoader) + .options(swcOptions) + .end() + .use(CHAIN_ID.USE.BABEL) + .loader(babelLoader) + .options(babelOptions); + } else { + // already set source.include / exclude in plugin-swc + const rule = chain.module.rule(CHAIN_ID.RULE.JS); + rule + .test(SCRIPT_REGEX) + .use(CHAIN_ID.USE.BABEL) + .after(CHAIN_ID.USE.SWC) + .loader(babelLoader) + .options(babelOptions); + } }); }, }); diff --git a/packages/plugin-babel/src/types.ts b/packages/plugin-babel/src/types.ts index 56f43a7c04..d09537e9f2 100644 --- a/packages/plugin-babel/src/types.ts +++ b/packages/plugin-babel/src/types.ts @@ -1,11 +1,10 @@ import type { ChainedConfigWithUtils } from '@rsbuild/shared'; import type { PluginItem as BabelPlugin, - PluginOptions as BabelPluginOptions, TransformOptions as BabelTransformOptions, } from '@babel/core'; -export { BabelPlugin, BabelPluginOptions, BabelTransformOptions }; +export type { BabelPlugin, BabelTransformOptions }; export type PresetEnvTargets = string | string[] | Record; export type PresetEnvBuiltIns = 'usage' | 'entry' | false; @@ -51,6 +50,8 @@ export type PresetReactOptions = | AutomaticRuntimePresetReactOptions | ClassicRuntimePresetReactOptions; +export type RuleCondition = string | RegExp | (string | RegExp); + export type BabelConfigUtils = { addPlugins: (plugins: BabelPlugin[]) => void; addPresets: (presets: BabelPlugin[]) => void; @@ -62,15 +63,17 @@ export type BabelConfigUtils = { * use `source.include` instead * @deprecated */ - addIncludes: (includes: string | RegExp | (string | RegExp)[]) => void; + addIncludes: (includes: RuleCondition[]) => void; /** * use `source.exclude` instead * @deprecated */ - addExcludes: (excludes: string | RegExp | (string | RegExp)[]) => void; + addExcludes: (excludes: RuleCondition[]) => void; }; export type PluginBabelOptions = { + include?: RuleCondition; + exclude?: RuleCondition; babelLoaderOptions?: ChainedConfigWithUtils< BabelTransformOptions, BabelConfigUtils diff --git a/packages/plugin-babel/tests/__snapshots__/index.test.ts.snap b/packages/plugin-babel/tests/__snapshots__/index.test.ts.snap index 63f0623244..2e517fc83f 100644 --- a/packages/plugin-babel/tests/__snapshots__/index.test.ts.snap +++ b/packages/plugin-babel/tests/__snapshots__/index.test.ts.snap @@ -1,46 +1,5 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`plugins/babel > babel-loader addIncludes & addExcludes should works 1`] = ` -{ - "module": { - "rules": [ - { - "exclude": [ - "src/example", - ], - "include": [ - /\\\\/node_modules\\\\/query-string\\\\//, - ], - "test": /\\\\\\.\\(js\\|jsx\\|mjs\\|cjs\\|ts\\|tsx\\|mts\\|cts\\)\\$/, - "use": [ - { - "loader": "/packages/plugin-babel/compiled/babel-loader/index.js", - "options": { - "babelrc": false, - "compact": false, - "configFile": false, - "plugins": [], - "presets": [ - [ - "/node_modules//@babel/preset-typescript/lib/index.js", - { - "allExtensions": true, - "allowDeclareFields": true, - "allowNamespaces": true, - "isTSX": true, - "optimizeConstEnums": true, - }, - ], - ], - }, - }, - ], - }, - ], - }, -} -`; - exports[`plugins/babel > babel-loader should works with builtin:swc-loader 1`] = ` { "exclude": [ diff --git a/packages/plugin-babel/tests/index.test.ts b/packages/plugin-babel/tests/index.test.ts index 3f4362fc17..31c06493e9 100644 --- a/packages/plugin-babel/tests/index.test.ts +++ b/packages/plugin-babel/tests/index.test.ts @@ -6,17 +6,15 @@ import { SCRIPT_REGEX } from '@rsbuild/shared'; describe('plugins/babel', () => { it('babel-loader should works with builtin:swc-loader', async () => { const rsbuild = await createStubRsbuild({ - rsbuildConfig: {}, + rsbuildConfig: { + source: { + include: [/\/node_modules\/query-string\//], + exclude: ['src/example'], + }, + }, }); - rsbuild.addPlugins([ - pluginBabel({ - babelLoaderOptions: (_config, { addExcludes, addIncludes }) => { - addIncludes(/\/node_modules\/query-string\//); - addExcludes('src/example'); - }, - }), - ]); + rsbuild.addPlugins([pluginBabel()]); const config = await rsbuild.unwrapConfig(); @@ -61,22 +59,4 @@ describe('plugins/babel', () => { expect(bundlerConfigs[0]).toMatchSnapshot(); }); - - it('babel-loader addIncludes & addExcludes should works', async () => { - const rsbuild = await createStubRsbuild({ - plugins: [ - pluginBabel({ - babelLoaderOptions: (_config, { addExcludes, addIncludes }) => { - addIncludes(/\/node_modules\/query-string\//); - addExcludes('src/example'); - }, - }), - ], - rsbuildConfig: {}, - }); - - const bundlerConfigs = await rsbuild.initConfigs(); - - expect(bundlerConfigs[0]).toMatchSnapshot(); - }); });