Skip to content

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

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

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
feat: check consistency of JSX between swc and tsconfig
  • Loading branch information
fi3ework committed Jan 16, 2025
commit 7bbbeb968ff06d8e7d00f0fa5732a0c4d0fbf1b2
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
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy