From c2f6c255d86bcc21a4dc8ddbabf4c3cab7925c14 Mon Sep 17 00:00:00 2001 From: Amir Saifutdinov Date: Fri, 9 Oct 2026 23:40:59 +0300 Subject: [PATCH] fix(repack-plugin-nativewind): configure SWC rules nested in oneOf NativeWindPlugin only updated top-level builtin:swc-loader rules, so with getJsTransformRules (which nests them in oneOf) JSX kept importing from `react`. Walk nested `oneOf` and `rules` as well. Related to #1422 --- .changeset/nativewind-swc-oneof.md | 5 +++ packages/plugin-nativewind/src/plugin.ts | 11 ++++++- pnpm-lock.yaml | 3 ++ tests/integration/package.json | 1 + tests/integration/src/helpers.ts | 31 +++++++++++++++++++ .../src/plugins/NativeWindPlugin.test.ts | 29 +++++++++++++++++ 6 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 .changeset/nativewind-swc-oneof.md create mode 100644 tests/integration/src/plugins/NativeWindPlugin.test.ts diff --git a/.changeset/nativewind-swc-oneof.md b/.changeset/nativewind-swc-oneof.md new file mode 100644 index 000000000..7e40d10b8 --- /dev/null +++ b/.changeset/nativewind-swc-oneof.md @@ -0,0 +1,5 @@ +--- +"@callstack/repack-plugin-nativewind": patch +--- + +Configure the NativeWind JSX import source for `builtin:swc-loader` rules nested in `oneOf` or `rules`, such as the ones from `getJsTransformRules`. Previously only top-level rules were updated, so JSX kept importing from `react`. diff --git a/packages/plugin-nativewind/src/plugin.ts b/packages/plugin-nativewind/src/plugin.ts index d2d4e6002..1d8ad2b11 100644 --- a/packages/plugin-nativewind/src/plugin.ts +++ b/packages/plugin-nativewind/src/plugin.ts @@ -1,5 +1,6 @@ import type { Compiler as RspackCompiler, + RuleSetRules, RuleSetUse, RuleSetUseItem, SwcLoaderOptions, @@ -154,7 +155,11 @@ export class NativeWindPlugin { * * TODO made obsolete by the new babel-swc-loader, remove in 6.0 */ - compiler.options.module.rules.forEach((rule) => { + this.configureRules(compiler.options.module.rules); + } + + private configureRules(rules: RuleSetRules | undefined) { + rules?.forEach((rule) => { if (!rule || typeof rule !== 'object') { return; } @@ -168,6 +173,10 @@ export class NativeWindPlugin { rule.options as SwcLoaderOptions ); } + + // e.g. `getJsTransformRules` nests its `builtin:swc-loader` rules in `oneOf` + this.configureRules(rule.oneOf); + this.configureRules(rule.rules); }); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78c3a2359..2b5f446d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1016,6 +1016,9 @@ importers: '@callstack/repack': specifier: workspace:* version: link:../../packages/repack + '@callstack/repack-plugin-nativewind': + specifier: workspace:* + version: link:../../packages/plugin-nativewind '@module-federation/enhanced': specifier: 2.0.1 version: 2.0.1(@rspack/core@1.6.0(@swc/helpers@0.5.23))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2)(webpack@5.105.4(@swc/core@1.15.46(@swc/helpers@0.5.23))) diff --git a/tests/integration/package.json b/tests/integration/package.json index c098876ce..c39be8c76 100644 --- a/tests/integration/package.json +++ b/tests/integration/package.json @@ -14,6 +14,7 @@ "devDependencies": { "@babel/runtime": "^7.29.7", "@callstack/repack": "workspace:*", + "@callstack/repack-plugin-nativewind": "workspace:*", "@module-federation/enhanced": "2.0.1", "@module-federation/enhanced-v15": "npm:@module-federation/enhanced@0.15.0", "@module-federation/enhanced-v21": "npm:@module-federation/enhanced@0.21.0", diff --git a/tests/integration/src/helpers.ts b/tests/integration/src/helpers.ts index ea231fe62..fb8d71548 100644 --- a/tests/integration/src/helpers.ts +++ b/tests/integration/src/helpers.ts @@ -1,6 +1,8 @@ import type { Compiler as RspackCompiler, Configuration as RspackConfiguration, + RuleSetRule, + RuleSetUseItem, } from '@rspack/core'; import { createFsFromVolume, Volume } from 'memfs'; import { inject } from 'vitest'; @@ -112,3 +114,32 @@ export function getReactNativeVirtualModules( };`, }; } + +export type SwcLoaderOptions = { + jsc?: { + transform?: { + react?: { development?: boolean; importSource?: string }; + }; + }; +}; + +/** + * Collect options of every `builtin:swc-loader` in `module.rules`, + * including nested `oneOf` and `rules` + */ +export function collectSwcLoaderOptions(rules: unknown[]): SwcLoaderOptions[] { + return rules.flatMap((rule) => { + if (!rule || typeof rule !== 'object') return []; + const { use, loader, options, oneOf, rules: nested } = rule as RuleSetRule; + const uses = [use].flat().filter(Boolean) as RuleSetUseItem[]; + return [ + ...(loader === 'builtin:swc-loader' ? [options as SwcLoaderOptions] : []), + ...uses + .filter((item) => typeof item === 'object') + .filter((item) => item.loader === 'builtin:swc-loader') + .map((item) => (item as { options: SwcLoaderOptions }).options), + ...collectSwcLoaderOptions(oneOf ?? []), + ...collectSwcLoaderOptions(nested ?? []), + ]; + }); +} diff --git a/tests/integration/src/plugins/NativeWindPlugin.test.ts b/tests/integration/src/plugins/NativeWindPlugin.test.ts new file mode 100644 index 000000000..bdf1995fd --- /dev/null +++ b/tests/integration/src/plugins/NativeWindPlugin.test.ts @@ -0,0 +1,29 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { getJsTransformRules } from '@callstack/repack'; +import { NativeWindPlugin } from '@callstack/repack-plugin-nativewind'; +import { describe, expect, inject, it } from 'vitest'; +import { collectSwcLoaderOptions, createCompiler } from '../helpers.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +// builtin:swc-loader only exists in Rspack +describe.runIf(inject('bundlerType') === 'rspack')('NativeWindPlugin', () => { + it('uses the NativeWind JSX import source in getJsTransformRules', async () => { + const compiler = await createCompiler({ + context: __dirname, + mode: 'development', + entry: './index.js', + output: { path: '/out' }, + module: { rules: getJsTransformRules() }, + plugins: [new NativeWindPlugin({ checkDependencies: false })], + }); + + const importSources = collectSwcLoaderOptions( + compiler.options.module.rules + ).map((options) => options.jsc?.transform?.react?.importSource); + + expect(importSources.length).toBeGreaterThan(0); + expect(new Set(importSources)).toEqual(new Set(['nativewind'])); + }); +});