Skip to content

Commit

Permalink
feat: check consistency of JSX between swc and tsconfig
Browse files Browse the repository at this point in the history
  • Loading branch information
fi3ework committed Jan 16, 2025
1 parent 860dba6 commit 7bbbeb9
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 13 deletions.
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"]
}

0 comments on commit 7bbbeb9

Please sign in to comment.