diff --git a/.changeset/ninety-seas-dig.md b/.changeset/ninety-seas-dig.md new file mode 100644 index 0000000000..853d812bb3 --- /dev/null +++ b/.changeset/ninety-seas-dig.md @@ -0,0 +1,3 @@ +--- + +--- diff --git a/packages/react/transform/index.d.ts b/packages/react/transform/index.d.ts index 8f75070879..a021924527 100644 --- a/packages/react/transform/index.d.ts +++ b/packages/react/transform/index.d.ts @@ -339,8 +339,57 @@ export interface CssScopeVisitorConfig { /** @public */ filename: string } +/** + * {@inheritdoc PluginReactLynxOptions.defineDCE} + * @public + */ export interface DefineDceVisitorConfig { - /** @public */ + /** + * @public + * Replaces variables in your code with other values or expressions at compile time. + * + * @remarks + * Caveat: differences between `source.define` + * + * `defineDCE` happens before transforming `background-only` directives. + * So it's useful for eliminating code that is only used in the background from main-thread. + * + * @example + * + * ```js + * import { defineConfig } from '@lynx-js/rspeedy' + * import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin' + * + * export default defineConfig({ + * plugins: [ + * pluginReactLynx({ + * defineDCE: { + * define: { + * __FOO__: 'false', + * 'process.env.PLATFORM': '"lynx"', + * }, + * }, + * }) + * ], + * }) + * ``` + * + * Then, `__FOO__` and `process.env.PLATFORM` could be used in source code. + * + * ``` + * if (process.env.PLATFORM === 'lynx') { + * console.log('lynx') + * } + * + * function FooOrBar() { + * if (__FOO__) { + * return foo + * } else { + * return bar + * } + * } + * ``` + */ define: Record } export interface DirectiveDceVisitorConfig { @@ -475,10 +524,15 @@ export interface ShakeVisitorConfig { */ removeCallParams: Array } +/** @internal */ export interface JsxTransformerConfig { + /** @internal */ preserveJsx: boolean + /** @internal */ runtimePkg: string + /** @internal */ jsxImportSource?: string + /** @internal */ filename: string /** @internal */ target: 'LEPUS' | 'JS' | 'MIXED' diff --git a/packages/react/transform/src/swc_plugin_define_dce/mod.rs b/packages/react/transform/src/swc_plugin_define_dce/mod.rs index 6bad046e10..770231e680 100644 --- a/packages/react/transform/src/swc_plugin_define_dce/mod.rs +++ b/packages/react/transform/src/swc_plugin_define_dce/mod.rs @@ -2,10 +2,55 @@ use std::{collections::HashMap, fmt::Debug}; use napi_derive::napi; +/// {@inheritdoc PluginReactLynxOptions.defineDCE} +/// @public #[napi(object)] #[derive(Clone, Debug)] pub struct DefineDCEVisitorConfig { /// @public + /// Replaces variables in your code with other values or expressions at compile time. + /// + /// @remarks + /// Caveat: differences between `source.define` + /// + /// `defineDCE` happens before transforming `background-only` directives. + /// So it's useful for eliminating code that is only used in the background from main-thread. + /// + /// @example + /// + /// ```js + /// import { defineConfig } from '@lynx-js/rspeedy' + /// import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin' + /// + /// export default defineConfig({ + /// plugins: [ + /// pluginReactLynx({ + /// defineDCE: { + /// define: { + /// __FOO__: 'false', + /// 'process.env.PLATFORM': '"lynx"', + /// }, + /// }, + /// }) + /// ], + /// }) + /// ``` + /// + /// Then, `__FOO__` and `process.env.PLATFORM` could be used in source code. + /// + /// ``` + /// if (process.env.PLATFORM === 'lynx') { + /// console.log('lynx') + /// } + /// + /// function FooOrBar() { + /// if (__FOO__) { + /// return foo + /// } else { + /// return bar + /// } + /// } + /// ``` pub define: HashMap, } diff --git a/packages/react/transform/src/swc_plugin_snapshot/mod.rs b/packages/react/transform/src/swc_plugin_snapshot/mod.rs index 86f80ae751..76444f2d7e 100644 --- a/packages/react/transform/src/swc_plugin_snapshot/mod.rs +++ b/packages/react/transform/src/swc_plugin_snapshot/mod.rs @@ -1083,12 +1083,17 @@ where } } +/// @internal #[napi(object)] #[derive(Clone, Debug)] pub struct JSXTransformerConfig { + /// @internal pub preserve_jsx: bool, + /// @internal pub runtime_pkg: String, + /// @internal pub jsx_import_source: Option, + /// @internal pub filename: String, /// @internal #[napi(ts_type = "'LEPUS' | 'JS' | 'MIXED'")] diff --git a/packages/rspeedy/plugin-react/src/validate.ts b/packages/rspeedy/plugin-react/src/validate.ts index 825e68213d..d0ba6cec3a 100644 --- a/packages/rspeedy/plugin-react/src/validate.ts +++ b/packages/rspeedy/plugin-react/src/validate.ts @@ -6,28 +6,51 @@ import * as typia from 'typia' import type { PluginReactLynxOptions } from './pluginReactLynx.js' +const validate: ( + input: unknown, +) => typia.IValidation = typia + .createValidateEquals() + export const validateConfig: ( input: unknown, -) => PluginReactLynxOptions | undefined = typia.createAssertEquals< - PluginReactLynxOptions | undefined ->(({ path, expected, value }) => { - if (expected === 'undefined') { - const errorMessage = - `Unknown property: \`${path}\` in the configuration of pluginReactLynx` - - // Unknown properties - return new Error(errorMessage) +) => PluginReactLynxOptions | undefined = (input: unknown) => { + const result = validate(input) + + if (result.success) { + return result.data + } + + const messages: string[] = result + .errors + .flatMap(({ expected, path, value }) => { + // Ignore the internal options + // See: #846 + if ( + path + && (path === '$input.jsx' || path.startsWith('$input.jsx.')) + && expected === 'undefined' + ) { + return null + } + + if (expected === 'undefined') { + return `Unknown property: \`${path}\` in the configuration of pluginReactLynx` + } + return [ + `Invalid config on pluginReactLynx: \`${path}\`.`, + ` - Expect to be ${expected}`, + ` - Got: ${whatIs(value)}`, + '', + ] + }) + .filter(message => message !== null) + + if (messages.length === 0) { + return result.data as PluginReactLynxOptions | undefined } - return new Error( - [ - `Invalid config on pluginReactLynx: \`${path}\`.`, - ` - Expect to be ${expected}`, - ` - Got: ${whatIs(value)}`, - '', - ].join('\n'), - ) -}) + throw new Error(messages.join('\n')) +} function whatIs(value: unknown): string { return Object.prototype.toString.call(value) diff --git a/packages/rspeedy/plugin-react/test/validate.test.ts b/packages/rspeedy/plugin-react/test/validate.test.ts index 16d7a4e614..4fa4fb9125 100644 --- a/packages/rspeedy/plugin-react/test/validate.test.ts +++ b/packages/rspeedy/plugin-react/test/validate.test.ts @@ -3,7 +3,11 @@ // LICENSE file in the root directory of this source tree. import { describe, expect, test } from 'vitest' -import type { CompatVisitorConfig } from '@lynx-js/react/transform' +import type { + CompatVisitorConfig, + DefineDceVisitorConfig, + JsxTransformerConfig, +} from '@lynx-js/react/transform' import { validateConfig } from '../src/validate.js' @@ -55,6 +59,83 @@ describe('Validation', () => { }) }) + test('defineDCE', () => { + const cases: Partial[] = [ + {}, + { define: {} }, + { define: { foo: 'bar' } }, + ] + + cases.forEach(defineDCE => { + expect(validateConfig({ defineDCE })).toStrictEqual({ defineDCE }) + }) + }) + + test('invalid defineDCE', () => { + expect(() => validateConfig({ defineDCE: 1 })) + .toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid config on pluginReactLynx: \`$input.defineDCE\`. + - Expect to be (Partial | undefined) + - Got: number + ] + `) + + expect(() => validateConfig({ defineDCE: [] })) + .toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid config on pluginReactLynx: \`$input.defineDCE\`. + - Expect to be (Partial | undefined) + - Got: array + ] + `) + + expect(() => validateConfig({ defineDCEs: {} })) + .toThrowErrorMatchingInlineSnapshot( + `[Error: Unknown property: \`$input.defineDCEs\` in the configuration of pluginReactLynx]`, + ) + + expect(() => + validateConfig({ + defineDCE: { + define: { + foo: 1, + bar: null, + }, + }, + }) + ) + .toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid config on pluginReactLynx: \`$input.defineDCE.define.foo\`. + - Expect to be string + - Got: number + + Invalid config on pluginReactLynx: \`$input.defineDCE.define.bar\`. + - Expect to be string + - Got: null + ] + `) + }) + + test('jsx', () => { + const cases: Partial[] = [ + {}, + { filename: '1' }, + { preserveJsx: false }, + // @ts-expect-error We bypass the internal type check. + { preserveJsx: 'foo' }, + ] + + cases.forEach(jsx => { + expect(validateConfig({ jsx })).toStrictEqual({ jsx }) + }) + + // cSpell:disable + expect(() => validateConfig({ jsxes: 1 })) + .toThrowErrorMatchingInlineSnapshot( + `[Error: Unknown property: \`$input.jsxes\` in the configuration of pluginReactLynx]`, + ) + // cSpell:enable + }) + test('targetSdkVersion', () => { expect(validateConfig({ targetSdkVersion: '3.2' })).toStrictEqual({ targetSdkVersion: '3.2',