Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .changeset/ninety-seas-dig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---

---
56 changes: 55 additions & 1 deletion packages/react/transform/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <text>foo</text>
* } else {
* return <text>bar</text>
* }
* }
* ```
*/
define: Record<string, string>
}
export interface DirectiveDceVisitorConfig {
Expand Down Expand Up @@ -475,10 +524,15 @@ export interface ShakeVisitorConfig {
*/
removeCallParams: Array<string>
}
/** @internal */
export interface JsxTransformerConfig {
/** @internal */
preserveJsx: boolean
/** @internal */
runtimePkg: string
/** @internal */
jsxImportSource?: string
/** @internal */
filename: string
/** @internal */
target: 'LEPUS' | 'JS' | 'MIXED'
Expand Down
45 changes: 45 additions & 0 deletions packages/react/transform/src/swc_plugin_define_dce/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <text>foo</text>
/// } else {
/// return <text>bar</text>
/// }
/// }
/// ```
pub define: HashMap<String, String>,
}

Expand Down
5 changes: 5 additions & 0 deletions packages/react/transform/src/swc_plugin_snapshot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
/// @internal
pub filename: String,
/// @internal
#[napi(ts_type = "'LEPUS' | 'JS' | 'MIXED'")]
Expand Down
59 changes: 41 additions & 18 deletions packages/rspeedy/plugin-react/src/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,51 @@ import * as typia from 'typia'

import type { PluginReactLynxOptions } from './pluginReactLynx.js'

const validate: (
input: unknown,
) => typia.IValidation<PluginReactLynxOptions | undefined> = typia
.createValidateEquals<PluginReactLynxOptions | undefined>()

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)
Expand Down
83 changes: 82 additions & 1 deletion packages/rspeedy/plugin-react/test/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -55,6 +59,83 @@ describe('Validation', () => {
})
})

test('defineDCE', () => {
const cases: Partial<DefineDceVisitorConfig>[] = [
{},
{ 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<DefineDceVisitorConfig> | undefined)
- Got: number
]
`)

expect(() => validateConfig({ defineDCE: [] }))
.toThrowErrorMatchingInlineSnapshot(`
[Error: Invalid config on pluginReactLynx: \`$input.defineDCE\`.
- Expect to be (Partial<DefineDceVisitorConfig> | 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<JsxTransformerConfig>[] = [
{},
{ 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',
Expand Down
Loading