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
5 changes: 5 additions & 0 deletions .changeset/chubby-icons-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/react": patch
---

Fix type error when using `Suspense` with `"jsx": "react-jsx"`.
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 @@
---

---
7 changes: 5 additions & 2 deletions examples/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
"type": "module",
"scripts": {
"build": "rspeedy build",
"dev": "rspeedy dev"
"dev": "rspeedy dev",
"test:type": "vitest --typecheck.only"
},
"dependencies": {
"@lynx-js/react": "workspace:*"
},
"devDependencies": {
"@lynx-js/qrcode-rsbuild-plugin": "workspace:*",
"@lynx-js/react-rsbuild-plugin": "workspace:*",
"@lynx-js/rspeedy": "workspace:*"
"@lynx-js/rspeedy": "workspace:*",
"@lynx-js/types": "^3.3.0",
"@types/react": "^18.3.21"
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
import type { JSX } from '../../jsx-runtime/index.js';
import type { MainThread, NodesRef, Target, TouchEvent } from '@lynx-js/types';
import { assertType, describe, test } from 'vitest';
import { useMainThreadRef } from '../../src/lynx-api.js';
import { useRef } from '../../src/hooks/react.js';

import {
Component,
forwardRef,
Fragment,
memo,
Suspense,
useMainThreadRef,
useRef,
} from '@lynx-js/react';
import type { JSX, ReactNode } from '@lynx-js/react';
import type { MainThread, NodesRef, Target, TouchEvent } from '@lynx-js/types';

describe('JSX Runtime Types', () => {
test('should support basic JSX element', () => {
Expand All @@ -19,7 +27,7 @@ describe('JSX Runtime Types', () => {

test('should validate the required props for raw-text', () => {
// @ts-expect-error: Missing required prop 'text'
const shouldError = <raw-text></raw-text>;
const _shouldError = <raw-text></raw-text>;

const rawTextELe = <raw-text text={'text'}></raw-text>;
assertType<JSX.Element>(rawTextELe);
Expand All @@ -37,7 +45,7 @@ describe('JSX Runtime Types', () => {

test('should error on unsupported tags', () => {
// @ts-expect-error: Unsupported tag
const divElement = <div></div>;
const _divElement = <div></div>;
});

test('should support event handlers', () => {
Expand Down Expand Up @@ -83,12 +91,10 @@ describe('JSX Runtime Types', () => {
});

test('should support ref prop with function', () => {
const ref = useRef<NodesRef>(null);
const viewWithRefCallback = (
<view
ref={(n) => {
assertType<NodesRef | null>(n);
ref.current = n;
}}
>
</view>
Expand All @@ -101,4 +107,78 @@ describe('JSX Runtime Types', () => {
const viewWithMainThreadRef = <view main-thread:ref={mtRef}></view>;
assertType<JSX.Element>(viewWithMainThreadRef);
});

test('should support Suspense', () => {
const jsx = (
<Suspense fallback={<text>Loading...</text>}>
<text>Hello, World!</text>
</Suspense>
);
assertType<JSX.Element>(jsx);
});

test('should support Fragment', () => {
const jsx = (
<>
<text>Hello, World!</text>
<Fragment>
<text>Hello, World!</text>
</Fragment>
</>
);
assertType<JSX.Element>(jsx);
});

test('should support class attributes', () => {
class Foo extends Component {
override render(): ReactNode {
return <text>Hello, World!</text>;
}
}

assertType<JSX.Element>(
<Foo
ref={(foo) => {
assertType<Foo | null>(foo);
}}
/>,
);

const ref = useRef<Foo>(null);
assertType<JSX.Element>(
<Foo ref={ref} />,
);
});

test('should support memo()', () => {
interface Props {
foo: string;
}

const MemoComponent = memo<Props>((props) => {
assertType<Props>(props);
return <text>Hello, World!</text>;
});

assertType<JSX.Element>(<MemoComponent foo='bar' />);
});

test('should support forwardRef()', () => {
interface Props {
foo: string;
}
const ForwardRefComponent = forwardRef<NodesRef, Props>((props, ref) => {
assertType<Props>(props);
return <text ref={ref}>Hello, World!</text>;
});

assertType<JSX.Element>(
<ForwardRefComponent
foo='bar'
ref={(node) => {
assertType<NodesRef | null>(node);
}}
/>,
);
});
});
5 changes: 3 additions & 2 deletions examples/react/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"jsx": "preserve",
"jsx": "react-jsx",
"jsxImportSource": "@lynx-js/react",
"noEmit": true,

"allowJs": true,
"checkJs": true,
"isolatedDeclarations": false,
},
"include": ["src", "lynx.config.js"],
"include": ["src", "lynx.config.js", "test"],
"references": [
{ "path": "../../packages/react/tsconfig.json" },
{ "path": "../../packages/rspeedy/core/tsconfig.build.json" },
Expand Down
7 changes: 7 additions & 0 deletions examples/react/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineProject } from 'vitest/config';

export default defineProject({
test: {
name: 'examples/react',
},
});
20 changes: 13 additions & 7 deletions packages/react/runtime/jsx-dev-runtime/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
import { JSX as _JSX } from 'react';
import * as React from 'react';

import { IntrinsicElements as _IntrinsicElements } from '@lynx-js/types';
import * as Lynx from '@lynx-js/types';

export { jsxDEV, Fragment } from 'react/jsx-dev-runtime';
export { jsxDEV, Fragment, JSXSource } from 'react/jsx-dev-runtime';
export { jsx, jsxs } from 'react/jsx-runtime';

// Modified from
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/981449c691b9be0fca569c48151fc57606f5ea7a/types/react/jsx-runtime.d.ts#L5
export namespace JSX {
interface IntrinsicElements extends _IntrinsicElements {}

type ElementType = React.JSX.ElementType;
interface Element extends React.JSX.Element {}
interface ElementClass extends React.JSX.ElementClass {}
interface ElementAttributesProperty extends React.JSX.ElementAttributesProperty {}
interface ElementChildrenAttribute extends React.JSX.ElementChildrenAttribute {}
type LibraryManagedAttributes<C, P> = React.JSX.LibraryManagedAttributes<C, P>;
interface IntrinsicAttributes {}

type Element = _JSX.Element;
interface IntrinsicClassAttributes<T> extends React.JSX.IntrinsicClassAttributes<T> {}
interface IntrinsicElements extends Lynx.IntrinsicElements {}
}
18 changes: 12 additions & 6 deletions packages/react/runtime/jsx-runtime/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
import { JSX as _JSX } from 'react';
import * as React from 'react';

import { IntrinsicElements as _IntrinsicElements } from '@lynx-js/types';
import * as Lynx from '@lynx-js/types';

export { jsx, jsxs, Fragment } from 'react/jsx-runtime';

// Modified from
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/981449c691b9be0fca569c48151fc57606f5ea7a/types/react/jsx-runtime.d.ts#L5
export namespace JSX {
interface IntrinsicElements extends _IntrinsicElements {}

type ElementType = React.JSX.ElementType;
interface Element extends React.JSX.Element {}
interface ElementClass extends React.JSX.ElementClass {}
interface ElementAttributesProperty extends React.JSX.ElementAttributesProperty {}
interface ElementChildrenAttribute extends React.JSX.ElementChildrenAttribute {}
type LibraryManagedAttributes<C, P> = React.JSX.LibraryManagedAttributes<C, P>;
interface IntrinsicAttributes {}

type Element = _JSX.Element;
interface IntrinsicClassAttributes<T> extends React.JSX.IntrinsicClassAttributes<T> {}
interface IntrinsicElements extends Lynx.IntrinsicElements {}
}
3 changes: 1 addition & 2 deletions packages/react/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
"src"
],
"scripts": {
"test": "vitest run --coverage",
"test:type": "vitest --typecheck.only"
"test": "vitest run --coverage"
},
"devDependencies": {
"@lynx-js/react": "workspace:*",
Expand Down
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
Loading