From c827cc42152b2c25eef50e42c5c8ede69b11053e Mon Sep 17 00:00:00 2001 From: mortalYoung Date: Sat, 20 Nov 2021 21:24:08 +0800 Subject: [PATCH 1/5] feat: register API plugin --- packages/plugin-dumi-api/.fatherrc.ts | 3 + packages/plugin-dumi-api/CONTRIBUTING.md | 40 +++ packages/plugin-dumi-api/README.md | 38 +++ .../plugin-dumi-api/__tests__/API.test.tsx | 32 +++ .../__tests__/__snapshots__/API.test.tsx.snap | 64 +++++ .../fixtures-parser/expect/class.json | 21 ++ .../fixtures-parser/expect/excludes.json | 16 ++ .../fixtures-parser/expect/extends.json | 27 ++ .../__tests__/fixtures-parser/expect/fc.json | 21 ++ .../fixtures-parser/expect/forwardRef.json | 29 +++ .../expect/localeDescription.json | 11 + .../fixtures-parser/expect/multiple.json | 28 ++ .../fixtures-parser/expect/propFilter.json | 16 ++ .../fixtures-parser/expect/skipEmptyDoc.json | 16 ++ .../expect/skipNodeModules.json | 21 ++ .../fixtures-parser/expect/union.json | 18 ++ .../__tests__/fixtures-parser/raw/class.tsx | 26 ++ .../fixtures-parser/raw/excludes.tsx | 26 ++ .../__tests__/fixtures-parser/raw/extends.tsx | 18 ++ .../__tests__/fixtures-parser/raw/fc.tsx | 21 ++ .../fixtures-parser/raw/forwardRef.tsx | 25 ++ .../fixtures-parser/raw/guess/FileName.tsx | 1 + .../raw/guess/NestSrc/src/index.tsx | 1 + .../fixtures-parser/raw/localeDescription.tsx | 14 + .../fixtures-parser/raw/multiple.tsx | 7 + .../fixtures-parser/raw/propFilter.tsx | 26 ++ .../fixtures-parser/raw/skipEmptyDoc.tsx | 23 ++ .../fixtures-parser/raw/skipNodeModules.tsx | 27 ++ .../__tests__/fixtures-parser/raw/union.tsx | 18 ++ .../__tests__/fixtures/alias/.umirc.ts | 10 + .../fixtures/alias/component/Hello/index.tsx | 8 + .../__tests__/fixtures/alias/docs/index.md | 1 + .../__tests__/fixtures/custom/.umirc.ts | 4 + .../fixtures/custom/docs/Hello/index.tsx | 8 + .../__tests__/fixtures/custom/docs/index.md | 1 + .../__tests__/fixtures/defineExport/.umirc.ts | 4 + .../fixtures/defineExport/docs/index.md | 5 + .../fixtures/defineExport/docs/index.tsx | 7 + .../__tests__/fixtures/index.ts | 7 + .../__tests__/fixtures/normal/.umirc.ts | 4 + .../__tests__/fixtures/normal/docs/index.md | 5 + .../__tests__/fixtures/normal/docs/index.tsx | 8 + .../plugin-dumi-api/__tests__/index.test.ts | 130 ++++++++++ .../plugin-dumi-api/__tests__/parser.test.ts | 91 +++++++ .../__tests__/useApiData.test.tsx | 40 +++ packages/plugin-dumi-api/package.json | 42 +++ packages/plugin-dumi-api/src/API.tsx | 74 ++++++ packages/plugin-dumi-api/src/index.ts | 242 ++++++++++++++++++ packages/plugin-dumi-api/src/parser.ts | 151 +++++++++++ packages/plugin-dumi-api/src/useApiData.ts | 60 +++++ packages/plugin-dumi-api/tsconfig.json | 12 + 51 files changed, 1548 insertions(+) create mode 100644 packages/plugin-dumi-api/.fatherrc.ts create mode 100644 packages/plugin-dumi-api/CONTRIBUTING.md create mode 100644 packages/plugin-dumi-api/README.md create mode 100644 packages/plugin-dumi-api/__tests__/API.test.tsx create mode 100644 packages/plugin-dumi-api/__tests__/__snapshots__/API.test.tsx.snap create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/expect/class.json create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/expect/excludes.json create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/expect/extends.json create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/expect/fc.json create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/expect/forwardRef.json create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/expect/localeDescription.json create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/expect/multiple.json create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/expect/propFilter.json create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/expect/skipEmptyDoc.json create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/expect/skipNodeModules.json create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/expect/union.json create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/raw/class.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/raw/excludes.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/raw/extends.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/raw/fc.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/raw/forwardRef.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/raw/guess/FileName.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/raw/guess/NestSrc/src/index.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/raw/localeDescription.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/raw/multiple.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/raw/propFilter.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/raw/skipEmptyDoc.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/raw/skipNodeModules.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures-parser/raw/union.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures/alias/.umirc.ts create mode 100644 packages/plugin-dumi-api/__tests__/fixtures/alias/component/Hello/index.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures/alias/docs/index.md create mode 100644 packages/plugin-dumi-api/__tests__/fixtures/custom/.umirc.ts create mode 100644 packages/plugin-dumi-api/__tests__/fixtures/custom/docs/Hello/index.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures/custom/docs/index.md create mode 100644 packages/plugin-dumi-api/__tests__/fixtures/defineExport/.umirc.ts create mode 100644 packages/plugin-dumi-api/__tests__/fixtures/defineExport/docs/index.md create mode 100644 packages/plugin-dumi-api/__tests__/fixtures/defineExport/docs/index.tsx create mode 100644 packages/plugin-dumi-api/__tests__/fixtures/index.ts create mode 100644 packages/plugin-dumi-api/__tests__/fixtures/normal/.umirc.ts create mode 100644 packages/plugin-dumi-api/__tests__/fixtures/normal/docs/index.md create mode 100644 packages/plugin-dumi-api/__tests__/fixtures/normal/docs/index.tsx create mode 100644 packages/plugin-dumi-api/__tests__/index.test.ts create mode 100644 packages/plugin-dumi-api/__tests__/parser.test.ts create mode 100644 packages/plugin-dumi-api/__tests__/useApiData.test.tsx create mode 100644 packages/plugin-dumi-api/package.json create mode 100644 packages/plugin-dumi-api/src/API.tsx create mode 100644 packages/plugin-dumi-api/src/index.ts create mode 100644 packages/plugin-dumi-api/src/parser.ts create mode 100644 packages/plugin-dumi-api/src/useApiData.ts create mode 100644 packages/plugin-dumi-api/tsconfig.json diff --git a/packages/plugin-dumi-api/.fatherrc.ts b/packages/plugin-dumi-api/.fatherrc.ts new file mode 100644 index 0000000000..332f1bffc6 --- /dev/null +++ b/packages/plugin-dumi-api/.fatherrc.ts @@ -0,0 +1,3 @@ +export default { + disableTypeCheck: false, +}; diff --git a/packages/plugin-dumi-api/CONTRIBUTING.md b/packages/plugin-dumi-api/CONTRIBUTING.md new file mode 100644 index 0000000000..6e1c2bc39d --- /dev/null +++ b/packages/plugin-dumi-api/CONTRIBUTING.md @@ -0,0 +1,40 @@ +# Contributing to plugin + +## Set up + +Install dev deps after git clone the repo. + +```bash +# npm is not allowed. +$ yarn +``` + +## Build + +Transform with babel and rollup. + +```bash +$ yarn build + +# Build and monitor file changes +$ yarn build --watch + +``` + +## Dev Plugin + +```bash +# This Step must only be executed in Build +$ yarn dev +``` + +## Debug + +TODO + +## Test + +```bash +$ yarn test +``` + diff --git a/packages/plugin-dumi-api/README.md b/packages/plugin-dumi-api/README.md new file mode 100644 index 0000000000..412ce8f549 --- /dev/null +++ b/packages/plugin-dumi-api/README.md @@ -0,0 +1,38 @@ +# plugin-dumi-api + +[![NPM version](https://img.shields.io/npm/v/plugin-dumi-api.svg?style=flat)](https://npmjs.org/package/plugin-dumi-api) +[![NPM downloads](http://img.shields.io/npm/dm/plugin-dumi-api.svg?style=flat)](https://npmjs.org/package/plugin-dumi-api) + + + +## Install + +```bash +# or yarn +$ npm install +``` + +```bash +$ npm run build --watch +$ npm run start +``` + +## Usage + +Configure in `.umirc.js`, + +```js +export default { + plugins: [ + ['plugin-dumi-api'], + ], +} +``` + +## Options + +TODO + +## LICENSE + +MIT diff --git a/packages/plugin-dumi-api/__tests__/API.test.tsx b/packages/plugin-dumi-api/__tests__/API.test.tsx new file mode 100644 index 0000000000..8d4684424d --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/API.test.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import API from '../src/API'; + +jest.mock('@@/dumi/apis', () => { + return { + Hello: { + default: [ + { + identifier: 'className', + description: 'Extra CSS className for this component', + 'description.zh-CN': '组件额外的 CSS className', + type: 'string', + }, + { + identifier: 'type', + description: "I'm required", + 'description.zh-CN': '我是一个必选属性', + type: 'string', + required: true, + }, + ], + }, + }; +}); + +describe('API component', () => { + test('Match snapshot', () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); +}); diff --git a/packages/plugin-dumi-api/__tests__/__snapshots__/API.test.tsx.snap b/packages/plugin-dumi-api/__tests__/__snapshots__/API.test.tsx.snap new file mode 100644 index 0000000000..69ed11b85a --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/__snapshots__/API.test.tsx.snap @@ -0,0 +1,64 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`API component Match snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Description + + Type + + Default +
+ className + + Extra CSS className for this component + + + string + + + + -- + +
+ type + + I'm required + + + string + + + + (required) + +
+
+`; diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/class.json b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/class.json new file mode 100644 index 0000000000..daa19845f7 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/class.json @@ -0,0 +1,21 @@ +{ + "A": [ + { + "identifier": "className", + "description": "extra CSS className for this component", + "type": "string" + }, + { + "identifier": "style", + "description": "inline styles", + "type": "CSSProperties" + }, + { + "identifier": "size", + "description": "component size", + "type": "\"small\" | \"large\"", + "default": "small", + "required": true + } + ] +} \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/excludes.json b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/excludes.json new file mode 100644 index 0000000000..18d9a7dc7c --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/excludes.json @@ -0,0 +1,16 @@ +{ + "AwithExcludes": [ + { + "identifier": "style", + "description": "inline styles", + "type": "CSSProperties" + }, + { + "identifier": "size", + "description": "component size", + "type": "\"small\" | \"large\"", + "default": "small", + "required": true + } + ] +} \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/extends.json b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/extends.json new file mode 100644 index 0000000000..55585614b2 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/extends.json @@ -0,0 +1,27 @@ +{ + "B": [ + { + "identifier": "isB", + "type": "boolean", + "default": "true", + "required": true + }, + { + "identifier": "className", + "description": "extra CSS className for this component", + "type": "string" + }, + { + "identifier": "style", + "description": "inline styles", + "type": "CSSProperties" + }, + { + "identifier": "size", + "description": "component size", + "type": "\"small\" | \"large\"", + "default": "small", + "required": true + } + ] +} \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/fc.json b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/fc.json new file mode 100644 index 0000000000..8583cd64a5 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/fc.json @@ -0,0 +1,21 @@ +{ + "fc": [ + { + "identifier": "className", + "description": "extra CSS className for this component", + "type": "string" + }, + { + "identifier": "style", + "description": "inline styles", + "type": "CSSProperties" + }, + { + "identifier": "size", + "description": "component size", + "type": "\"small\" | \"large\"", + "default": "small", + "required": true + } + ] +} \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/forwardRef.json b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/forwardRef.json new file mode 100644 index 0000000000..589b2cba4b --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/forwardRef.json @@ -0,0 +1,29 @@ +{ + "forwardRef": [ + { + "identifier": "className", + "description": "extra CSS className for this component", + "type": "string" + }, + { + "identifier": "style", + "description": "inline styles", + "type": "CSSProperties" + }, + { + "identifier": "size", + "description": "component size", + "type": "\"small\" | \"large\"", + "default": "small", + "required": true + }, + { + "identifier": "ref", + "type": "Ref" + }, + { + "identifier": "key", + "type": "Key" + } + ] +} \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/localeDescription.json b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/localeDescription.json new file mode 100644 index 0000000000..40a33fb94f --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/localeDescription.json @@ -0,0 +1,11 @@ +{ + "localeDescription": [ + { + "identifier": "className", + "description": "default version", + "description.zh-CN": "中文说明", + "type": "string", + "default": "{ type: 'A', value: [{ name: 'B' }] }" + } + ] +} \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/multiple.json b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/multiple.json new file mode 100644 index 0000000000..8a46ec6058 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/multiple.json @@ -0,0 +1,28 @@ +{ + "A": [ + { + "identifier": "className", + "description": "extra CSS className for this component", + "type": "string" + }, + { + "identifier": "style", + "description": "inline styles", + "type": "CSSProperties" + }, + { + "identifier": "size", + "description": "component size", + "type": "\"small\" | \"large\"", + "default": "small", + "required": true + } + ], + "multiple": [ + { + "identifier": "isHello", + "type": "boolean", + "required": true + } + ] +} \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/propFilter.json b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/propFilter.json new file mode 100644 index 0000000000..7aa4b77216 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/propFilter.json @@ -0,0 +1,16 @@ +{ + "AwithPropFilter": [ + { + "identifier": "className", + "description": "extra CSS className for this component", + "type": "string" + }, + { + "identifier": "size", + "description": "component size", + "type": "\"small\" | \"large\"", + "default": "small", + "required": true + } + ] +} \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/skipEmptyDoc.json b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/skipEmptyDoc.json new file mode 100644 index 0000000000..97806016d5 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/skipEmptyDoc.json @@ -0,0 +1,16 @@ +{ + "AwithEmptyDoc": [ + { + "identifier": "className", + "description": "extra CSS className for this component", + "type": "string" + }, + { + "identifier": "size", + "description": "component size", + "type": "\"small\" | \"large\"", + "default": "small", + "required": true + } + ] +} \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/skipNodeModules.json b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/skipNodeModules.json new file mode 100644 index 0000000000..84ecfbe461 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/skipNodeModules.json @@ -0,0 +1,21 @@ +{ + "AextendsNode": [ + { + "identifier": "className", + "description": "extra CSS className for this component", + "type": "string" + }, + { + "identifier": "style", + "description": "inline styles", + "type": "CSSProperties" + }, + { + "identifier": "size", + "description": "component size", + "type": "\"small\" | \"large\"", + "default": "small", + "required": true + } + ] +} \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/union.json b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/union.json new file mode 100644 index 0000000000..3a22144065 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/expect/union.json @@ -0,0 +1,18 @@ +{ + "union": [ + { + "identifier": "type", + "type": "\"primary\" | \"link\"", + "required": true + }, + { + "identifier": "className", + "type": "string" + }, + { + "identifier": "href", + "type": "string", + "required": true + } + ] +} \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/class.tsx b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/class.tsx new file mode 100644 index 0000000000..9897479e56 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/class.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +export interface IAProps { + /** + * extra CSS className for this component + */ + className?: string; + /** + * inline styles + */ + style?: React.CSSProperties; + /** + * component size + * @default small + */ + size: 'small' | 'large'; +} + +// eslint-disable-next-line react/prefer-stateless-function +class A extends React.Component { + render() { + return <>Hello World! + } +} + +export default A; diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/excludes.tsx b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/excludes.tsx new file mode 100644 index 0000000000..9331449b2c --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/excludes.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +export interface IAwithExcludesProps { + /** + * extra CSS className for this component + */ + className?: string; + /** + * inline styles + */ + style?: React.CSSProperties; + /** + * component size + * @default small + */ + size: 'small' | 'large'; +} + +// eslint-disable-next-line react/prefer-stateless-function +class AwithExcludes extends React.Component { + render() { + return <>Hello World! + } +} + +export default AwithExcludes; diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/extends.tsx b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/extends.tsx new file mode 100644 index 0000000000..69c03f4b79 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/extends.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import type { IAProps } from './class'; + +export interface IBProps extends IAProps { + /** + * @default true + */ + isB: boolean; +} + +// eslint-disable-next-line react/prefer-stateless-function +class B extends React.Component { + render() { + return <>Hello World! + } +} + +export default B; diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/fc.tsx b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/fc.tsx new file mode 100644 index 0000000000..c34e60e1c5 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/fc.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +export interface IAProps { + /** + * extra CSS className for this component + */ + className?: string; + /** + * inline styles + */ + style?: React.CSSProperties; + /** + * component size + * @default small + */ + size: 'small' | 'large'; +} + +const A: React.FC = () => <>Hello World! + +export default A; \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/forwardRef.tsx b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/forwardRef.tsx new file mode 100644 index 0000000000..c6449a4fc3 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/forwardRef.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import type { IAProps } from './class'; +import A from './class'; + +export interface IBProps extends IAProps { + ref?: React.LegacyRef, +} + +class B extends React.Component { + render() { + const { ref, ...props } = this.props; + + return ( +
+ +
+ ); + } +} + +export default React.forwardRef((props, ref) => ( + // FIXME: ts type error + // @ts-ignore + +)); diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/guess/FileName.tsx b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/guess/FileName.tsx new file mode 100644 index 0000000000..06e46ce6fe --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/guess/FileName.tsx @@ -0,0 +1 @@ +export { default } from '../fc'; \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/guess/NestSrc/src/index.tsx b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/guess/NestSrc/src/index.tsx new file mode 100644 index 0000000000..c71b833e2c --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/guess/NestSrc/src/index.tsx @@ -0,0 +1 @@ +export { default } from '../../../fc'; \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/localeDescription.tsx b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/localeDescription.tsx new file mode 100644 index 0000000000..5d4440b6b0 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/localeDescription.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +export interface IAProps { + /** + * @description default version + * @description.zh-CN 中文说明 + * @default { type: 'A', value: [{ name: 'B' }] } + */ + className?: string; +} + +const A: React.FC = () => <>Hello World! + +export default A; \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/multiple.tsx b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/multiple.tsx new file mode 100644 index 0000000000..9b5fe4d0ab --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/multiple.tsx @@ -0,0 +1,7 @@ +import type React from 'react'; + +export { default as A } from './class'; + +const Main: React.FC<{ isHello: boolean; }> = () => null; + +export default Main; diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/propFilter.tsx b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/propFilter.tsx new file mode 100644 index 0000000000..42ec9aad9e --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/propFilter.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +export interface IAwithPropFilterAProps { + /** + * extra CSS className for this component + */ + className?: string; + /** + * inline styles + */ + style?: React.CSSProperties; + /** + * component size + * @default small + */ + size: 'small' | 'large'; +} + +// eslint-disable-next-line react/prefer-stateless-function +class AwithPropFilter extends React.Component { + render() { + return <>Hello World! + } +} + +export default AwithPropFilter; diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/skipEmptyDoc.tsx b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/skipEmptyDoc.tsx new file mode 100644 index 0000000000..3a79d91e67 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/skipEmptyDoc.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +export interface IAwithEmptyDocProps { + /** + * extra CSS className for this component + */ + className?: string; + style?: React.CSSProperties; + /** + * component size + * @default small + */ + size: 'small' | 'large'; +} + +// eslint-disable-next-line react/prefer-stateless-function +class AwithEmptyDoc extends React.Component { + render() { + return <>Hello World! + } +} + +export default AwithEmptyDoc; diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/skipNodeModules.tsx b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/skipNodeModules.tsx new file mode 100644 index 0000000000..74814a92e8 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/skipNodeModules.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import type { Node } from 'unist'; + +export interface IAextendsNodeProps extends Node { + /** + * extra CSS className for this component + */ + className?: string; + /** + * inline styles + */ + style?: React.CSSProperties; + /** + * component size + * @default small + */ + size: 'small' | 'large'; +} + +// eslint-disable-next-line react/prefer-stateless-function +class AextendsNode extends React.Component { + render() { + return <>Hello World! + } +} + +export default AextendsNode; diff --git a/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/union.tsx b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/union.tsx new file mode 100644 index 0000000000..16e81dcd4d --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures-parser/raw/union.tsx @@ -0,0 +1,18 @@ +import type React from 'react'; + +interface IA1Props { + type: 'primary'; + className?: string; +} + +interface IA2Props { + type: 'link'; + href: string; + className?: string; +} + +type IAProps = IA1Props | IA2Props; + +const A: React.FC = () => null; + +export default A; diff --git a/packages/plugin-dumi-api/__tests__/fixtures/alias/.umirc.ts b/packages/plugin-dumi-api/__tests__/fixtures/alias/.umirc.ts new file mode 100644 index 0000000000..4801dc620f --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures/alias/.umirc.ts @@ -0,0 +1,10 @@ +import path from 'path'; + +export default { + history: { type: "memory" }, + mountElementId: "", + alias: { + '@': path.resolve(__dirname, './component'), + } +}; + \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures/alias/component/Hello/index.tsx b/packages/plugin-dumi-api/__tests__/fixtures/alias/component/Hello/index.tsx new file mode 100644 index 0000000000..8550d6085f --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures/alias/component/Hello/index.tsx @@ -0,0 +1,8 @@ +interface IHelloProps { + className?: string; + name: string; +} +export default ({ className, name }: IHelloProps) => { + return
Hello World, ${name}
; +}; + \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures/alias/docs/index.md b/packages/plugin-dumi-api/__tests__/fixtures/alias/docs/index.md new file mode 100644 index 0000000000..ec054ba469 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures/alias/docs/index.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures/custom/.umirc.ts b/packages/plugin-dumi-api/__tests__/fixtures/custom/.umirc.ts new file mode 100644 index 0000000000..1ecb0db9cb --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures/custom/.umirc.ts @@ -0,0 +1,4 @@ +export default { + history: { type: 'memory' }, + mountElementId: '', +} \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures/custom/docs/Hello/index.tsx b/packages/plugin-dumi-api/__tests__/fixtures/custom/docs/Hello/index.tsx new file mode 100644 index 0000000000..8550d6085f --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures/custom/docs/Hello/index.tsx @@ -0,0 +1,8 @@ +interface IHelloProps { + className?: string; + name: string; +} +export default ({ className, name }: IHelloProps) => { + return
Hello World, ${name}
; +}; + \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures/custom/docs/index.md b/packages/plugin-dumi-api/__tests__/fixtures/custom/docs/index.md new file mode 100644 index 0000000000..1b584a982e --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures/custom/docs/index.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures/defineExport/.umirc.ts b/packages/plugin-dumi-api/__tests__/fixtures/defineExport/.umirc.ts new file mode 100644 index 0000000000..1ecb0db9cb --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures/defineExport/.umirc.ts @@ -0,0 +1,4 @@ +export default { + history: { type: 'memory' }, + mountElementId: '', +} \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures/defineExport/docs/index.md b/packages/plugin-dumi-api/__tests__/fixtures/defineExport/docs/index.md new file mode 100644 index 0000000000..18036382f4 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures/defineExport/docs/index.md @@ -0,0 +1,5 @@ +--- +componentName: Helloooo +--- + + diff --git a/packages/plugin-dumi-api/__tests__/fixtures/defineExport/docs/index.tsx b/packages/plugin-dumi-api/__tests__/fixtures/defineExport/docs/index.tsx new file mode 100644 index 0000000000..ecc295db61 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures/defineExport/docs/index.tsx @@ -0,0 +1,7 @@ +import type React from 'react'; + +const Hello: React.FC<{ className?: string }> = () => null; + +export const World = Hello; + +export default Hello; \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures/index.ts b/packages/plugin-dumi-api/__tests__/fixtures/index.ts new file mode 100644 index 0000000000..2b6b53f1e5 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures/index.ts @@ -0,0 +1,7 @@ +export default () => { + return { + plugins: [ + require.resolve('../../src/index') + ], + }; + }; diff --git a/packages/plugin-dumi-api/__tests__/fixtures/normal/.umirc.ts b/packages/plugin-dumi-api/__tests__/fixtures/normal/.umirc.ts new file mode 100644 index 0000000000..1ecb0db9cb --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures/normal/.umirc.ts @@ -0,0 +1,4 @@ +export default { + history: { type: 'memory' }, + mountElementId: '', +} \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures/normal/docs/index.md b/packages/plugin-dumi-api/__tests__/fixtures/normal/docs/index.md new file mode 100644 index 0000000000..9bf88aba1a --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures/normal/docs/index.md @@ -0,0 +1,5 @@ +--- +componentName: Helloooo +--- + + diff --git a/packages/plugin-dumi-api/__tests__/fixtures/normal/docs/index.tsx b/packages/plugin-dumi-api/__tests__/fixtures/normal/docs/index.tsx new file mode 100644 index 0000000000..8550d6085f --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures/normal/docs/index.tsx @@ -0,0 +1,8 @@ +interface IHelloProps { + className?: string; + name: string; +} +export default ({ className, name }: IHelloProps) => { + return
Hello World, ${name}
; +}; + \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/index.test.ts b/packages/plugin-dumi-api/__tests__/index.test.ts new file mode 100644 index 0000000000..b62a873e69 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/index.test.ts @@ -0,0 +1,130 @@ +import { join } from 'path'; +import { Service } from '@umijs/core'; +import fs from 'fs'; + +const fixtures = join(__dirname, 'fixtures'); + +describe('@umijs/plugin-dumi-api', () => { + test('init', async () => { + const service = new Service({ + cwd: fixtures, + presets: [require.resolve('@umijs/preset-built-in'), require.resolve('@umijs/preset-dumi')], + }); + + await expect(service.init()).resolves.not.toThrowError(); + }); + + test('normal parse apis.json', async () => { + const cwd = join(fixtures, 'normal'); + const service = new Service({ + cwd, + presets: [ + require.resolve('@umijs/preset-built-in'), + require.resolve('@umijs/preset-dumi'), + require.resolve('./fixtures/index'), + ], + }); + + await service.run({ + name: 'g', + args: { + _: ['g', 'tmp'], + }, + }); + + const apis = JSON.parse( + fs.readFileSync(join(cwd, '.umi-test', 'dumi', 'apis.json')).toString(), + ); + expect(apis).toEqual({ + Helloooo: { + default: [ + { + identifier: 'className', + type: 'string', + }, + { + identifier: 'name', + type: 'string', + required: true, + }, + ], + }, + }); + }); + + test('Should support to customize src', async () => { + const cwd = join(fixtures, 'custom'); + const service = new Service({ + cwd, + presets: [ + require.resolve('@umijs/preset-built-in'), + require.resolve('@umijs/preset-dumi'), + require.resolve('./fixtures/index'), + ], + }); + + await service.run({ + name: 'g', + args: { + _: ['g', 'tmp'], + }, + }); + + const apis = JSON.parse( + fs.readFileSync(join(cwd, '.umi-test', 'dumi', 'apis.json')).toString(), + ); + expect(apis).toEqual({ + Hello: { + default: [ + { + identifier: 'className', + type: 'string', + }, + { + identifier: 'name', + type: 'string', + required: true, + }, + ], + }, + }); + }); + + test('Should support to alias src', async () => { + const cwd = join(fixtures, 'alias'); + const service = new Service({ + cwd, + presets: [ + require.resolve('@umijs/preset-built-in'), + require.resolve('@umijs/preset-dumi'), + require.resolve('./fixtures/index'), + ], + }); + + await service.run({ + name: 'g', + args: { + _: ['g', 'tmp'], + }, + }); + + const apis = JSON.parse( + fs.readFileSync(join(cwd, '.umi-test', 'dumi', 'apis.json')).toString(), + ); + expect(apis).toEqual({ + Hello: { + default: [ + { + identifier: 'className', + type: 'string', + }, + { + identifier: 'name', + type: 'string', + required: true, + }, + ], + }, + }); + }); +}); diff --git a/packages/plugin-dumi-api/__tests__/parser.test.ts b/packages/plugin-dumi-api/__tests__/parser.test.ts new file mode 100644 index 0000000000..b95dbc7425 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/parser.test.ts @@ -0,0 +1,91 @@ +import fs from 'fs'; +import path from 'path'; +import ctx from '@umijs/preset-dumi/lib/context'; +import { winEOL } from '@umijs/utils'; +import parser from '../src/parser'; + +const rawPath = path.join(__dirname, 'fixtures-parser', 'raw'); +const expectPath = path.join(__dirname, 'fixtures-parser', 'expect'); + +function assertResult(filename: string, extraProperties?: Record) { + expect( + winEOL(JSON.stringify(parser(path.join(rawPath, filename), extraProperties), null, 2)), + ).toEqual( + winEOL( + fs + .readFileSync(path.join(expectPath, `${path.basename(filename, '.tsx')}.json`), 'utf8') + .toString(), + ), + ); +} + +describe('api parser', () => { + it('should parse normal class component', () => { + assertResult('class.tsx'); + }); + + it('should parse normal function component', () => { + assertResult('fc.tsx'); + }); + + it('should parse extended class component', () => { + assertResult('extends.tsx'); + }); + + it('should parse forward ref component', () => { + assertResult('forwardRef.tsx'); + }); + + it('should parse union types', () => { + assertResult('union.tsx'); + }); + + it('should parse multiple exports', () => { + assertResult('multiple.tsx'); + }); + + it('should parse locale description', () => { + assertResult('localeDescription.tsx'); + }); + + it('should parse with global propFilter', () => { + const oOpts = ctx.opts; + + ctx.opts = { + apiParser: { + propFilter: (prop: any) => { + return prop.name !== 'style'; + }, + }, + } as any; + assertResult('propFilter.tsx'); + ctx.opts = oOpts; + }); + + it('should parse with skipEmptyDoc', () => { + assertResult('skipEmptyDoc.tsx', { + skipPropsWithoutDoc: true, + }); + }); + + it('should parse with skipNodeModules', () => { + assertResult('skipNodeModules.tsx', { + skipNodeModules: true, + }); + }); + + it('should parse with skipPropsWithName', () => { + assertResult('excludes.tsx', { + skipPropsWithName: ['className'], + }); + }); + + it('should guess component name correctly', () => { + expect(parser(path.join(rawPath, 'guess', 'FileName.tsx'))).toHaveProperty('FileName'); + expect( + parser(path.join(rawPath, 'guess', 'NestSrc', 'src', 'index.tsx'), { + componentName: 'NestSrc', + }), + ).toHaveProperty('default'); + }); +}); diff --git a/packages/plugin-dumi-api/__tests__/useApiData.test.tsx b/packages/plugin-dumi-api/__tests__/useApiData.test.tsx new file mode 100644 index 0000000000..9b142fa3f2 --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/useApiData.test.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import type { IThemeContext } from '@umijs/preset-dumi/lib/theme/context'; +import Context from '@umijs/preset-dumi/lib/theme/context'; +import useApiData from '../src/useApiData'; + +describe('theme API: useApiData', () => { + const wrapper = ({ children }) => ( + + {children} + + ); + + it('should get normal api data', async () => { + const { result } = renderHook(() => useApiData('Normal'), { wrapper }); + + expect(result.current).toEqual({ default: [{ identifier: 'normal', type: 'string' }] }); + }); + + it('should get api data with locale description', async () => { + const { result } = renderHook(() => useApiData('LocaleDescription'), { wrapper }); + + expect(result.current).toEqual({ + default: [ + { + identifier: 'locale', + type: 'string', + description: 'default description', + }, + ], + }); + }); +}); diff --git a/packages/plugin-dumi-api/package.json b/packages/plugin-dumi-api/package.json new file mode 100644 index 0000000000..bd72377269 --- /dev/null +++ b/packages/plugin-dumi-api/package.json @@ -0,0 +1,42 @@ +{ + "name": "@umijs/plugin-dumi-api", + "version": "1.0.0", + "description": "@umijs/plugin-dumi-api", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "files": [ + "lib" + ], + "repository": { + "type": "git", + "url": "https://github.com/umijs/dumi" + }, + "keywords": [ + "dumi", + "father-build", + "umi" + ], + "authors": [ + "Peach (https://github.com/PeachScript)" + ], + "license": "MIT", + "bugs": "http://github.com/umijs/dumi/issues", + "homepage": "https://github.com/umijs/dumi/tree/master/packages/create-dumi-lib#readme", + "devDependencies": { + "@testing-library/jest-dom": "^5.11.3", + "@testing-library/react": "^10.4.8", + "@testing-library/react-hooks": "^3.3.0", + "@types/node": "^13.7.7", + "@umijs/types": "3.x", + "dumi-assets-types": "^1.0.0", + "react": "^16.12.0", + "react-dom": "^16.12.0" + }, + "dependencies": { + "@umijs/preset-dumi": "1.1.32", + "@umijs/utils": "^3.0.5", + "deepmerge": "^4.2.2", + "hast-util-has-property": "^1.0.4", + "react-docgen-typescript-dumi-tmp": "^1.22.1-0" + } +} diff --git a/packages/plugin-dumi-api/src/API.tsx b/packages/plugin-dumi-api/src/API.tsx new file mode 100644 index 0000000000..faffc4f375 --- /dev/null +++ b/packages/plugin-dumi-api/src/API.tsx @@ -0,0 +1,74 @@ +import React, { useContext } from 'react'; +import { context } from 'dumi/theme'; +import useApiData from './useApiData'; + +interface IApiComponentProps { + /** + * api data identifier + * @note it is the component identifier by default + * will fallback to the src path on element if component identifier is not available + */ + identifier: string; + /** + * which export should be displayed + */ + export: string; + /** + * whether the title is hidden when compiling + */ + hideTitle: boolean; +} + +const LOCALE_TEXTS = { + 'zh-CN': { + name: '属性名', + description: '描述', + type: '类型', + default: '默认值', + required: '(必选)', + }, + 'en-US': { + name: 'Name', + description: 'Description', + type: 'Type', + default: 'Default', + required: '(required)', + }, +}; + +export default ({ identifier, export: expt }: IApiComponentProps) => { + const data = useApiData(identifier); + const { locale } = useContext(context); + const texts = /^zh|cn$/i.test(locale!) ? LOCALE_TEXTS['zh-CN'] : LOCALE_TEXTS['en-US']; + + return ( + <> + {data && ( + + + + + + + + + + + {data[expt].map(row => ( + + + + + + + ))} + +
{texts.name}{texts.description}{texts.type}{texts.default}
{row.identifier}{row.description || '--'} + {row.type} + + {row.default || (row.required && texts.required) || '--'} +
+ )} + + ); +}; diff --git a/packages/plugin-dumi-api/src/index.ts b/packages/plugin-dumi-api/src/index.ts new file mode 100644 index 0000000000..bb83833fd3 --- /dev/null +++ b/packages/plugin-dumi-api/src/index.ts @@ -0,0 +1,242 @@ +// ref: +// - https://umijs.org/plugins/api +import { IApi } from '@umijs/types'; +import path from 'path'; +import has from 'hast-util-has-property'; +import type { ArgsType } from '@umijs/utils'; +import { getModuleResolvePath } from '@umijs/preset-dumi/lib/utils/moduleResolver'; +import type { IMarkdwonComponent } from '@umijs/preset-dumi/lib/transformer/remark/mdComponent'; +import { parseElmAttrToProps } from '@umijs/preset-dumi/lib/transformer/remark/utils'; +import { listenFileOnceChange } from '@umijs/preset-dumi/lib/utils/watcher'; +import { setOptions } from '@umijs/preset-dumi/lib/context'; +import parser, { IApiDefinition } from './parser'; +import deepmerge from 'deepmerge'; +import type { IDumiElmNode } from '@umijs/preset-dumi/lib/transformer/remark'; + +function guessComponentName(fileAbsPath: string) { + const parsed = path.parse(fileAbsPath); + + if (['index', 'index.d'].includes(parsed.name)) { + // button/index.tsx => button + // packages/button/src/index.tsx => button + // packages/button/lib/index.d.ts => button + // windows: button\\src\\index.tsx => button + // windows: button\\lib\\index.d.ts => button + return path.basename(parsed.dir.replace(/(\/|\\)(src|lib)$/, '')); + } + + // components/button.tsx => button + return parsed.name; +} + +function applyApiData(api, identifier: string, definitions: ReturnType) { + if (identifier && definitions) { + api.applyPlugins({ + key: 'dumi.detectApi', + type: api.ApplyPluginsType.event, + args: { + identifier, + data: definitions, + }, + }); + } +} + +/** + * watch component change to update api data + * @param absPath component absolute path + * @param identifier api identifier + * @param parseOpts extra parse options + */ +function watchComponentUpdate( + api, + absPath: string, + identifier: string, + parseOpts: ArgsType[1], +) { + listenFileOnceChange(absPath, () => { + let definitions: ReturnType; + + try { + definitions = parser(absPath, parseOpts); + } catch (err) { + /* noting */ + } + + // update api data + applyApiData(api, identifier, definitions); + + // watch next turn + watchComponentUpdate(api, absPath, identifier, parseOpts); + }); +} + +function serializeAPINodes( + node: IDumiElmNode, + identifier: string, + definitions: ReturnType, +) { + const parsedAttrs = parseElmAttrToProps(node.properties); + const expts: string[] = parsedAttrs.exports || Object.keys(definitions); + const showTitle = !parsedAttrs.hideTitle; + + return expts.reduce((list, expt, i) => { + // render large API title if it is default export + // or it is the first export and the exports attribute was not custom + const isInsertAPITitle = expt === 'default' || (!i && !parsedAttrs.exports); + // render sub title for non-default export + const isInsertSubTitle = expt !== 'default'; + const apiNode = deepmerge({}, node); + + // insert API title + if (showTitle && isInsertAPITitle) { + list.push( + { + type: 'element', + tagName: 'h2', + properties: {}, + // @ts-ignore + children: [{ type: 'text', value: 'API' }], + }, + { + type: 'text', + value: '\n', + }, + ); + } + + // insert export sub title + if (showTitle && isInsertSubTitle) { + list.push( + { + type: 'element', + tagName: 'h3', + properties: { id: `api-${expt.toLowerCase()}` }, + // @ts-ignore + children: [{ type: 'text', value: expt }], + }, + { + type: 'text', + value: '\n', + }, + ); + } + + // insert API Node + delete apiNode.properties.exports; + apiNode.properties.identifier = identifier; + apiNode.properties.export = expt; + + list.push(apiNode); + + return list; + }, []); +} + +export default function (api: IApi) { + const apis: Record = {}; + const generateApisFile = () => { + api.writeTmpFile({ + path: 'dumi/apis.json', + content: JSON.stringify(apis, null, 2), + }); + }; + + // write all apis into .umi dir + api.onGenerateFiles(() => { + generateApisFile(); + }); + + // register demo detections + api.register({ + key: 'dumi.detectApi', + fn({ identifier, data }) { + const isUpdated = Boolean(apis[identifier]); + + apis[identifier] = data; + + if (isUpdated) { + generateApisFile(); + } + }, + }); + + api.describe({ + key: 'apiParser', + config: { + schema(joi) { + return joi.object(); + }, + default: {}, + onChange: api.ConfigChangeType.regenerateTmpFiles, + }, + }); + + // share config with other source module via context + api.modifyConfig(memo => { + setOptions('apiParser', memo.apiParser); + + return memo; + }); + + api.register({ + key: 'dumi.registerMdComponent', + fn: (): IMarkdwonComponent => ({ + name: 'API', + component: path.join(__dirname, 'API.js'), + compiler(node, i, parent, vFile) { + let identifier: string; + let definitions: ReturnType; + const parseOpts = parseElmAttrToProps(node.properties); + + if (has(node, 'src')) { + const src = node.properties.src || ''; + let absPath = path.join(path.dirname(this.data('fileAbsPath') as string), src); + try { + absPath = getModuleResolvePath({ + basePath: process.cwd(), + sourcePath: src, + silent: true, + }); + } catch (err) { + // nothing + } + // guess component name if there has no identifier property + const componentName = node.properties.identifier || guessComponentName(absPath); + + parseOpts.componentName = componentName; + definitions = parser(absPath, parseOpts); + identifier = componentName || src; + + // trigger listener to update previewer props after this file changed + watchComponentUpdate(api, absPath, identifier, parseOpts); + } else if ((vFile.data as any).componentName) { + try { + const sourcePath = getModuleResolvePath({ + basePath: process.cwd(), + sourcePath: path.dirname(this.data('fileAbsPath') as string), + silent: true, + }); + + parseOpts.componentName = (vFile.data as any).componentName; + definitions = parser(sourcePath, parseOpts); + identifier = (vFile.data as any).componentName; + + // trigger listener to update previewer props after this file changed + watchComponentUpdate(api, sourcePath, identifier, parseOpts); + } catch (err) { + /* noting */ + } + } + + // @ts-ignore + if (identifier && definitions) { + // replace original node + parent!.children.splice(i, 1, ...serializeAPINodes(node, identifier, definitions)); + // apply api data + applyApiData(api, identifier, definitions); + } + }, + }), + }); +} diff --git a/packages/plugin-dumi-api/src/parser.ts b/packages/plugin-dumi-api/src/parser.ts new file mode 100644 index 0000000000..51990afe61 --- /dev/null +++ b/packages/plugin-dumi-api/src/parser.ts @@ -0,0 +1,151 @@ +import * as parser from 'react-docgen-typescript-dumi-tmp'; +import { buildFilter as getBuiltinFilter } from 'react-docgen-typescript-dumi-tmp/lib/buildFilter'; +import FileCache from '@umijs/preset-dumi/lib/utils/cache'; +import ctx from '@umijs/preset-dumi/lib/context'; +import type { AtomPropsDefinition } from 'dumi-assets-types'; +import type { + PropFilter as IPropFilter, + PropItem as IPropItem, +} from 'react-docgen-typescript-dumi-tmp/lib/parser'; +import type { IDumiOpts, IStaticPropFilter } from '@umijs/preset-dumi/lib/context'; + +const cacher = new FileCache(); +// ref: https://github.com/styleguidist/react-docgen-typescript/blob/048980a/src/parser.ts#L1110 +const DEFAULT_EXPORTS = [ + 'default', + '__function', + 'Stateless', + 'StyledComponentClass', + 'StyledComponent', + 'FunctionComponent', + 'StatelessComponent', + 'ForwardRefExoticComponent', +]; + +export type IApiDefinition = AtomPropsDefinition; + +/** + * implement skipNodeModules filter option + * @param prop api prop item, from parser + * @param opts filter options + */ +function extraFilter(prop: IPropItem, opts: IStaticPropFilter) { + // check within node_modules + if (opts.skipNodeModules && prop.declarations?.find(d => d.fileName.includes('node_modules'))) { + return false; + } + + return true; +} + +export default ( + filePath: string, + { componentName, ...filterOpts }: IStaticPropFilter & { componentName?: string } = {}, +) => { + let definitions: IApiDefinition = cacher.get(filePath); + let localFilter: IDumiOpts['apiParser']['propFilter'] = filterOpts; + const globalFilter = ctx.opts?.apiParser?.propFilter; + const isDefaultRegExp = new RegExp(`^${componentName}$`, 'i'); + + switch (typeof globalFilter) { + // always use global filter if it is funuction + case 'function': + localFilter = globalFilter; + break; + + // merge passed opts & global opts, and create custom filter + default: + localFilter = ( + (mergedOpts): IPropFilter => + (prop, component) => { + const builtinFilter = getBuiltinFilter({ propFilter: mergedOpts }); + + return builtinFilter(prop, component) && extraFilter(prop, mergedOpts); + } + )(Object.assign({}, globalFilter, localFilter)); + } + + // use cache first + if (!definitions) { + let defaultDefinition: IApiDefinition[''] | null = null; + + definitions = {}; + parser + .withCompilerOptions( + { esModuleInterop: true, jsx: 'preserve' as any }, + { + savePropValueAsString: true, + shouldExtractLiteralValuesFromEnum: true, + shouldRemoveUndefinedFromOptional: true, + componentNameResolver: source => { + // use parsed component name from remark pipeline as default export's displayName + return DEFAULT_EXPORTS.includes(source.getName()) ? componentName : undefined; + }, + propFilter: localFilter, + }, + ) + .parse(filePath) + .forEach(item => { + // convert result to IApiDefinition + const exportName = isDefaultRegExp.test(item.displayName) ? 'default' : item.displayName; + const props = Object.entries(item.props).map(([identifier, prop]) => { + const result = { identifier } as IApiDefinition[''][0]; + const fields = ['identifier', 'description', 'type', 'defaultValue', 'required']; + const localeDescReg = /(?:^|\n+)@description\s+/; + + fields.forEach(field => { + switch (field) { + case 'type': + result.type = prop.type.raw || prop.type.name; + break; + + case 'description': + // the workaround way for support locale description + // detect locale description content, such as @description.zh-CN xxx + if (localeDescReg.test(prop.description)) { + // split by @description symbol + const groups = prop.description.split(localeDescReg).filter(Boolean); + + groups?.forEach(str => { + const [, locale, content] = str.match(/^(\.[\w-]+)?\s*([^]*)$/)!; + + result[`description${locale || ''}`] = content; + }); + } else if (prop.description) { + result.description = prop.description; + } + break; + + case 'defaultValue': + if (prop[field]) { + result.default = prop[field].value; + } + break; + + default: + if (prop[field]) { + result[field] = prop[field]; + } + } + }); + + return result; + }); + + if (exportName === 'default') { + defaultDefinition = props; + } else { + definitions[exportName] = props; + } + }); + + // to make sure default export always in the top + if (defaultDefinition) { + definitions = Object.assign({ default: defaultDefinition }, definitions); + } + } + + cacher.add(filePath, definitions); + + return definitions; +}; diff --git a/packages/plugin-dumi-api/src/useApiData.ts b/packages/plugin-dumi-api/src/useApiData.ts new file mode 100644 index 0000000000..e7761110b7 --- /dev/null +++ b/packages/plugin-dumi-api/src/useApiData.ts @@ -0,0 +1,60 @@ +import { useState, useEffect, useContext } from 'react'; +// @ts-ignore +import apis from '@@/dumi/apis'; +import context from '@umijs/preset-dumi/lib/theme/context'; +import type { AtomPropsDefinition } from 'dumi-assets-types'; +/** + * get API data + * @param identifier component name + * @param locale current locale + * @param isDefaultLocale default locale flag + */ +function getApiData(identifier: string, locale: string, isDefaultLocale: boolean) { + return Object.entries(apis[identifier] as AtomPropsDefinition).reduce( + (expts, [expt, rows]) => { + expts[expt] = rows.map(props => { + // copy original data + const result = Object.assign({}, props); + + Object.keys(props).forEach(prop => { + // discard useless locale property + if (/^description(\.|$)/.test(prop)) { + const [, propLocale] = prop.match(/^description\.?(.*)$/)!; + + if ((propLocale && propLocale !== locale) || (!propLocale && !isDefaultLocale)) { + delete result[prop]; + } else { + result.description = result[prop]; + } + } + }); + + return result; + }); + + return expts; + }, + {}, + ); +} + +/** + * use api data by identifier + * @note identifier is component name or component path + */ +export default (identifier: string) => { + const { + locale, + config: { locales }, + } = useContext(context); + const isDefaultLocale = !locales.length || locales[0].name === locale; + const [data, setData] = useState( + getApiData(identifier, locale!, isDefaultLocale), + ); + + useEffect(() => { + setData(getApiData(identifier, locale!, isDefaultLocale)); + }, [identifier, locale, isDefaultLocale]); + + return data; +}; diff --git a/packages/plugin-dumi-api/tsconfig.json b/packages/plugin-dumi-api/tsconfig.json new file mode 100644 index 0000000000..46ef4b7e45 --- /dev/null +++ b/packages/plugin-dumi-api/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "esnext", + "moduleResolution": "node", + "jsx": "preserve", + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true + }, + "files": ["./src/theme/index.ts"] +} From e523953a21475a5bdaed8c2eeb1e4f713a8f8c22 Mon Sep 17 00:00:00 2001 From: mortalYoung Date: Sat, 20 Nov 2021 21:42:12 +0800 Subject: [PATCH 2/5] feat: remove original API plugin --- package.json | 1 + .../__tests__/useApiData.test.tsx | 39 ++++ packages/plugin-dumi-api/package.json | 2 +- packages/plugin-dumi-api/src/index.ts | 10 +- packages/plugin-dumi-api/src/useApiData.ts | 19 +- .../src/api-parser/fixtures/expect/class.json | 21 -- .../api-parser/fixtures/expect/excludes.json | 16 -- .../api-parser/fixtures/expect/extends.json | 27 --- .../src/api-parser/fixtures/expect/fc.json | 21 -- .../fixtures/expect/forwardRef.json | 29 --- .../fixtures/expect/localeDescription.json | 11 - .../api-parser/fixtures/expect/multiple.json | 28 --- .../fixtures/expect/propFilter.json | 16 -- .../fixtures/expect/skipEmptyDoc.json | 16 -- .../fixtures/expect/skipNodeModules.json | 21 -- .../src/api-parser/fixtures/expect/union.json | 18 -- .../src/api-parser/fixtures/raw/class.tsx | 26 --- .../src/api-parser/fixtures/raw/excludes.tsx | 26 --- .../src/api-parser/fixtures/raw/extends.tsx | 18 -- .../src/api-parser/fixtures/raw/fc.tsx | 21 -- .../api-parser/fixtures/raw/forwardRef.tsx | 25 --- .../fixtures/raw/guess/FileName.tsx | 1 - .../fixtures/raw/guess/NestSrc/src/index.tsx | 1 - .../fixtures/raw/localeDescription.tsx | 14 -- .../src/api-parser/fixtures/raw/multiple.tsx | 7 - .../api-parser/fixtures/raw/propFilter.tsx | 26 --- .../api-parser/fixtures/raw/skipEmptyDoc.tsx | 23 -- .../fixtures/raw/skipNodeModules.tsx | 27 --- .../src/api-parser/fixtures/raw/union.tsx | 18 -- .../preset-dumi/src/api-parser/index.test.ts | 87 -------- packages/preset-dumi/src/api-parser/index.ts | 151 ------------- packages/preset-dumi/src/index.ts | 1 - .../preset-dumi/src/plugins/features/api.ts | 53 ----- packages/preset-dumi/src/theme/context.ts | 2 +- .../src/theme/hooks/useApiData.test.tsx | 79 ------- .../preset-dumi/src/theme/hooks/useApiData.ts | 63 ------ packages/preset-dumi/src/theme/index.ts | 20 +- packages/preset-dumi/src/theme/loader.ts | 1 - .../fixtures/remark-api/Hello/World.tsx | 1 - .../fixtures/remark-api/Hello/index.d.ts | 7 - .../fixtures/remark-api/Hello/index.md | 5 - .../fixtures/remark-api/Hello/index.tsx | 7 - .../transformer/fixtures/remark-api/alias.md | 1 - .../fixtures/remark-api/api-slugs.md | 11 - .../fixtures/remark-api/auto-detect/index.md | 1 - .../fixtures/remark-api/auto-detect/index.tsx | 5 - .../fixtures/remark-api/custom-exports.md | 1 - .../fixtures/remark-api/custom-src.md | 1 - .../fixtures/remark-api/guess-name.md | 2 - .../fixtures/remark-api/hide-title.md | 1 - .../remark-api/packages/pkgA/README.md | 1 - .../remark-api/packages/pkgA/src/index.tsx | 7 - .../preset-dumi/src/transformer/remark/api.ts | 207 ------------------ .../src/transformer/remark/index.ts | 3 - .../src/transformer/test/remark-api.test.ts | 118 ---------- .../src/utils/getHostPkgAlias.test.ts | 1 + packages/theme-default/src/builtins/API.tsx | 57 ----- .../theme-default/src/test/index.test.tsx | 5 - 58 files changed, 64 insertions(+), 1363 deletions(-) delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/expect/class.json delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/expect/excludes.json delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/expect/extends.json delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/expect/fc.json delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/expect/forwardRef.json delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/expect/localeDescription.json delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/expect/multiple.json delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/expect/propFilter.json delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/expect/skipEmptyDoc.json delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/expect/skipNodeModules.json delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/expect/union.json delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/raw/class.tsx delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/raw/excludes.tsx delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/raw/extends.tsx delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/raw/fc.tsx delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/raw/forwardRef.tsx delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/raw/guess/FileName.tsx delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/raw/guess/NestSrc/src/index.tsx delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/raw/localeDescription.tsx delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/raw/multiple.tsx delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/raw/propFilter.tsx delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/raw/skipEmptyDoc.tsx delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/raw/skipNodeModules.tsx delete mode 100644 packages/preset-dumi/src/api-parser/fixtures/raw/union.tsx delete mode 100644 packages/preset-dumi/src/api-parser/index.test.ts delete mode 100644 packages/preset-dumi/src/api-parser/index.ts delete mode 100644 packages/preset-dumi/src/plugins/features/api.ts delete mode 100644 packages/preset-dumi/src/theme/hooks/useApiData.test.tsx delete mode 100644 packages/preset-dumi/src/theme/hooks/useApiData.ts delete mode 100644 packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/World.tsx delete mode 100644 packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/index.d.ts delete mode 100644 packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/index.md delete mode 100644 packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/index.tsx delete mode 100644 packages/preset-dumi/src/transformer/fixtures/remark-api/alias.md delete mode 100644 packages/preset-dumi/src/transformer/fixtures/remark-api/api-slugs.md delete mode 100644 packages/preset-dumi/src/transformer/fixtures/remark-api/auto-detect/index.md delete mode 100644 packages/preset-dumi/src/transformer/fixtures/remark-api/auto-detect/index.tsx delete mode 100644 packages/preset-dumi/src/transformer/fixtures/remark-api/custom-exports.md delete mode 100644 packages/preset-dumi/src/transformer/fixtures/remark-api/custom-src.md delete mode 100644 packages/preset-dumi/src/transformer/fixtures/remark-api/guess-name.md delete mode 100644 packages/preset-dumi/src/transformer/fixtures/remark-api/hide-title.md delete mode 100644 packages/preset-dumi/src/transformer/fixtures/remark-api/packages/pkgA/README.md delete mode 100644 packages/preset-dumi/src/transformer/fixtures/remark-api/packages/pkgA/src/index.tsx delete mode 100644 packages/preset-dumi/src/transformer/remark/api.ts delete mode 100644 packages/preset-dumi/src/transformer/test/remark-api.test.ts delete mode 100644 packages/theme-default/src/builtins/API.tsx diff --git a/package.json b/package.json index 6cfc33bd9e..b05c4fad26 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@types/jest": "^24.0.13", "@umijs/fabric": "^2.2.2", "@umijs/plugin-analytics": "^0.2.2", + "@umijs/plugin-dumi-api": "1.0.0", "@umijs/test": "^3.0.3", "antd": "^4.8.2", "babel-plugin-import": "^1.13.0", diff --git a/packages/plugin-dumi-api/__tests__/useApiData.test.tsx b/packages/plugin-dumi-api/__tests__/useApiData.test.tsx index 9b142fa3f2..a0deb93a0e 100644 --- a/packages/plugin-dumi-api/__tests__/useApiData.test.tsx +++ b/packages/plugin-dumi-api/__tests__/useApiData.test.tsx @@ -11,6 +11,45 @@ describe('theme API: useApiData', () => { { locale: 'zh-CN', config: { locales: [{ name: 'zh-CN', label: '中文' }] }, + apis: { + Normal: { + default: [ + { + identifier: 'normal', + type: 'string', + }, + ], + }, + LocaleDescription: { + default: [ + { + identifier: 'locale', + type: 'string', + description: 'default description', + 'description.en-US': 'english description', + }, + ], + }, + MultipleExports: { + default: [], + Other: [ + { + identifier: 'other', + type: 'string', + }, + { + identifier: 'another', + type: 'string', + required: true, + }, + { + identifier: 'anotherAgain', + type: 'string', + default: 'again', + }, + ], + }, + }, } as unknown as IThemeContext } > diff --git a/packages/plugin-dumi-api/package.json b/packages/plugin-dumi-api/package.json index bd72377269..63caf70afc 100644 --- a/packages/plugin-dumi-api/package.json +++ b/packages/plugin-dumi-api/package.json @@ -33,7 +33,7 @@ "react-dom": "^16.12.0" }, "dependencies": { - "@umijs/preset-dumi": "1.1.32", + "@umijs/preset-dumi": "1.1.33", "@umijs/utils": "^3.0.5", "deepmerge": "^4.2.2", "hast-util-has-property": "^1.0.4", diff --git a/packages/plugin-dumi-api/src/index.ts b/packages/plugin-dumi-api/src/index.ts index bb83833fd3..653c9237c8 100644 --- a/packages/plugin-dumi-api/src/index.ts +++ b/packages/plugin-dumi-api/src/index.ts @@ -1,6 +1,7 @@ // ref: // - https://umijs.org/plugins/api import { IApi } from '@umijs/types'; +import fs from 'fs'; import path from 'path'; import has from 'hast-util-has-property'; import type { ArgsType } from '@umijs/utils'; @@ -67,7 +68,14 @@ function watchComponentUpdate( applyApiData(api, identifier, definitions); // watch next turn - watchComponentUpdate(api, absPath, identifier, parseOpts); + // FIXME: workaround for resolve no such file error + /* istanbul ignore next */ + setTimeout( + () => { + watchComponentUpdate(api, absPath, identifier, parseOpts); + }, + fs.existsSync(absPath) ? 0 : 50, + ); }); } diff --git a/packages/plugin-dumi-api/src/useApiData.ts b/packages/plugin-dumi-api/src/useApiData.ts index e7761110b7..205c990a59 100644 --- a/packages/plugin-dumi-api/src/useApiData.ts +++ b/packages/plugin-dumi-api/src/useApiData.ts @@ -1,15 +1,19 @@ import { useState, useEffect, useContext } from 'react'; -// @ts-ignore -import apis from '@@/dumi/apis'; import context from '@umijs/preset-dumi/lib/theme/context'; import type { AtomPropsDefinition } from 'dumi-assets-types'; +import type { IApiDefinition } from './parser'; /** * get API data * @param identifier component name * @param locale current locale * @param isDefaultLocale default locale flag */ -function getApiData(identifier: string, locale: string, isDefaultLocale: boolean) { +function getApiData( + apis: Record, + identifier: string, + locale: string, + isDefaultLocale: boolean, +) { return Object.entries(apis[identifier] as AtomPropsDefinition).reduce( (expts, [expt, rows]) => { expts[expt] = rows.map(props => { @@ -46,15 +50,14 @@ export default (identifier: string) => { const { locale, config: { locales }, + apis, } = useContext(context); const isDefaultLocale = !locales.length || locales[0].name === locale; - const [data, setData] = useState( - getApiData(identifier, locale!, isDefaultLocale), - ); + const [data, setData] = useState(getApiData(apis, identifier, locale, isDefaultLocale)); useEffect(() => { - setData(getApiData(identifier, locale!, isDefaultLocale)); - }, [identifier, locale, isDefaultLocale]); + setData(getApiData(apis, identifier, locale, isDefaultLocale)); + }, [apis, identifier, locale, isDefaultLocale]); return data; }; diff --git a/packages/preset-dumi/src/api-parser/fixtures/expect/class.json b/packages/preset-dumi/src/api-parser/fixtures/expect/class.json deleted file mode 100644 index daa19845f7..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/expect/class.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "A": [ - { - "identifier": "className", - "description": "extra CSS className for this component", - "type": "string" - }, - { - "identifier": "style", - "description": "inline styles", - "type": "CSSProperties" - }, - { - "identifier": "size", - "description": "component size", - "type": "\"small\" | \"large\"", - "default": "small", - "required": true - } - ] -} \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/expect/excludes.json b/packages/preset-dumi/src/api-parser/fixtures/expect/excludes.json deleted file mode 100644 index 18d9a7dc7c..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/expect/excludes.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "AwithExcludes": [ - { - "identifier": "style", - "description": "inline styles", - "type": "CSSProperties" - }, - { - "identifier": "size", - "description": "component size", - "type": "\"small\" | \"large\"", - "default": "small", - "required": true - } - ] -} \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/expect/extends.json b/packages/preset-dumi/src/api-parser/fixtures/expect/extends.json deleted file mode 100644 index 55585614b2..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/expect/extends.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "B": [ - { - "identifier": "isB", - "type": "boolean", - "default": "true", - "required": true - }, - { - "identifier": "className", - "description": "extra CSS className for this component", - "type": "string" - }, - { - "identifier": "style", - "description": "inline styles", - "type": "CSSProperties" - }, - { - "identifier": "size", - "description": "component size", - "type": "\"small\" | \"large\"", - "default": "small", - "required": true - } - ] -} \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/expect/fc.json b/packages/preset-dumi/src/api-parser/fixtures/expect/fc.json deleted file mode 100644 index 8583cd64a5..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/expect/fc.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "fc": [ - { - "identifier": "className", - "description": "extra CSS className for this component", - "type": "string" - }, - { - "identifier": "style", - "description": "inline styles", - "type": "CSSProperties" - }, - { - "identifier": "size", - "description": "component size", - "type": "\"small\" | \"large\"", - "default": "small", - "required": true - } - ] -} \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/expect/forwardRef.json b/packages/preset-dumi/src/api-parser/fixtures/expect/forwardRef.json deleted file mode 100644 index 589b2cba4b..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/expect/forwardRef.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "forwardRef": [ - { - "identifier": "className", - "description": "extra CSS className for this component", - "type": "string" - }, - { - "identifier": "style", - "description": "inline styles", - "type": "CSSProperties" - }, - { - "identifier": "size", - "description": "component size", - "type": "\"small\" | \"large\"", - "default": "small", - "required": true - }, - { - "identifier": "ref", - "type": "Ref" - }, - { - "identifier": "key", - "type": "Key" - } - ] -} \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/expect/localeDescription.json b/packages/preset-dumi/src/api-parser/fixtures/expect/localeDescription.json deleted file mode 100644 index 40a33fb94f..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/expect/localeDescription.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "localeDescription": [ - { - "identifier": "className", - "description": "default version", - "description.zh-CN": "中文说明", - "type": "string", - "default": "{ type: 'A', value: [{ name: 'B' }] }" - } - ] -} \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/expect/multiple.json b/packages/preset-dumi/src/api-parser/fixtures/expect/multiple.json deleted file mode 100644 index 8a46ec6058..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/expect/multiple.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "A": [ - { - "identifier": "className", - "description": "extra CSS className for this component", - "type": "string" - }, - { - "identifier": "style", - "description": "inline styles", - "type": "CSSProperties" - }, - { - "identifier": "size", - "description": "component size", - "type": "\"small\" | \"large\"", - "default": "small", - "required": true - } - ], - "multiple": [ - { - "identifier": "isHello", - "type": "boolean", - "required": true - } - ] -} \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/expect/propFilter.json b/packages/preset-dumi/src/api-parser/fixtures/expect/propFilter.json deleted file mode 100644 index 7aa4b77216..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/expect/propFilter.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "AwithPropFilter": [ - { - "identifier": "className", - "description": "extra CSS className for this component", - "type": "string" - }, - { - "identifier": "size", - "description": "component size", - "type": "\"small\" | \"large\"", - "default": "small", - "required": true - } - ] -} \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/expect/skipEmptyDoc.json b/packages/preset-dumi/src/api-parser/fixtures/expect/skipEmptyDoc.json deleted file mode 100644 index 97806016d5..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/expect/skipEmptyDoc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "AwithEmptyDoc": [ - { - "identifier": "className", - "description": "extra CSS className for this component", - "type": "string" - }, - { - "identifier": "size", - "description": "component size", - "type": "\"small\" | \"large\"", - "default": "small", - "required": true - } - ] -} \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/expect/skipNodeModules.json b/packages/preset-dumi/src/api-parser/fixtures/expect/skipNodeModules.json deleted file mode 100644 index 84ecfbe461..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/expect/skipNodeModules.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "AextendsNode": [ - { - "identifier": "className", - "description": "extra CSS className for this component", - "type": "string" - }, - { - "identifier": "style", - "description": "inline styles", - "type": "CSSProperties" - }, - { - "identifier": "size", - "description": "component size", - "type": "\"small\" | \"large\"", - "default": "small", - "required": true - } - ] -} \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/expect/union.json b/packages/preset-dumi/src/api-parser/fixtures/expect/union.json deleted file mode 100644 index 3a22144065..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/expect/union.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "union": [ - { - "identifier": "type", - "type": "\"primary\" | \"link\"", - "required": true - }, - { - "identifier": "className", - "type": "string" - }, - { - "identifier": "href", - "type": "string", - "required": true - } - ] -} \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/raw/class.tsx b/packages/preset-dumi/src/api-parser/fixtures/raw/class.tsx deleted file mode 100644 index 9897479e56..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/raw/class.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; - -export interface IAProps { - /** - * extra CSS className for this component - */ - className?: string; - /** - * inline styles - */ - style?: React.CSSProperties; - /** - * component size - * @default small - */ - size: 'small' | 'large'; -} - -// eslint-disable-next-line react/prefer-stateless-function -class A extends React.Component { - render() { - return <>Hello World! - } -} - -export default A; diff --git a/packages/preset-dumi/src/api-parser/fixtures/raw/excludes.tsx b/packages/preset-dumi/src/api-parser/fixtures/raw/excludes.tsx deleted file mode 100644 index 9331449b2c..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/raw/excludes.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; - -export interface IAwithExcludesProps { - /** - * extra CSS className for this component - */ - className?: string; - /** - * inline styles - */ - style?: React.CSSProperties; - /** - * component size - * @default small - */ - size: 'small' | 'large'; -} - -// eslint-disable-next-line react/prefer-stateless-function -class AwithExcludes extends React.Component { - render() { - return <>Hello World! - } -} - -export default AwithExcludes; diff --git a/packages/preset-dumi/src/api-parser/fixtures/raw/extends.tsx b/packages/preset-dumi/src/api-parser/fixtures/raw/extends.tsx deleted file mode 100644 index 69c03f4b79..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/raw/extends.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import type { IAProps } from './class'; - -export interface IBProps extends IAProps { - /** - * @default true - */ - isB: boolean; -} - -// eslint-disable-next-line react/prefer-stateless-function -class B extends React.Component { - render() { - return <>Hello World! - } -} - -export default B; diff --git a/packages/preset-dumi/src/api-parser/fixtures/raw/fc.tsx b/packages/preset-dumi/src/api-parser/fixtures/raw/fc.tsx deleted file mode 100644 index c34e60e1c5..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/raw/fc.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; - -export interface IAProps { - /** - * extra CSS className for this component - */ - className?: string; - /** - * inline styles - */ - style?: React.CSSProperties; - /** - * component size - * @default small - */ - size: 'small' | 'large'; -} - -const A: React.FC = () => <>Hello World! - -export default A; \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/raw/forwardRef.tsx b/packages/preset-dumi/src/api-parser/fixtures/raw/forwardRef.tsx deleted file mode 100644 index c6449a4fc3..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/raw/forwardRef.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import type { IAProps } from './class'; -import A from './class'; - -export interface IBProps extends IAProps { - ref?: React.LegacyRef, -} - -class B extends React.Component { - render() { - const { ref, ...props } = this.props; - - return ( - - ); - } -} - -export default React.forwardRef((props, ref) => ( - // FIXME: ts type error - // @ts-ignore - -)); diff --git a/packages/preset-dumi/src/api-parser/fixtures/raw/guess/FileName.tsx b/packages/preset-dumi/src/api-parser/fixtures/raw/guess/FileName.tsx deleted file mode 100644 index 06e46ce6fe..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/raw/guess/FileName.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from '../fc'; \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/raw/guess/NestSrc/src/index.tsx b/packages/preset-dumi/src/api-parser/fixtures/raw/guess/NestSrc/src/index.tsx deleted file mode 100644 index c71b833e2c..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/raw/guess/NestSrc/src/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from '../../../fc'; \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/raw/localeDescription.tsx b/packages/preset-dumi/src/api-parser/fixtures/raw/localeDescription.tsx deleted file mode 100644 index 5d4440b6b0..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/raw/localeDescription.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -export interface IAProps { - /** - * @description default version - * @description.zh-CN 中文说明 - * @default { type: 'A', value: [{ name: 'B' }] } - */ - className?: string; -} - -const A: React.FC = () => <>Hello World! - -export default A; \ No newline at end of file diff --git a/packages/preset-dumi/src/api-parser/fixtures/raw/multiple.tsx b/packages/preset-dumi/src/api-parser/fixtures/raw/multiple.tsx deleted file mode 100644 index 9b5fe4d0ab..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/raw/multiple.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import type React from 'react'; - -export { default as A } from './class'; - -const Main: React.FC<{ isHello: boolean; }> = () => null; - -export default Main; diff --git a/packages/preset-dumi/src/api-parser/fixtures/raw/propFilter.tsx b/packages/preset-dumi/src/api-parser/fixtures/raw/propFilter.tsx deleted file mode 100644 index 42ec9aad9e..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/raw/propFilter.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; - -export interface IAwithPropFilterAProps { - /** - * extra CSS className for this component - */ - className?: string; - /** - * inline styles - */ - style?: React.CSSProperties; - /** - * component size - * @default small - */ - size: 'small' | 'large'; -} - -// eslint-disable-next-line react/prefer-stateless-function -class AwithPropFilter extends React.Component { - render() { - return <>Hello World! - } -} - -export default AwithPropFilter; diff --git a/packages/preset-dumi/src/api-parser/fixtures/raw/skipEmptyDoc.tsx b/packages/preset-dumi/src/api-parser/fixtures/raw/skipEmptyDoc.tsx deleted file mode 100644 index 3a79d91e67..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/raw/skipEmptyDoc.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; - -export interface IAwithEmptyDocProps { - /** - * extra CSS className for this component - */ - className?: string; - style?: React.CSSProperties; - /** - * component size - * @default small - */ - size: 'small' | 'large'; -} - -// eslint-disable-next-line react/prefer-stateless-function -class AwithEmptyDoc extends React.Component { - render() { - return <>Hello World! - } -} - -export default AwithEmptyDoc; diff --git a/packages/preset-dumi/src/api-parser/fixtures/raw/skipNodeModules.tsx b/packages/preset-dumi/src/api-parser/fixtures/raw/skipNodeModules.tsx deleted file mode 100644 index 74814a92e8..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/raw/skipNodeModules.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import type { Node } from 'unist'; - -export interface IAextendsNodeProps extends Node { - /** - * extra CSS className for this component - */ - className?: string; - /** - * inline styles - */ - style?: React.CSSProperties; - /** - * component size - * @default small - */ - size: 'small' | 'large'; -} - -// eslint-disable-next-line react/prefer-stateless-function -class AextendsNode extends React.Component { - render() { - return <>Hello World! - } -} - -export default AextendsNode; diff --git a/packages/preset-dumi/src/api-parser/fixtures/raw/union.tsx b/packages/preset-dumi/src/api-parser/fixtures/raw/union.tsx deleted file mode 100644 index 16e81dcd4d..0000000000 --- a/packages/preset-dumi/src/api-parser/fixtures/raw/union.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type React from 'react'; - -interface IA1Props { - type: 'primary'; - className?: string; -} - -interface IA2Props { - type: 'link'; - href: string; - className?: string; -} - -type IAProps = IA1Props | IA2Props; - -const A: React.FC = () => null; - -export default A; diff --git a/packages/preset-dumi/src/api-parser/index.test.ts b/packages/preset-dumi/src/api-parser/index.test.ts deleted file mode 100644 index 3379706b1a..0000000000 --- a/packages/preset-dumi/src/api-parser/index.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { winEOL } from '@umijs/utils'; -import ctx from '../context'; -import parser from '.'; - -const rawPath = path.join(__dirname, 'fixtures', 'raw'); -const expectPath = path.join(__dirname, 'fixtures', 'expect'); - -function assertResult(filename, extraProperties?) { - expect(winEOL(JSON.stringify(parser(path.join(rawPath, filename), extraProperties), null, 2))).toEqual( - winEOL( - fs - .readFileSync(path.join(expectPath, `${path.basename(filename, '.tsx')}.json`), 'utf8') - .toString(), - ), - ); -} - -describe('api parser', () => { - it('should parse normal class component', () => { - assertResult('class.tsx'); - }); - - it('should parse normal function component', () => { - assertResult('fc.tsx'); - }); - - it('should parse extended class component', () => { - assertResult('extends.tsx'); - }); - - it('should parse forward ref component', () => { - assertResult('forwardRef.tsx'); - }); - - it('should parse union types', () => { - assertResult('union.tsx'); - }); - - it('should parse multiple exports', () => { - assertResult('multiple.tsx'); - }); - - it('should parse locale description', () => { - assertResult('localeDescription.tsx'); - }); - - it('should parse with global propFilter', () => { - const oOpts = ctx.opts; - - ctx.opts = { - apiParser: { - propFilter: (prop) => { - return prop.name !== 'style'; - }, - }, - } as any; - assertResult('propFilter.tsx'); - ctx.opts = oOpts; - }); - - it('should parse with skipEmptyDoc', () => { - assertResult('skipEmptyDoc.tsx', { - skipPropsWithoutDoc: true, - }); - }); - - it('should parse with skipNodeModules', () => { - assertResult('skipNodeModules.tsx', { - skipNodeModules: true, - }); - }); - - it('should parse with skipPropsWithName', () => { - assertResult('excludes.tsx', { - skipPropsWithName: ['className'], - }); - }); - - it('should guess component name correctly', () => { - expect(parser(path.join(rawPath, 'guess', 'FileName.tsx'))).toHaveProperty('FileName'); - expect( - parser(path.join(rawPath, 'guess', 'NestSrc', 'src', 'index.tsx'), { componentName: 'NestSrc' }), - ).toHaveProperty('default'); - }); -}); diff --git a/packages/preset-dumi/src/api-parser/index.ts b/packages/preset-dumi/src/api-parser/index.ts deleted file mode 100644 index df8507c6ed..0000000000 --- a/packages/preset-dumi/src/api-parser/index.ts +++ /dev/null @@ -1,151 +0,0 @@ -import * as parser from 'react-docgen-typescript-dumi-tmp'; -import { buildFilter as getBuiltinFilter } from 'react-docgen-typescript-dumi-tmp/lib/buildFilter'; -import FileCache from '../utils/cache'; -import ctx from '../context'; -import type { AtomPropsDefinition } from 'dumi-assets-types'; -import type { - PropFilter as IPropFilter, - PropItem as IPropItem, -} from 'react-docgen-typescript-dumi-tmp/lib/parser'; -import type { IDumiOpts, IStaticPropFilter } from '../context'; - -const cacher = new FileCache(); -// ref: https://github.com/styleguidist/react-docgen-typescript/blob/048980a/src/parser.ts#L1110 -const DEFAULT_EXPORTS = [ - 'default', - '__function', - 'Stateless', - 'StyledComponentClass', - 'StyledComponent', - 'FunctionComponent', - 'StatelessComponent', - 'ForwardRefExoticComponent', -]; - -export type IApiDefinition = AtomPropsDefinition; - -/** - * implement skipNodeModules filter option - * @param prop api prop item, from parser - * @param opts filter options - */ -function extraFilter(prop: IPropItem, opts: IStaticPropFilter) { - // check within node_modules - if (opts.skipNodeModules && prop.declarations.find(d => d.fileName.includes('node_modules'))) { - return false; - } - - return true; -} - -export default ( - filePath: string, - { componentName, ...filterOpts }: IStaticPropFilter & { componentName?: string } = {}, -) => { - let definitions: IApiDefinition = cacher.get(filePath); - let localFilter: IDumiOpts['apiParser']['propFilter'] = filterOpts; - const globalFilter = ctx.opts?.apiParser?.propFilter; - const isDefaultRegExp = new RegExp(`^${componentName}$`, 'i'); - - switch (typeof globalFilter) { - // always use global filter if it is funuction - case 'function': - localFilter = globalFilter; - break; - - // merge passed opts & global opts, and create custom filter - default: - localFilter = ( - (mergedOpts): IPropFilter => - (prop, component) => { - const builtinFilter = getBuiltinFilter({ propFilter: mergedOpts }); - - return builtinFilter(prop, component) && extraFilter(prop, mergedOpts); - } - )(Object.assign({}, globalFilter, localFilter)); - } - - // use cache first - if (!definitions) { - let defaultDefinition: IApiDefinition['']; - - definitions = {}; - parser - .withCompilerOptions( - { esModuleInterop: true, jsx: 'preserve' as any }, - { - savePropValueAsString: true, - shouldExtractLiteralValuesFromEnum: true, - shouldRemoveUndefinedFromOptional: true, - componentNameResolver: source => { - // use parsed component name from remark pipeline as default export's displayName - return DEFAULT_EXPORTS.includes(source.getName()) ? componentName : undefined; - }, - propFilter: localFilter, - }, - ) - .parse(filePath) - .forEach(item => { - // convert result to IApiDefinition - const exportName = isDefaultRegExp.test(item.displayName) ? 'default' : item.displayName; - const props = Object.entries(item.props).map(([identifier, prop]) => { - const result = { identifier } as IApiDefinition[''][0]; - const fields = ['identifier', 'description', 'type', 'defaultValue', 'required']; - const localeDescReg = /(?:^|\n+)@description\s+/; - - fields.forEach(field => { - switch (field) { - case 'type': - result.type = prop.type.raw || prop.type.name; - break; - - case 'description': - // the workaround way for support locale description - // detect locale description content, such as @description.zh-CN xxx - if (localeDescReg.test(prop.description)) { - // split by @description symbol - const groups = prop.description.split(localeDescReg).filter(Boolean); - - groups?.forEach(str => { - const [, locale, content] = str.match(/^(\.[\w-]+)?\s*([^]*)$/); - - result[`description${locale || ''}`] = content; - }); - } else if (prop.description) { - result.description = prop.description; - } - break; - - case 'defaultValue': - if (prop[field]) { - result.default = prop[field].value; - } - break; - - default: - if (prop[field]) { - result[field] = prop[field]; - } - } - }); - - return result; - }); - - if (exportName === 'default') { - defaultDefinition = props; - } else { - definitions[exportName] = props; - } - }); - - // to make sure default export always in the top - if (defaultDefinition) { - definitions = Object.assign({ default: defaultDefinition }, definitions); - } - } - - cacher.add(filePath, definitions); - - return definitions; -}; diff --git a/packages/preset-dumi/src/index.ts b/packages/preset-dumi/src/index.ts index cb14831d3e..94a3e27433 100644 --- a/packages/preset-dumi/src/index.ts +++ b/packages/preset-dumi/src/index.ts @@ -28,7 +28,6 @@ export default () => { // generate files require.resolve('./plugins/features/demo'), require.resolve('./plugins/features/config'), - require.resolve('./plugins/features/api'), // integrate other umi plugins require.resolve('./plugins/features/extras'), diff --git a/packages/preset-dumi/src/plugins/features/api.ts b/packages/preset-dumi/src/plugins/features/api.ts deleted file mode 100644 index 3594e7e823..0000000000 --- a/packages/preset-dumi/src/plugins/features/api.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { IApi } from '@umijs/types'; -import type { IApiDefinition } from '../../api-parser'; -import { setOptions } from '../../context'; - -/** - * plugin for generate apis.json into .umi temp directory - */ -export default (api: IApi) => { - const apis: Record = {}; - const generateApisFile = () => { - api.writeTmpFile({ - path: 'dumi/apis.json', - content: JSON.stringify(apis, null, 2), - }); - }; - - // write all apis into .umi dir - api.onGenerateFiles(() => { - generateApisFile(); - }); - - // register demo detections - api.register({ - key: 'dumi.detectApi', - fn({ identifier, data }) { - const isUpdated = Boolean(apis[identifier]); - - apis[identifier] = data; - - if (isUpdated) { - generateApisFile(); - } - }, - }); - - api.describe({ - key: 'apiParser', - config: { - schema(joi) { - return joi.object(); - }, - default: {}, - onChange: api.ConfigChangeType.regenerateTmpFiles, - }, - }); - - // share config with other source module via context - api.modifyConfig(memo => { - setOptions('apiParser', memo.apiParser); - - return memo; - }); -}; diff --git a/packages/preset-dumi/src/theme/context.ts b/packages/preset-dumi/src/theme/context.ts index d03a951c96..147cfe427c 100644 --- a/packages/preset-dumi/src/theme/context.ts +++ b/packages/preset-dumi/src/theme/context.ts @@ -3,7 +3,7 @@ import type { IConfig, IRoute } from '@umijs/types'; import type { INav } from '../routes/getNavFromRoutes'; import type { IMenu } from '../routes/getMenuFromRoutes'; import type { ILocale } from '../routes/getLocaleFromRoutes'; -import type { IApiDefinition } from '../api-parser'; +import type { IApiDefinition } from '@umijs/plugin-dumi-api/lib/parser'; import type { IDumiOpts } from '..'; import type { IPreviewerComponentProps } from '.'; diff --git a/packages/preset-dumi/src/theme/hooks/useApiData.test.tsx b/packages/preset-dumi/src/theme/hooks/useApiData.test.tsx deleted file mode 100644 index 2a4883aeb5..0000000000 --- a/packages/preset-dumi/src/theme/hooks/useApiData.test.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; -import type { IThemeContext } from '../context'; -import Context from '../context'; -import useApiData from './useApiData'; - -describe('theme API: useApiData', () => { - const wrapper = ({ children }) => ( - - {children} - - ); - - it('should get normal api data', async () => { - const { result } = renderHook(() => useApiData('Normal'), { wrapper }); - - expect(result.current).toEqual({ default: [{ identifier: 'normal', type: 'string' }] }); - }); - - it('should get api data with locale description', async () => { - const { result } = renderHook(() => useApiData('LocaleDescription'), { wrapper }); - - expect(result.current).toEqual({ - default: [ - { - identifier: 'locale', - type: 'string', - description: 'default description', - }, - ], - }); - }); -}); diff --git a/packages/preset-dumi/src/theme/hooks/useApiData.ts b/packages/preset-dumi/src/theme/hooks/useApiData.ts deleted file mode 100644 index 95cf362fc1..0000000000 --- a/packages/preset-dumi/src/theme/hooks/useApiData.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { useState, useEffect, useContext } from 'react'; -import context from '../context'; -import type { IThemeContext } from '../context'; - -/** - * get API data - * @param identifier component name - * @param locale current locale - * @param isDefaultLocale default locale flag - */ -function getApiData( - apis: IThemeContext['apis'], - identifier: string, - locale: string, - isDefaultLocale: boolean, -) { - return Object.entries(apis[identifier]).reduce( - (expts, [expt, rows]) => { - expts[expt] = rows.map(props => { - // copy original data - const result = Object.assign({}, props); - - Object.keys(props).forEach(prop => { - // discard useless locale property - if (/^description(\.|$)/.test(prop)) { - const [, propLocale] = prop.match(/^description\.?(.*)$/); - - if ((propLocale && propLocale !== locale) || (!propLocale && !isDefaultLocale)) { - delete result[prop]; - } else { - result.description = result[prop]; - } - } - }); - - return result; - }); - - return expts; - }, - {}, - ); -} - -/** - * use api data by identifier - * @note identifier is component name or component path - */ -export default (identifier: string) => { - const { - locale, - config: { locales }, - apis, - } = useContext(context); - const isDefaultLocale = !locales.length || locales[0].name === locale; - const [data, setData] = useState(getApiData(apis, identifier, locale, isDefaultLocale)); - - useEffect(() => { - setData(getApiData(apis, identifier, locale, isDefaultLocale)); - }, [apis, identifier, locale, isDefaultLocale]); - - return data; -}; diff --git a/packages/preset-dumi/src/theme/index.ts b/packages/preset-dumi/src/theme/index.ts index bc28f4bbab..b96c5612e1 100644 --- a/packages/preset-dumi/src/theme/index.ts +++ b/packages/preset-dumi/src/theme/index.ts @@ -11,7 +11,6 @@ export { default as useMotions } from './hooks/useMotions'; export { default as useCodeSandbox } from './hooks/useCodeSandbox'; export { default as useLocaleProps } from './hooks/useLocaleProps'; export { default as useDemoUrl } from './hooks/useDemoUrl'; -export { default as useApiData } from './hooks/useApiData'; export { default as useTSPlaygroundUrl } from './hooks/useTSPlaygroundUrl'; export { default as usePrefersColor } from './hooks/usePrefersColor'; @@ -56,21 +55,4 @@ export interface IPreviewerComponentProps { */ debug?: true; [key: string]: any; -} - -export interface IApiComponentProps { - /** - * api data identifier - * @note it is the component identifier by default - * will fallback to the src path on element if component identifier is not available - */ - identifier: string; - /** - * which export should be displayed - */ - export: string; - /** - * whether the title is hidden when compiling - */ - hideTitle: boolean; -} +} \ No newline at end of file diff --git a/packages/preset-dumi/src/theme/loader.ts b/packages/preset-dumi/src/theme/loader.ts index d212892b08..f5eed97ae3 100644 --- a/packages/preset-dumi/src/theme/loader.ts +++ b/packages/preset-dumi/src/theme/loader.ts @@ -63,7 +63,6 @@ const LOCAL_THEME_PATH = '.dumi/theme'; const FALLBACK_THEME = `${THEME_PREFIX}default`; const REQUIRED_THEME_BUILTINS = [ 'Alert', - 'API', 'Badge', 'Example', 'Previewer', diff --git a/packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/World.tsx b/packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/World.tsx deleted file mode 100644 index 657985aabd..0000000000 --- a/packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/World.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from '.'; \ No newline at end of file diff --git a/packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/index.d.ts b/packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/index.d.ts deleted file mode 100644 index 7439174f77..0000000000 --- a/packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/index.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type React from 'react'; - -export declare const Hello: React.FC<{ className?: string }>; - -export const World: typeof Hello; - -export default Hello; \ No newline at end of file diff --git a/packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/index.md b/packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/index.md deleted file mode 100644 index ea545c03e3..0000000000 --- a/packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/index.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -componentName: Helloooo ---- - - diff --git a/packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/index.tsx b/packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/index.tsx deleted file mode 100644 index ecc295db61..0000000000 --- a/packages/preset-dumi/src/transformer/fixtures/remark-api/Hello/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import type React from 'react'; - -const Hello: React.FC<{ className?: string }> = () => null; - -export const World = Hello; - -export default Hello; \ No newline at end of file diff --git a/packages/preset-dumi/src/transformer/fixtures/remark-api/alias.md b/packages/preset-dumi/src/transformer/fixtures/remark-api/alias.md deleted file mode 100644 index ec054ba469..0000000000 --- a/packages/preset-dumi/src/transformer/fixtures/remark-api/alias.md +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/preset-dumi/src/transformer/fixtures/remark-api/api-slugs.md b/packages/preset-dumi/src/transformer/fixtures/remark-api/api-slugs.md deleted file mode 100644 index 427fd45a9e..0000000000 --- a/packages/preset-dumi/src/transformer/fixtures/remark-api/api-slugs.md +++ /dev/null @@ -1,11 +0,0 @@ -## First - -first - - - -## Second - -test custom exports - - diff --git a/packages/preset-dumi/src/transformer/fixtures/remark-api/auto-detect/index.md b/packages/preset-dumi/src/transformer/fixtures/remark-api/auto-detect/index.md deleted file mode 100644 index d032aef13c..0000000000 --- a/packages/preset-dumi/src/transformer/fixtures/remark-api/auto-detect/index.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/preset-dumi/src/transformer/fixtures/remark-api/auto-detect/index.tsx b/packages/preset-dumi/src/transformer/fixtures/remark-api/auto-detect/index.tsx deleted file mode 100644 index 2875d56459..0000000000 --- a/packages/preset-dumi/src/transformer/fixtures/remark-api/auto-detect/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import type React from 'react'; - -const Hello: React.FC<{ className?: string }> = () => null; - -export default Hello; diff --git a/packages/preset-dumi/src/transformer/fixtures/remark-api/custom-exports.md b/packages/preset-dumi/src/transformer/fixtures/remark-api/custom-exports.md deleted file mode 100644 index 3524a048db..0000000000 --- a/packages/preset-dumi/src/transformer/fixtures/remark-api/custom-exports.md +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/preset-dumi/src/transformer/fixtures/remark-api/custom-src.md b/packages/preset-dumi/src/transformer/fixtures/remark-api/custom-src.md deleted file mode 100644 index 1b584a982e..0000000000 --- a/packages/preset-dumi/src/transformer/fixtures/remark-api/custom-src.md +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/preset-dumi/src/transformer/fixtures/remark-api/guess-name.md b/packages/preset-dumi/src/transformer/fixtures/remark-api/guess-name.md deleted file mode 100644 index 88ec692aed..0000000000 --- a/packages/preset-dumi/src/transformer/fixtures/remark-api/guess-name.md +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/packages/preset-dumi/src/transformer/fixtures/remark-api/hide-title.md b/packages/preset-dumi/src/transformer/fixtures/remark-api/hide-title.md deleted file mode 100644 index cd2657e910..0000000000 --- a/packages/preset-dumi/src/transformer/fixtures/remark-api/hide-title.md +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/preset-dumi/src/transformer/fixtures/remark-api/packages/pkgA/README.md b/packages/preset-dumi/src/transformer/fixtures/remark-api/packages/pkgA/README.md deleted file mode 100644 index f7b27bc2fd..0000000000 --- a/packages/preset-dumi/src/transformer/fixtures/remark-api/packages/pkgA/README.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/preset-dumi/src/transformer/fixtures/remark-api/packages/pkgA/src/index.tsx b/packages/preset-dumi/src/transformer/fixtures/remark-api/packages/pkgA/src/index.tsx deleted file mode 100644 index c622558419..0000000000 --- a/packages/preset-dumi/src/transformer/fixtures/remark-api/packages/pkgA/src/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import type React from 'react'; - -const Hello: React.FC<{ className?: string }> = () => null; - -export const World = Hello; - -export default Hello; diff --git a/packages/preset-dumi/src/transformer/remark/api.ts b/packages/preset-dumi/src/transformer/remark/api.ts deleted file mode 100644 index 72fc2531c1..0000000000 --- a/packages/preset-dumi/src/transformer/remark/api.ts +++ /dev/null @@ -1,207 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import type { Node } from 'unist'; -import deepmerge from 'deepmerge'; -import is from 'hast-util-is-element'; -import has from 'hast-util-has-property'; -import visit from 'unist-util-visit'; -import { parseElmAttrToProps } from './utils'; -import parser from '../../api-parser'; -import { getModuleResolvePath } from '../../utils/moduleResolver'; -import { listenFileOnceChange } from '../../utils/watcher'; -import ctx from '../../context'; -import type { ArgsType } from '@umijs/utils'; -import type { IDumiUnifiedTransformer, IDumiElmNode } from '.'; - -function applyApiData(identifier: string, definitions: ReturnType) { - if (identifier && definitions) { - ctx.umi?.applyPlugins({ - key: 'dumi.detectApi', - type: ctx.umi.ApplyPluginsType.event, - args: { - identifier, - data: definitions, - }, - }); - } -} - -/** - * serialize api node to [title, node, title, node, ...] - * @param node original api node - * @param identifier api parse identifier, mapping in .umi/dumi/apis.json - * @param definitions api definitions - */ -function serializeAPINodes( - node: IDumiElmNode, - identifier: string, - definitions: ReturnType, -) { - const parsedAttrs = parseElmAttrToProps(node.properties); - const expts: string[] = parsedAttrs.exports || Object.keys(definitions); - const showTitle = !parsedAttrs.hideTitle - - return expts.reduce<(IDumiElmNode | Node)[]>((list, expt, i) => { - // render large API title if it is default export - // or it is the first export and the exports attribute was not custom - const isInsertAPITitle = expt === 'default' || (!i && !parsedAttrs.exports); - // render sub title for non-default export - const isInsertSubTitle = expt !== 'default'; - const apiNode = deepmerge({}, node); - - // insert API title - if (showTitle && isInsertAPITitle) { - list.push( - { - type: 'element', - tagName: 'h2', - properties: {}, - children: [{ type: 'text', value: 'API' }], - }, - { - type: 'text', - value: '\n', - }, - ); - } - - // insert export sub title - if (showTitle && isInsertSubTitle) { - list.push( - { - type: 'element', - tagName: 'h3', - properties: { id: `api-${expt.toLowerCase()}` }, - children: [{ type: 'text', value: expt }], - }, - { - type: 'text', - value: '\n', - }, - ); - } - - // insert API Node - delete apiNode.properties.exports; - apiNode.properties.identifier = identifier; - apiNode.properties.export = expt; - apiNode._dumi_parsed = true; - list.push(apiNode); - - return list; - }, []); -} - -/** - * detect component name via file path - */ -function guessComponentName(fileAbsPath: string) { - const parsed = path.parse(fileAbsPath); - - if (['index', 'index.d'].includes(parsed.name)) { - // button/index.tsx => button - // packages/button/src/index.tsx => button - // packages/button/lib/index.d.ts => button - // windows: button\\src\\index.tsx => button - // windows: button\\lib\\index.d.ts => button - return path.basename(parsed.dir.replace(/(\/|\\)(src|lib)$/, '')); - } - - // components/button.tsx => button - return parsed.name; -} - -/** - * watch component change to update api data - * @param absPath component absolute path - * @param identifier api identifier - * @param parseOpts extra parse options - */ -function watchComponentUpdate( - absPath: string, - identifier: string, - parseOpts: ArgsType[1], -) { - listenFileOnceChange(absPath, () => { - let definitions: ReturnType; - - try { - definitions = parser(absPath, parseOpts); - } catch (err) { - /* noting */ - } - - // update api data - applyApiData(identifier, definitions); - - // watch next turn - // FIXME: workaround for resolve no such file error - /* istanbul ignore next */ - setTimeout(() => { - watchComponentUpdate(absPath, identifier, parseOpts); - }, fs.existsSync(absPath) ? 0 : 50); - }); -} - -/** - * remark plugin for parse embed tag to external module - */ -export default function api(): IDumiUnifiedTransformer { - return (ast, vFile) => { - visit(ast, 'element', (node, i, parent) => { - if (is(node, 'API') && !node._dumi_parsed) { - let identifier: string; - let definitions: ReturnType; - const parseOpts = parseElmAttrToProps(node.properties); - - if (has(node, 'src')) { - const src = node.properties.src || ''; - let absPath = path.join(path.dirname(this.data('fileAbsPath')), src); - try { - absPath = getModuleResolvePath({ - basePath: process.cwd(), - sourcePath: src, - silent: true, - }); - } catch (err) { - // nothing - } - // guess component name if there has no identifier property - const componentName = node.properties.identifier || guessComponentName(absPath); - - parseOpts.componentName = componentName; - definitions = parser(absPath, parseOpts); - identifier = componentName || src; - - // trigger listener to update previewer props after this file changed - watchComponentUpdate(absPath, identifier, parseOpts); - } else if (vFile.data.componentName) { - try { - const sourcePath = getModuleResolvePath({ - basePath: process.cwd(), - sourcePath: path.dirname(this.data('fileAbsPath')), - silent: true, - }); - - parseOpts.componentName = vFile.data.componentName; - definitions = parser(sourcePath, parseOpts); - identifier = vFile.data.componentName; - - // trigger listener to update previewer props after this file changed - watchComponentUpdate(sourcePath, identifier, parseOpts); - } catch (err) { - /* noting */ - } - } - - if (identifier && definitions) { - // replace original node - parent.children.splice(i, 1, ...serializeAPINodes(node, identifier, definitions)); - - // apply api data - applyApiData(identifier, definitions); - } - } - }); - }; -} diff --git a/packages/preset-dumi/src/transformer/remark/index.ts b/packages/preset-dumi/src/transformer/remark/index.ts index 4aedc364fb..93fd905347 100644 --- a/packages/preset-dumi/src/transformer/remark/index.ts +++ b/packages/preset-dumi/src/transformer/remark/index.ts @@ -16,7 +16,6 @@ import meta from './meta'; import codeBlock from './codeBlock'; import code from './code'; import embed from './embed'; -import api from './api'; import mdComponent from './mdComponent'; import link from './link'; import img from './img'; @@ -138,8 +137,6 @@ export default (source: string, fileAbsPath: string, type: 'jsx' | 'html', maste .use(debug('comments')) .use(code) .use(debug('code')) - .use(api) - .use(debug('api')) .use(mdComponent) .use(slug) .use(debug('slug')) diff --git a/packages/preset-dumi/src/transformer/test/remark-api.test.ts b/packages/preset-dumi/src/transformer/test/remark-api.test.ts deleted file mode 100644 index 63339cdc1d..0000000000 --- a/packages/preset-dumi/src/transformer/test/remark-api.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { Service } from '@umijs/core'; -import ctx, { init } from '../../context'; -import transformer from '..'; -import type { IDumiOpts } from '../..'; - -describe('component api example', () => { - const fixtures = path.join(__dirname, '../fixtures/remark-api'); - - beforeAll(() => { - const service = new Service({ - cwd: path.dirname(fixtures), - }); - init(service as any, {} as IDumiOpts); - ctx.umi.config = { - alias: { - '@': path.resolve(__dirname, '../fixtures/remark-api'), - }, - }; - }); - - it('transform api for component md', () => { - const filePath = path.join(fixtures, 'Hello', 'index.md'); - const result = transformer.markdown(fs.readFileSync(filePath, 'utf8').toString(), filePath) - .content; - - // compare transform content - expect(result).toEqual( - `

API

-

World

-
`, - ); - }); - - it('transform api when specific src path', () => { - const filePath = path.join(fixtures, 'custom-src.md'); - const result = transformer.markdown(fs.readFileSync(filePath, 'utf8').toString(), filePath) - .content; - - // compare transform content - expect(result).toEqual( - `

API

-

World

-
`, - ); - }); - - it('transform api when alias src path', () => { - const filePath = path.join(fixtures, 'alias.md'); - const result = transformer.markdown(fs.readFileSync(filePath, 'utf8').toString(), filePath) - .content; - - // compare transform content - expect(result).toEqual( - `

API

-

World

-
`, - ); - }); - - it('transform api and show specific exports', () => { - const filePath = path.join(fixtures, 'custom-exports.md'); - const result = transformer.markdown(fs.readFileSync(filePath, 'utf8').toString(), filePath) - .content; - - // compare transform content - expect(result).toEqual( - `

API

-
`, - ); - }); - - it('transform api and render correct slugs', () => { - const filePath = path.join(fixtures, 'api-slugs.md'); - const result = transformer.markdown(fs.readFileSync(filePath, 'utf8').toString(), filePath); - - // compare transform meta - expect(result.meta.slugs).toEqual([ - { depth: 2, value: 'First', heading: 'first' }, - { depth: 2, value: 'API', heading: 'api' }, - { depth: 3, value: 'World', heading: 'api-world' }, - { depth: 2, value: 'Second', heading: 'second' }, - { depth: 3, value: 'World', heading: 'api-world' }, - ]); - }); - - it('transform api when use hideTitle', () => { - const filePath = path.join(fixtures, 'hide-title.md'); - const result = transformer.markdown(fs.readFileSync(filePath, 'utf8').toString(), filePath); - - expect(result.content).not.toContain('

'); - expect(result.content).not.toContain('

'); - }); - - it('should guess filename as component name', () => { - const filePath = path.join(fixtures, 'guess-name.md'); - const result = transformer.markdown(fs.readFileSync(filePath, 'utf8').toString(), filePath); - - expect(result.content).toContain('identifier="World"'); - expect(result.content).toContain('identifier="Hello"'); - }); - - it('should guess monorepo package name as component name', () => { - const filePath = path.join(fixtures, 'packages', 'pkgA', 'README.md'); - const result = transformer.markdown(fs.readFileSync(filePath, 'utf8').toString(), filePath); - - expect(result.content).toContain('identifier="pkgA"'); - }); - - it('should guess folder name as component name', () => { - const filePath = path.join(fixtures, 'auto-detect', 'index.md'); - const result = transformer.markdown(fs.readFileSync(filePath, 'utf8').toString(), filePath); - - expect(result.content).toContain('identifier="auto-detect"'); - }); - -}); diff --git a/packages/preset-dumi/src/utils/getHostPkgAlias.test.ts b/packages/preset-dumi/src/utils/getHostPkgAlias.test.ts index 27a4b414a1..65da0bfbfe 100644 --- a/packages/preset-dumi/src/utils/getHostPkgAlias.test.ts +++ b/packages/preset-dumi/src/utils/getHostPkgAlias.test.ts @@ -17,6 +17,7 @@ describe('getHostPkgAlias', () => { '@umijs/create-dumi-app', '@umijs/create-dumi-lib', 'dumi', + "@umijs/plugin-dumi-api", '@umijs/preset-dumi', 'dumi-theme-default', 'dumi-theme-mobile', diff --git a/packages/theme-default/src/builtins/API.tsx b/packages/theme-default/src/builtins/API.tsx deleted file mode 100644 index ce815d3965..0000000000 --- a/packages/theme-default/src/builtins/API.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useContext } from 'react'; -import type { IApiComponentProps} from 'dumi/theme'; -import { context, useApiData, AnchorLink } from 'dumi/theme'; - -const LOCALE_TEXTS = { - 'zh-CN': { - name: '属性名', - description: '描述', - type: '类型', - default: '默认值', - required: '(必选)', - }, - 'en-US': { - name: 'Name', - description: 'Description', - type: 'Type', - default: 'Default', - required: '(required)', - }, -}; - -export default ({ identifier, export: expt }: IApiComponentProps) => { - const data = useApiData(identifier); - const { locale } = useContext(context); - const texts = /^zh|cn$/i.test(locale) ? LOCALE_TEXTS['zh-CN'] : LOCALE_TEXTS['en-US']; - - return ( - <> - {data && ( - - - - - - - - - - - {data[expt].map(row => ( - - - - - - - ))} - -
{texts.name}{texts.description}{texts.type}{texts.default}
{row.identifier}{row.description || '--'} - {row.type} - - {row.default || (row.required && texts.required) || '--'} -
- )} - - ); -}; diff --git a/packages/theme-default/src/test/index.test.tsx b/packages/theme-default/src/test/index.test.tsx index 790b955ea9..f8977e9421 100644 --- a/packages/theme-default/src/test/index.test.tsx +++ b/packages/theme-default/src/test/index.test.tsx @@ -9,7 +9,6 @@ import Alert from '../builtins/Alert'; import Badge from '../builtins/Badge'; import Tree from '../builtins/Tree'; import Previewer from '../builtins/Previewer'; -import API from '../builtins/API'; import Layout from '../layout'; let history: MemoryHistory; @@ -296,7 +295,6 @@ describe('default theme', () => { > <>demo-3 Content - , @@ -370,8 +368,5 @@ describe('default theme', () => { expect((container.querySelector('[data-iframe] iframe') as HTMLElement).style.height).toEqual( '100px', ); - - // expect render API property - expect(getByText('other', { selector: 'table td' })).not.toBeNull(); }); }); From 2b665414027141b9f709d4466674450a45ae3760 Mon Sep 17 00:00:00 2001 From: mortalYoung Date: Mon, 22 Nov 2021 20:14:53 +0800 Subject: [PATCH 3/5] test: update tests --- .../fixtures/alias/component/Peach.tsx | 18 +++++++++++++ .../__tests__/fixtures/alias/docs/index.md | 6 ++++- .../plugin-dumi-api/__tests__/index.test.ts | 25 +++++++++++++++++++ packages/plugin-dumi-api/src/index.ts | 6 ++--- packages/plugin-dumi-api/tsconfig.json | 1 - 5 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 packages/plugin-dumi-api/__tests__/fixtures/alias/component/Peach.tsx diff --git a/packages/plugin-dumi-api/__tests__/fixtures/alias/component/Peach.tsx b/packages/plugin-dumi-api/__tests__/fixtures/alias/component/Peach.tsx new file mode 100644 index 0000000000..99508e8c7e --- /dev/null +++ b/packages/plugin-dumi-api/__tests__/fixtures/alias/component/Peach.tsx @@ -0,0 +1,18 @@ +interface IPeachProps { + className: string; + age: number; + } + + interface IAppleProps { + className?: string; + type: "Peach" | "apple"; + } + + export const Apple = ({ className, type }: IAppleProps) => { + return
This is Apple
; + }; + + export default ({ className, age }: IPeachProps) => { + return
Hello World, ${age}
; + }; + \ No newline at end of file diff --git a/packages/plugin-dumi-api/__tests__/fixtures/alias/docs/index.md b/packages/plugin-dumi-api/__tests__/fixtures/alias/docs/index.md index ec054ba469..ff2ea1f47c 100644 --- a/packages/plugin-dumi-api/__tests__/fixtures/alias/docs/index.md +++ b/packages/plugin-dumi-api/__tests__/fixtures/alias/docs/index.md @@ -1 +1,5 @@ - \ No newline at end of file + + +--------- + + diff --git a/packages/plugin-dumi-api/__tests__/index.test.ts b/packages/plugin-dumi-api/__tests__/index.test.ts index b62a873e69..2848a9a13d 100644 --- a/packages/plugin-dumi-api/__tests__/index.test.ts +++ b/packages/plugin-dumi-api/__tests__/index.test.ts @@ -125,6 +125,31 @@ describe('@umijs/plugin-dumi-api', () => { }, ], }, + Peach: { + default: [ + { + identifier: 'className', + type: 'string', + required: true, + }, + { + identifier: 'age', + type: 'number', + required: true, + }, + ], + Apple: [ + { + identifier: 'className', + type: 'string', + }, + { + identifier: 'type', + type: '"Peach" | "apple"', + required: true, + }, + ], + }, }); }); }); diff --git a/packages/plugin-dumi-api/src/index.ts b/packages/plugin-dumi-api/src/index.ts index 653c9237c8..d6f45e8d5b 100644 --- a/packages/plugin-dumi-api/src/index.ts +++ b/packages/plugin-dumi-api/src/index.ts @@ -30,7 +30,7 @@ function guessComponentName(fileAbsPath: string) { return parsed.name; } -function applyApiData(api, identifier: string, definitions: ReturnType) { +function applyApiData(api: IApi, identifier: string, definitions: ReturnType) { if (identifier && definitions) { api.applyPlugins({ key: 'dumi.detectApi', @@ -50,7 +50,7 @@ function applyApiData(api, identifier: string, definitions: ReturnType[1], @@ -162,7 +162,7 @@ export default function (api: IApi) { const isUpdated = Boolean(apis[identifier]); apis[identifier] = data; - + if (isUpdated) { generateApisFile(); } diff --git a/packages/plugin-dumi-api/tsconfig.json b/packages/plugin-dumi-api/tsconfig.json index 46ef4b7e45..568884cc8c 100644 --- a/packages/plugin-dumi-api/tsconfig.json +++ b/packages/plugin-dumi-api/tsconfig.json @@ -8,5 +8,4 @@ "skipLibCheck": true, "declaration": true }, - "files": ["./src/theme/index.ts"] } From f42e595be06474fe0a346e97f77a7c176a4f2c9d Mon Sep 17 00:00:00 2001 From: mortalYoung Date: Tue, 23 Nov 2021 20:55:29 +0800 Subject: [PATCH 4/5] test: ignore watch function coverage --- packages/plugin-dumi-api/src/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/plugin-dumi-api/src/index.ts b/packages/plugin-dumi-api/src/index.ts index d6f45e8d5b..4ce576be98 100644 --- a/packages/plugin-dumi-api/src/index.ts +++ b/packages/plugin-dumi-api/src/index.ts @@ -49,6 +49,7 @@ function applyApiData(api: IApi, identifier: string, definitions: ReturnType Date: Fri, 3 Dec 2021 18:07:56 +0800 Subject: [PATCH 5/5] test: update API plugin tests --- .../plugin-dumi-api/__tests__/API.test.tsx | 48 ++++++++++++------- packages/plugin-dumi-api/src/useApiData.ts | 2 +- packages/preset-dumi/src/theme/context.ts | 4 +- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/packages/plugin-dumi-api/__tests__/API.test.tsx b/packages/plugin-dumi-api/__tests__/API.test.tsx index 8d4684424d..345e7581f4 100644 --- a/packages/plugin-dumi-api/__tests__/API.test.tsx +++ b/packages/plugin-dumi-api/__tests__/API.test.tsx @@ -2,28 +2,44 @@ import React from 'react'; import { render } from '@testing-library/react'; import API from '../src/API'; -jest.mock('@@/dumi/apis', () => { +jest.mock('dumi/theme', function () { return { - Hello: { - default: [ - { - identifier: 'className', - description: 'Extra CSS className for this component', - 'description.zh-CN': '组件额外的 CSS className', - type: 'string', + context: { + locale: '', + config: { + locales: [], + }, + apis: { + Hello: { + default: [ + { + identifier: 'className', + description: 'Extra CSS className for this component', + 'description.zh-CN': '组件额外的 CSS className', + type: 'string', + }, + { + identifier: 'type', + description: "I'm required", + 'description.zh-CN': '我是一个必选属性', + type: 'string', + required: true, + }, + ], }, - { - identifier: 'type', - description: "I'm required", - 'description.zh-CN': '我是一个必选属性', - type: 'string', - required: true, - }, - ], + }, }, }; }); +jest.mock('react', function () { + const originalModule = jest.requireActual('react'); + return { + ...originalModule, + useContext: jest.fn(context => context), + }; +}); + describe('API component', () => { test('Match snapshot', () => { const { asFragment } = render(); diff --git a/packages/plugin-dumi-api/src/useApiData.ts b/packages/plugin-dumi-api/src/useApiData.ts index 205c990a59..a81a2a3c26 100644 --- a/packages/plugin-dumi-api/src/useApiData.ts +++ b/packages/plugin-dumi-api/src/useApiData.ts @@ -1,5 +1,5 @@ import { useState, useEffect, useContext } from 'react'; -import context from '@umijs/preset-dumi/lib/theme/context'; +import { context } from 'dumi/theme'; import type { AtomPropsDefinition } from 'dumi-assets-types'; import type { IApiDefinition } from './parser'; /** diff --git a/packages/preset-dumi/src/theme/context.ts b/packages/preset-dumi/src/theme/context.ts index 147cfe427c..f8283980bf 100644 --- a/packages/preset-dumi/src/theme/context.ts +++ b/packages/preset-dumi/src/theme/context.ts @@ -3,9 +3,11 @@ import type { IConfig, IRoute } from '@umijs/types'; import type { INav } from '../routes/getNavFromRoutes'; import type { IMenu } from '../routes/getMenuFromRoutes'; import type { ILocale } from '../routes/getLocaleFromRoutes'; -import type { IApiDefinition } from '@umijs/plugin-dumi-api/lib/parser'; import type { IDumiOpts } from '..'; import type { IPreviewerComponentProps } from '.'; +import type { AtomPropsDefinition } from 'dumi-assets-types'; + +export type IApiDefinition = AtomPropsDefinition; export interface IThemeContext { /**