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

feat: check consistency of JSX between swc and tsconfig #682

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions packages/core/src/check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { RsbuildConfig, RsbuildPlugin } from '@rsbuild/core';
import type { TsconfigCompilerOptions } from './types';
import { color } from './utils/helper';
import { logger } from './utils/logger';

type PluginReactOptions = {
tsconfigCompilerOptions?: TsconfigCompilerOptions;
};

const mapTsconfigJsxToSwcJsx = (jsx: string | undefined): string | null => {
if (jsx === undefined) {
// 'preserve' is the default value of tsconfig.compilerOptions.jsx
return null;
}

// Calculate a corresponding SWC JSX config if tsconfig.compilerOptions.jsx is set to React related option.
// Return `null` stands for no need to check.
switch (jsx) {
case 'react-jsx':
case 'react-jsxdev':
return 'automatic';
case 'react':
return 'classic';
case 'preserve':
case 'react-native':
// SWC JSX does not support `preserve` as of now.
return null;
default:
return null;
}
};

const checkJsx = ({
tsconfigCompilerOptions,
}: PluginReactOptions): RsbuildPlugin => ({
name: 'rsbuild:lib-check',
setup(api) {
api.onBeforeEnvironmentCompile(({ environment }) => {
const config = api.getNormalizedConfig({
environment: environment.name,
});
const swc = config.tools.swc;
const tsconfigJsx = tsconfigCompilerOptions?.jsx;
if (swc && !Array.isArray(swc) && typeof swc !== 'function') {
const swcReactRuntime = swc?.jsc?.transform?.react?.runtime || null;
const mapped = mapTsconfigJsxToSwcJsx(tsconfigJsx);
if (mapped !== swcReactRuntime) {
logger.warn(
`JSX runtime is set to ${color.green(`${JSON.stringify(swcReactRuntime)}`)} in SWC, but got ${color.green(`${JSON.stringify(tsconfigJsx)}`)} in tsconfig.json. This may cause unexpected behavior, considering aligning them.`,
);
}
}
});
},
});

export const composeCheckConfig = (
compilerOptions: TsconfigCompilerOptions,
): RsbuildConfig => {
return { plugins: [checkJsx({ tsconfigCompilerOptions: compilerOptions })] };
};
7 changes: 6 additions & 1 deletion packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from '@rsbuild/core';
import { glob } from 'tinyglobby';
import { composeAssetConfig } from './asset/assetConfig';
import { composeCheckConfig } from './check';
import {
DEFAULT_CONFIG_EXTENSIONS,
DEFAULT_CONFIG_NAME,
Expand Down Expand Up @@ -57,6 +58,7 @@ import type {
RspackResolver,
Shims,
Syntax,
TsconfigCompilerOptions,
} from './types';
import { getDefaultExtension } from './utils/extension';
import {
Expand Down Expand Up @@ -434,7 +436,7 @@ export function composeBannerFooterConfig(
}

export function composeDecoratorsConfig(
compilerOptions?: Record<string, any>,
compilerOptions?: TsconfigCompilerOptions,
version?: NonNullable<
NonNullable<EnvironmentConfig['source']>['decorators']
>['version'],
Expand Down Expand Up @@ -1327,6 +1329,8 @@ async function composeLibRsbuildConfig(
rootPath,
config.source?.tsconfigPath,
);

const checkConfig = composeCheckConfig({ compilerOptions });
const cssModulesAuto = config.output?.cssModules?.auto ?? true;

const {
Expand Down Expand Up @@ -1438,6 +1442,7 @@ async function composeLibRsbuildConfig(
dtsConfig,
bannerFooterConfig,
decoratorsConfig,
checkConfig,
);
}

Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,3 +305,7 @@ export type RslibConfigExport =
| RslibConfig
| RslibConfigSyncFn
| RslibConfigAsyncFn;

export type TsconfigCompilerOptions = Record<string, any> & {
jsx?: 'react-jsx' | 'react-jsxdev' | 'react';
};
26 changes: 14 additions & 12 deletions packages/core/src/utils/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,21 +167,23 @@ export function omit<T extends object, U extends keyof T>(
);
}

export function isPluginIncluded(
function findPlugin(pluginName: string, plugins?: RsbuildPlugins) {
return plugins?.find((plugin) => {
if (Array.isArray(plugin)) {
return isPluginIncluded(pluginName, plugin);
}
if (typeof plugin === 'object' && plugin !== null && 'name' in plugin) {
return plugin.name === pluginName;
}
return false;
});
}

function isPluginIncluded(
pluginName: string,
plugins?: RsbuildPlugins,
): boolean {
return Boolean(
plugins?.some((plugin) => {
if (Array.isArray(plugin)) {
return isPluginIncluded(pluginName, plugin);
}
if (typeof plugin === 'object' && plugin !== null && 'name' in plugin) {
return plugin.name === pluginName;
}
return false;
}),
);
return Boolean(findPlugin(pluginName, plugins));
}

export function checkMFPlugin(
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions tests/integration/check/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { join } from 'node:path';
import stripAnsi from 'strip-ansi';
import { buildAndGetResults, proxyConsole } from 'test-helper';
import { expect, test } from 'vitest';

test('should receive JSX mismatch warning of SWC with tsconfig', async () => {
const { logs, restore } = proxyConsole();
const fixturePath = join(__dirname, 'jsx');
await buildAndGetResults({ fixturePath });
const logStrings = logs
.map((log) => stripAnsi(log))
.filter((log) => log.startsWith('warn'))
.sort()
.join('\n');

expect(logStrings).toMatchInlineSnapshot(`
"warn JSX runtime is set to "automatic" in SWC, but got undefined in tsconfig.json. This may cause unexpected behavior, considering aligning them.
warn JSX runtime is set to "automatic" in SWC, but got undefined in tsconfig.json. This may cause unexpected behavior, considering aligning them."
`);

restore();
});
11 changes: 11 additions & 0 deletions tests/integration/check/jsx/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "check-jsx-test",
"version": "1.0.0",
"private": true,
"type": "module",
"devDependencies": {
"@rsbuild/plugin-react": "^1.1.0",
"@types/react": "^19.0.6",
"react": "^19.0.0"
}
}
8 changes: 8 additions & 0 deletions tests/integration/check/jsx/rslib.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { pluginReact } from '@rsbuild/plugin-react';
import { defineConfig } from '@rslib/core';
import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper';

export default defineConfig({
lib: [generateBundleEsmConfig(), generateBundleCjsConfig()],
plugins: [pluginReact()],
});
3 changes: 3 additions & 0 deletions tests/integration/check/jsx/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import React from 'react';

export const Foo = <div>foo</div>;
8 changes: 8 additions & 0 deletions tests/integration/check/jsx/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "@rslib/tsconfig/base",
"compilerOptions": {
"baseUrl": "./",
"jsx": "react"
},
"include": ["src"]
}
Loading