Skip to content

Commit

Permalink
✨ introduce qiankun react binding (#2706)
Browse files Browse the repository at this point in the history
  • Loading branch information
bravepg authored Oct 19, 2023
1 parent fd78ab0 commit 1d14f74
Show file tree
Hide file tree
Showing 20 changed files with 24,981 additions and 10,242 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ module.exports = {
tsconfigRootDir: __dirname,
project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
},
ignorePatterns: ['.eslintrc.cjs'],
rules: {
'@typescript-eslint/no-unnecessary-condition': 'error',
'@typescript-eslint/no-explicit-any': ['error', { fixToUnknown: true }],
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
'@typescript-eslint/consistent-type-exports': ['error', { fixMixedExportsWithInlineTypeSpecifier: true }],
Expand Down
3 changes: 2 additions & 1 deletion examples/main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@
"react-dom": "^16.13.1",
"vue": "^2.6.11",
"zone.js": "^0.10.2"
}
},
"repository": "[email protected]:umijs/qiankun.git"
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
"node": ">=16"
},
"scripts": {
"dev": "pnpm run build && npm run start:main && npm run start:react15",
"start:example": "pnpm run build && npm run start:main && npm run start:react15",
"start:main": "cd ./examples/main && npm start",
"start:react15": "cd ./examples/react15 && npm start",
"build": "pnpm -r run build",
"build": "pnpm -r --filter=./packages/**/* run build",
"prerelease:alpha": "changeset pre enter alpha && changeset && changeset version",
"release:alpha": "pnpm run build && changeset publish && changeset pre exit",
"lint": "eslint packages/",
Expand Down
1 change: 1 addition & 0 deletions packages/loader/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export async function loadEntry<T>(entry: Entry, container: HTMLElement, opts: I
if (await isEntryScript()) {
// the latest set prop is the entry script exposed global variable
if (sandbox?.latestSetProp) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
entryScriptLoadedDeferred.resolve(sandbox.globalThis[sandbox.latestSetProp as number] as T);
} else {
// TODO support non sandbox mode?
Expand Down
1 change: 1 addition & 0 deletions packages/qiankun/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './apis/loadMicroApp';
export * from './apis/registerMicroApps';
export * from './types';
1 change: 0 additions & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@qiankunjs/sandbox": "workspace:^",
"lodash": "^4.17.11",
"semver": "^7.5.3"
},
Expand Down
6 changes: 4 additions & 2 deletions packages/shared/src/assets-transpilers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
* @author Kuitos
* @since 2023-08-26
*/
import type { Sandbox } from '@qiankunjs/sandbox';
// import type { Sandbox } from '@qiankunjs/sandbox';
import type { BaseLoaderOpts } from '../common';

import type { MatchResult } from '../module-resolver';

export type BaseTranspilerOpts = BaseLoaderOpts & {
moduleResolver?: (url: string) => MatchResult | undefined;
sandbox?: Sandbox;
// TODO: 先把 sandbox 类型设置如下,解除和 @qiankunjs/loader 相互依赖的问题
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sandbox?: Record<string, any>;
};

export type AssetsTranspilerOpts = BaseTranspilerOpts & { rawNode: Node };
Expand Down
11 changes: 11 additions & 0 deletions packages/ui-bindings/react/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
extends: ['plugin:react/recommended', require.resolve('../../../.eslintrc.cjs')],
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
rules: {
'react/display-name': 'off',
'react/prop-types': 'off',
},
};
6 changes: 6 additions & 0 deletions packages/ui-bindings/react/.fatherrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import cfg from '../../../.fatherrc.cjs';

export default {
umd: {},
...cfg,
};
121 changes: 119 additions & 2 deletions packages/ui-bindings/react/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,124 @@
# qiankun react binding
# qiankun ui binding for react

## Usage

```bash
npm i @qiankunjs/react-binding
npm i @qiankunjs/react
```

## MicroApp component

Load (or unload) child apps directly through the `<MicroApp/>` component, which provides loading and error catching-related capabilities:

```tsx
import { MicroApp } from '@qiankunjs/react';

export default function Page() {
return <MicroApp name="app1" entry="http://localhost:8000" />;
}
```

When the sub-app loading animation or error capture capability is enabled, the sub-app accepts an additional style class `wrapperClassName`, and the rendered result is as follows:

```tsx
<div style={{ position: 'relative' }} className={wrapperClassName}>
<MicroAppLoader loading={loading} />
<ErrorBoundary error={e} />
<MicroApp className={className} />
</div>
```

### Load animation

When this capability is enabled, loading animations are automatically displayed when child apps are loading. When the sub-application is mounted and changes to the `MOUNTED` state, the loading status ends and the sub-application content is displayed.

Just pass `autoSetLoading` as a parameter:

```tsx
import { MicroApp } from '@qiankunjs/react';

export default function Page() {
return <MicroApp name="app1" entry="http://localhost:8000" autoSetLoading />;
}
```

#### Custom loading animation

If you want to override the default loading animation style, you can set a custom loading component `loader` as the loading animation for the child app.

```tsx
import CustomLoader from '@/components/CustomLoader';
import { MicroApp } from '@qiankunjs/react';

export default function Page() {
return (
<MicroApp name="app1" entry="http://localhost:8000" loader={(loading) => <CustomLoader loading={loading} />} />
);
}
```

where `loading` is the `boolean` type parameter, `true` indicates that the loading state is still being loaded, and `false` indicates that the loading state has ended.

### Error catching

When this capability is enabled, an error message is automatically displayed when a child app loads unexpectedly. You can pass the `autoCaptureError` property to the sub-app to enable sub-app error capture capabilities:

```tsx
import { MicroApp } from '@qiankunjs/react';

export default function Page() {
return <MicroApp name="app1" entry="http://localhost:8000" autoCaptureError />;
}
```

#### Custom error capture

If you want to override the default error capture component style, you can set a custom component `errorBoundary` as the error capture component for the child app:

```tsx
import CustomErrorBoundary from '@/components/CustomErrorBoundary';
import { MicroApp } from '@qiankunjs/react';

export default function Page() {
return (
<MicroApp
name="app1"
entry="http://localhost:8000"
errorBoundary={(error) => <CustomErrorBoundary error={error} />}
/>
);
}
```

### Component Props

| Name | Required | Description | Type | Default |
| --- | --- | --- | --- | --- |
| `name` | yes | The name of the microapp | `string` |
| `entry` | yes | The HTML address of the microapp | `string` |
| `autoSetLoading` | no | Automatically set the loading state of your microapp | `boolean` | `false` |
| `loader` | no | Custom microapps load state components | `(loading) => React.ReactNode` | `undefined` |
| `autoCaptureError` | no | Automatically set up error capture for microapps | `boolean` | `false` |
| `errorBoundary` | no | Custom microapp error capture component | `(error: any) => React.ReactNode` | `undefined` |
| `className` | no | The style class for the microapp | `string` | `undefined` |
| `wrapperClassName` | no | Wrap the microapp loading component, error capture component, and microapp's style classes, and are only valid when the load component or error capture component is enabled | `string` | `undefined` |

## Get the child app load status

The loading status includes: "NOT_LOADED" | "LOADING_SOURCE_CODE" | "NOT_BOOTSTRAPPED" | "BOOTSTRAPPING" | "NOT_MOUNTED" | "MOUNTING" | "MOUNTED" | "UPDATING" | "UNMOUNTING" | "UNLOADING" |

```tsx
import { useRef } from 'react';
import { MicroApp } from '@qiankunjs/react';

export default function Page() {
const microAppRef = useRef();

useEffect(() => {
// Get the child app load status
console.log(microAppRef.current?.getStatus());
}, []);

return <MicroApp name="app1" entry="http://localhost:8000" ref={microAppRef} />;
}
```
124 changes: 124 additions & 0 deletions packages/ui-bindings/react/README.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# qiankun ui binding for react

## 安装

```bash
npm i @qiankunjs/react
```

## MicroApp 组件

直接通过 `<MicroApp />` 组件加载(或卸载)子应用,该组件提供了 loading 以及错误捕获相关的能力:

```tsx
import { MicroApp } from '@qiankunjs/react';

export default function Page() {
return <MicroApp name="app1" entry="http://localhost:8000" />;
}
```

当启用子应用加载动画或错误捕获能力时,子应用接受一个额外的样式类 `wrapperClassName`,渲染的结果如下所示:

```tsx
<div style={{ position: 'relative' }} className={wrapperClassName}>
<MicroAppLoader loading={loading} />
<ErrorBoundary error={e} />
<MicroApp className={className} />
</div>
```

### 加载动画

启用此能力后,当子应用正在加载时,会自动显示加载动画。当子应用挂载完成变成 `MOUNTED` 状态时,加载状态结束,显示子应用内容。

直接将 `autoSetLoading` 作为参数传入即可:

```tsx
import { MicroApp } from '@qiankunjs/react';

export default function Page() {
return <MicroApp name="app1" entry="http://localhost:8000" autoSetLoading />;
}
```

#### 自定义加载动画

如果您希望覆盖默认的加载动画样式时,可以设置一个自定义的加载组件 `loader` 作为子应用的加载动画。

```tsx
import CustomLoader from '@/components/CustomLoader';
import { MicroApp } from '@qiankunjs/react';

export default function Page() {
return (
<MicroApp name="app1" entry="http://localhost:8000" loader={(loading) => <CustomLoader loading={loading} />} />
);
}
```

其中,`loading``boolean` 类型参数,为 `true` 时表示仍在加载状态,为 `false` 时表示加载状态已结束。

### 错误捕获

启用此能力后,当子应用加载出现异常时,会自动显示错误信息。可以向子应用传入 `autoCaptureError` 属性以开启子应用错误捕获能力:

```tsx
import { MicroApp } from '@qiankunjs/react';

export default function Page() {
return <MicroApp name="app1" entry="http://localhost:8000" autoCaptureError />;
}
```

#### 自定义错误捕获

如果您希望覆盖默认的错误捕获组件样式时,可以设置一个自定义的组件 `errorBoundary` 作为子应用的错误捕获组件:

```tsx
import CustomErrorBoundary from '@/components/CustomErrorBoundary';
import { MicroApp } from '@qiankunjs/react';

export default function Page() {
return (
<MicroApp
name="app1"
entry="http://localhost:8000"
errorBoundary={(error) => <CustomErrorBoundary error={error} />}
/>
);
}
```

### 组件属性

| 属性 | 必填 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- | --- |
| `name` || 微应用的名称 | `string` |
| `entry` || 微应用的 HTML 地址 | `string` |
| `autoSetLoading` || 自动设置微应用的加载状态 | `boolean` | `false` |
| `loader` || 自定义的微应用加载状态组件 | `(loading) => React.ReactNode` | `undefined` |
| `autoCaptureError` || 自动设置微应用的错误捕获 | `boolean` | `false` |
| `errorBoundary` || 自定义的微应用错误捕获组件 | `(error: any) => React.ReactNode` | `undefined` |
| `className` || 微应用的样式类 | `string` | `undefined` |
| `wrapperClassName` || 包裹微应用加载组件、错误捕获组件和微应用的样式类,仅在启用加载组件或错误捕获组件时有效 | `string` | `undefined` |

## 获取子应用加载状态

加载状态包括:"NOT_LOADED" | "LOADING_SOURCE_CODE" | "NOT_BOOTSTRAPPED" | "BOOTSTRAPPING" | "NOT_MOUNTED" | "MOUNTING" | "MOUNTED" | "UPDATING" | "UNMOUNTING" | "UNLOADING" |

```tsx
import { useRef } from 'react';
import { MicroApp } from '@qiankunjs/react';

export default function Page() {
const microAppRef = useRef();

useEffect(() => {
// 获取子应用加载状态
console.log(microAppRef.current?.getStatus());
}, []);

return <MicroApp name="app1" entry="http://localhost:8000" ref={microAppRef} />;
}
```
33 changes: 33 additions & 0 deletions packages/ui-bindings/react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@qiankunjs/react",
"version": "0.0.1-rc.1",
"description": "",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./src/index.ts",
"sideEffects": false,
"scripts": {
"build": "father build",
"dev": "father dev",
"test": "cross-env NODE_ENV=test vitest"
},
"author": "Bravepg",
"license": "MIT",
"dependencies": {
"qiankun": "workspace:^",
"lodash": "^4.17.11"
},
"devDependencies": {
"@types/react": "^18.0.0",
"eslint-plugin-react": "latest"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"files": ["dist", "src"],
"repository": "[email protected]:umijs/qiankun.git"
}
5 changes: 5 additions & 0 deletions packages/ui-bindings/react/src/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

const ErrorBoundary: React.FC<{ error: Error }> = ({ error }) => <div>{error.message}</div>;

export default ErrorBoundary;
Loading

0 comments on commit 1d14f74

Please sign in to comment.