@cfp-studio/plugin-react 17.3.0 → 17.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/index.d.mts +44 -27
- package/dist/index.mjs +1 -1
- package/package.json +5 -5
- package/preview/error-boundary.tsx +31 -37
- package/preview/index.tsx +68 -75
- package/preview/render-16.tsx +2 -1
- package/preview/render-18.tsx +5 -4
- package/preview/tsconfig.json +7 -7
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
React 框架的 CFP Studio 插件,支持 React 组件/Story 静态分析、属性/类型提取、预览渲染、CSF/Storybook 兼容等。
|
|
4
4
|
|
|
5
5
|
## 特性
|
|
6
|
+
|
|
6
7
|
- 自动检测并适配 React 16/17/18+ 项目
|
|
7
8
|
- 支持 React 组件/Story 的批量静态分析与属性/类型提取
|
|
8
9
|
- 兼容 CSF/Storybook 规范,支持 args/参数提取
|
|
@@ -10,6 +11,7 @@ React 框架的 CFP Studio 插件,支持 React 组件/Story 静态分析、属
|
|
|
10
11
|
- 内置丰富的 E2E/单元测试用例,覆盖多种工程模板
|
|
11
12
|
|
|
12
13
|
## 架构与工作原理
|
|
14
|
+
|
|
13
15
|
- 通过 `FrameworkPluginFactory` 实现插件注册与兼容性检测
|
|
14
16
|
- 使用 `@cfp-studio/analyzer-react` 进行 TS/JSX 类型分析
|
|
15
17
|
- `react-js-imports-plugin` 自动为 .js 文件注入 React 导入,兼容 React 17+ 新写法
|
|
@@ -17,6 +19,7 @@ React 框架的 CFP Studio 插件,支持 React 组件/Story 静态分析、属
|
|
|
17
19
|
- 支持多种 React 工程模板(apps/ 下有多种测试工程)
|
|
18
20
|
|
|
19
21
|
## 主要 API/模块
|
|
22
|
+
|
|
20
23
|
- `src/index.ts`:插件主入口,注册工厂与分析器
|
|
21
24
|
- `src/react-js-imports-plugin.ts`:自动注入 React 导入
|
|
22
25
|
- `preview/index.tsx`:前端渲染入口
|
|
@@ -24,19 +27,23 @@ React 框架的 CFP Studio 插件,支持 React 组件/Story 静态分析、属
|
|
|
24
27
|
- `tests/`、`apps/`:丰富的测试与模板
|
|
25
28
|
|
|
26
29
|
## 用法示例
|
|
30
|
+
|
|
27
31
|
```ts
|
|
28
|
-
import { reactFrameworkPlugin } from
|
|
32
|
+
import { reactFrameworkPlugin } from "@cfp-studio/plugin-react";
|
|
29
33
|
// 传入 core/Previewer 或 createChromelessWorkspace 等集成
|
|
30
34
|
```
|
|
31
35
|
|
|
32
36
|
## 依赖
|
|
37
|
+
|
|
33
38
|
- `react`、`@cfp-studio/*`、`typescript`、`vite`、`esbuild-plugin-polyfill-node`
|
|
34
39
|
- 测试:`vitest`、`@playwright/test`
|
|
35
40
|
|
|
36
41
|
## 构建
|
|
42
|
+
|
|
37
43
|
```sh
|
|
38
44
|
pnpm build
|
|
39
45
|
```
|
|
40
46
|
|
|
41
47
|
## 许可证
|
|
48
|
+
|
|
42
49
|
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { Reader } from "@cfp-studio/vfs";
|
|
3
|
-
import * as fs$1 from "node:fs";
|
|
4
2
|
import * as url$1 from "node:url";
|
|
5
3
|
import { URL as URL$1 } from "node:url";
|
|
4
|
+
import * as fs$1 from "node:fs";
|
|
5
|
+
import { Reader } from "@cfp-studio/vfs";
|
|
6
6
|
import { Analyzer } from "@cfp-studio/analyzer-api";
|
|
7
7
|
import * as http from "node:http";
|
|
8
8
|
import { Agent, ClientRequest, ClientRequestArgs, IncomingMessage, OutgoingHttpHeaders, Server } from "node:http";
|
|
@@ -15,6 +15,8 @@ import * as stream from "node:stream";
|
|
|
15
15
|
import { Duplex, DuplexOptions } from "node:stream";
|
|
16
16
|
import { SecureContextOptions } from "node:tls";
|
|
17
17
|
import { ZlibOptions } from "node:zlib";
|
|
18
|
+
import Lightningcss from "lightningcss";
|
|
19
|
+
import DartSass from "sass";
|
|
18
20
|
import SassEmbedded from "sass-embedded";
|
|
19
21
|
import Less from "less";
|
|
20
22
|
import Stylus from "stylus";
|
|
@@ -34,7 +36,7 @@ type FrameworkPluginInfo = {
|
|
|
34
36
|
* @returns 实现了 Api 接口的 AxiosApi 实例
|
|
35
37
|
*/
|
|
36
38
|
//#endregion
|
|
37
|
-
//#region ../../../../node_modules/.pnpm/@types+estree@1.0.
|
|
39
|
+
//#region ../../../../node_modules/.pnpm/@types+estree@1.0.9/node_modules/@types/estree/index.d.ts
|
|
38
40
|
// This definition file follows a somewhat unusual format. ESTree allows
|
|
39
41
|
// runtime type checks based on the `type` parameter. In order to explain this
|
|
40
42
|
// to typescript we want to use discriminated union types:
|
|
@@ -284,7 +286,7 @@ interface PrivateIdentifier extends BaseNode {
|
|
|
284
286
|
}
|
|
285
287
|
interface Property extends BaseNode {
|
|
286
288
|
type: "Property";
|
|
287
|
-
key: Expression
|
|
289
|
+
key: Expression;
|
|
288
290
|
value: Expression | Pattern; // Could be an AssignmentProperty
|
|
289
291
|
kind: "init" | "get" | "set";
|
|
290
292
|
method: boolean;
|
|
@@ -558,7 +560,7 @@ interface AwaitExpression extends BaseExpression {
|
|
|
558
560
|
argument: Expression;
|
|
559
561
|
}
|
|
560
562
|
//#endregion
|
|
561
|
-
//#region ../../../../node_modules/.pnpm/rollup@4.
|
|
563
|
+
//#region ../../../../node_modules/.pnpm/rollup@4.65.0/node_modules/rollup/dist/rollup.d.ts
|
|
562
564
|
declare module 'estree' {
|
|
563
565
|
export interface Decorator extends BaseNode {
|
|
564
566
|
type: 'Decorator';
|
|
@@ -638,11 +640,11 @@ type DecodedSourceMapOrMissing = {
|
|
|
638
640
|
missing?: false | undefined;
|
|
639
641
|
});
|
|
640
642
|
interface SourceMap$1 {
|
|
641
|
-
file
|
|
643
|
+
file?: string | undefined;
|
|
642
644
|
mappings: string;
|
|
643
645
|
names: string[];
|
|
644
646
|
sources: string[];
|
|
645
|
-
sourcesContent?: string[] | undefined;
|
|
647
|
+
sourcesContent?: (string | null)[] | undefined;
|
|
646
648
|
version: number;
|
|
647
649
|
debugId?: string | undefined;
|
|
648
650
|
toString(): string;
|
|
@@ -664,7 +666,8 @@ interface SourceDescription extends Partial<PartialNull<ModuleOptions>> {
|
|
|
664
666
|
}
|
|
665
667
|
interface TransformModuleJSON {
|
|
666
668
|
ast?: ProgramNode | undefined;
|
|
667
|
-
code: string;
|
|
669
|
+
code: string;
|
|
670
|
+
safeVariableNames: Record<string, string> | null; // note if plugins use new this.cache to opt-out auto transform cache
|
|
668
671
|
customTransformCache: boolean;
|
|
669
672
|
originalCode: string;
|
|
670
673
|
originalSourcemap: ExistingDecodedSourceMap | null;
|
|
@@ -672,6 +675,7 @@ interface TransformModuleJSON {
|
|
|
672
675
|
transformDependencies: string[];
|
|
673
676
|
}
|
|
674
677
|
interface ModuleJSON extends TransformModuleJSON, ModuleOptions {
|
|
678
|
+
safeVariableNames: Record<string, string> | null;
|
|
675
679
|
ast: ProgramNode;
|
|
676
680
|
dependencies: string[];
|
|
677
681
|
id: string;
|
|
@@ -727,6 +731,7 @@ interface ModuleInfo extends ModuleOptions {
|
|
|
727
731
|
dynamicallyImportedIds: readonly string[];
|
|
728
732
|
exportedBindings: Record<string, string[]> | null;
|
|
729
733
|
exports: string[] | null;
|
|
734
|
+
safeVariableNames: Record<string, string> | null;
|
|
730
735
|
hasDefaultExport: boolean | null;
|
|
731
736
|
id: string;
|
|
732
737
|
implicitlyLoadedAfterOneOf: readonly string[];
|
|
@@ -774,6 +779,7 @@ interface PluginContext extends MinimalPluginContext$1 {
|
|
|
774
779
|
} & Partial<PartialNull<ModuleOptions>>) => Promise<ModuleInfo>;
|
|
775
780
|
parse: ParseAst;
|
|
776
781
|
resolve: (source: string, importer?: string, options?: {
|
|
782
|
+
importerAttributes?: Record<string, string>;
|
|
777
783
|
attributes?: Record<string, string>;
|
|
778
784
|
custom?: CustomPluginOptions;
|
|
779
785
|
isEntry?: boolean;
|
|
@@ -810,10 +816,12 @@ type ResolveIdResult = string | NullValue | false | PartialResolvedId;
|
|
|
810
816
|
type ResolveIdHook = (this: PluginContext, source: string, importer: string | undefined, options: {
|
|
811
817
|
attributes: Record<string, string>;
|
|
812
818
|
custom?: CustomPluginOptions;
|
|
819
|
+
importerAttributes?: Record<string, string> | undefined;
|
|
813
820
|
isEntry: boolean;
|
|
814
821
|
}) => ResolveIdResult;
|
|
815
822
|
type ShouldTransformCachedModuleHook = (this: PluginContext, options: {
|
|
816
823
|
ast: ProgramNode;
|
|
824
|
+
attributes: Record<string, string>;
|
|
817
825
|
code: string;
|
|
818
826
|
id: string;
|
|
819
827
|
meta: CustomPluginOptions;
|
|
@@ -824,7 +832,12 @@ type ShouldTransformCachedModuleHook = (this: PluginContext, options: {
|
|
|
824
832
|
type IsExternal = (source: string, importer: string | undefined, isResolved: boolean) => boolean;
|
|
825
833
|
type HasModuleSideEffects = (id: string, external: boolean) => boolean;
|
|
826
834
|
type LoadResult = SourceDescription | string | NullValue;
|
|
827
|
-
type LoadHook = (this: PluginContext, id: string
|
|
835
|
+
type LoadHook = (this: PluginContext, id: string, // temporarily marked as optional for better Vite type-compatibility
|
|
836
|
+
options?: {
|
|
837
|
+
// unused, temporarily added for better Vite type-compatibility
|
|
838
|
+
ssr?: boolean | undefined; // temporarily marked as optional for better Vite type-compatibility
|
|
839
|
+
attributes?: Record<string, string>;
|
|
840
|
+
} | undefined) => LoadResult;
|
|
828
841
|
interface TransformPluginContext extends PluginContext {
|
|
829
842
|
debug: LoggingFunctionWithPosition;
|
|
830
843
|
error: (error: RollupError | string, pos?: number | {
|
|
@@ -836,7 +849,12 @@ interface TransformPluginContext extends PluginContext {
|
|
|
836
849
|
warn: LoggingFunctionWithPosition;
|
|
837
850
|
}
|
|
838
851
|
type TransformResult$2 = string | NullValue | Partial<SourceDescription>;
|
|
839
|
-
type TransformHook = (this: TransformPluginContext, code: string, id: string
|
|
852
|
+
type TransformHook = (this: TransformPluginContext, code: string, id: string, // temporarily marked as optional for better Vite type-compatibility
|
|
853
|
+
options?: {
|
|
854
|
+
// unused, temporarily added for better Vite type-compatibility
|
|
855
|
+
ssr?: boolean | undefined; // temporarily marked as optional for better Vite type-compatibility
|
|
856
|
+
attributes?: Record<string, string>;
|
|
857
|
+
} | undefined) => TransformResult$2;
|
|
840
858
|
type ModuleParsedHook = (this: PluginContext, info: ModuleInfo) => void;
|
|
841
859
|
type RenderChunkHook = (this: PluginContext, code: string, chunk: RenderedChunk, options: NormalizedOutputOptions, meta: {
|
|
842
860
|
chunks: Record<string, RenderedChunk>;
|
|
@@ -846,13 +864,16 @@ type RenderChunkHook = (this: PluginContext, code: string, chunk: RenderedChunk,
|
|
|
846
864
|
} | string | NullValue;
|
|
847
865
|
type ResolveDynamicImportHook = (this: PluginContext, specifier: string | AstNode, importer: string, options: {
|
|
848
866
|
attributes: Record<string, string>;
|
|
867
|
+
importerAttributes: Record<string, string>;
|
|
849
868
|
}) => ResolveIdResult;
|
|
850
869
|
type ResolveImportMetaHook = (this: PluginContext, property: string | null, options: {
|
|
870
|
+
attributes: Record<string, string>;
|
|
851
871
|
chunkId: string;
|
|
852
872
|
format: InternalModuleFormat;
|
|
853
873
|
moduleId: string;
|
|
854
874
|
}) => string | NullValue;
|
|
855
875
|
type ResolveFileUrlHook = (this: PluginContext, options: {
|
|
876
|
+
attributes: Record<string, string>;
|
|
856
877
|
chunkId: string;
|
|
857
878
|
fileName: string;
|
|
858
879
|
format: InternalModuleFormat;
|
|
@@ -903,6 +924,7 @@ interface FunctionPluginHooks {
|
|
|
903
924
|
chunk: PreRenderedChunkWithFileName;
|
|
904
925
|
targetChunk: PreRenderedChunkWithFileName | null;
|
|
905
926
|
getTargetChunkImports: () => DynamicImportTargetChunk[] | null;
|
|
927
|
+
targetModuleAttributes: Record<string, string>;
|
|
906
928
|
}) => {
|
|
907
929
|
left: string;
|
|
908
930
|
right: string;
|
|
@@ -1318,16 +1340,14 @@ interface AwaitingEventEmitter<T extends Record<string, (...parameters: any) =>
|
|
|
1318
1340
|
off<K extends keyof T>(event: K, listener: AwaitedEventListener<T, K>): this;
|
|
1319
1341
|
/**
|
|
1320
1342
|
* Registers an event listener that will be awaited before Rollup continues.
|
|
1321
|
-
* All listeners
|
|
1322
|
-
*
|
|
1343
|
+
* All listeners are invoked and awaited in parallel. Rollup waits until all
|
|
1344
|
+
* of them have settled and then throws the first rejection, if any.
|
|
1323
1345
|
*/
|
|
1324
1346
|
on<K extends keyof T>(event: K, listener: AwaitedEventListener<T, K>): this;
|
|
1325
1347
|
/**
|
|
1326
|
-
* Registers an event listener
|
|
1327
|
-
*
|
|
1328
|
-
*
|
|
1329
|
-
* Listeners are removed automatically when removeListenersForCurrentRun is
|
|
1330
|
-
* called, which happens automatically after each run.
|
|
1348
|
+
* Registers an event listener like `on`. Listeners are removed automatically
|
|
1349
|
+
* when removeListenersForCurrentRun is called, which happens right before each
|
|
1350
|
+
* rebuild, after the changes that triggered it were announced.
|
|
1331
1351
|
*/
|
|
1332
1352
|
onCurrentRun<K extends keyof T>(event: K, listener: (...parameters: Parameters<T[K]>) => Promise<ReturnType<T[K]>>): this;
|
|
1333
1353
|
removeAllListeners(): this;
|
|
@@ -1428,7 +1448,7 @@ interface RollupFileStats {
|
|
|
1428
1448
|
birthtime: Date;
|
|
1429
1449
|
}
|
|
1430
1450
|
//#endregion
|
|
1431
|
-
//#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.
|
|
1451
|
+
//#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.1_13c54df9746541450cb8a36e10ae9cdd/node_modules/vite/types/hmrPayload.d.ts
|
|
1432
1452
|
type HotPayload = ConnectedPayload | PingPayload | UpdatePayload | FullReloadPayload | CustomPayload | ErrorPayload | PrunePayload;
|
|
1433
1453
|
interface ConnectedPayload {
|
|
1434
1454
|
type: 'connected';
|
|
@@ -1487,7 +1507,7 @@ interface ErrorPayload {
|
|
|
1487
1507
|
};
|
|
1488
1508
|
}
|
|
1489
1509
|
//#endregion
|
|
1490
|
-
//#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.
|
|
1510
|
+
//#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.1_13c54df9746541450cb8a36e10ae9cdd/node_modules/vite/types/customEvent.d.ts
|
|
1491
1511
|
interface CustomEventMap {
|
|
1492
1512
|
'vite:beforeUpdate': UpdatePayload;
|
|
1493
1513
|
'vite:afterUpdate': UpdatePayload;
|
|
@@ -1518,7 +1538,7 @@ interface InvalidatePayload {
|
|
|
1518
1538
|
*/
|
|
1519
1539
|
type InferCustomEventPayload<T extends string> = T extends keyof CustomEventMap ? CustomEventMap[T] : any;
|
|
1520
1540
|
//#endregion
|
|
1521
|
-
//#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.
|
|
1541
|
+
//#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.1_13c54df9746541450cb8a36e10ae9cdd/node_modules/vite/dist/node/moduleRunnerTransport.d-DJ_mE5sf.d.ts
|
|
1522
1542
|
interface FetchFunctionOptions {
|
|
1523
1543
|
cached?: boolean;
|
|
1524
1544
|
startOffset?: number;
|
|
@@ -2181,12 +2201,12 @@ declare global {
|
|
|
2181
2201
|
interface URL {}
|
|
2182
2202
|
}
|
|
2183
2203
|
//#endregion
|
|
2184
|
-
//#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.
|
|
2204
|
+
//#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.1_13c54df9746541450cb8a36e10ae9cdd/node_modules/vite/types/internal/lightningcssOptions.d.ts
|
|
2185
2205
|
/* eslint-enable @typescript-eslint/ban-ts-comment */
|
|
2186
2206
|
type LightningCSSOptions = Omit<Lightningcss.BundleAsyncOptions<Lightningcss.CustomAtRules>, 'filename' | 'resolver' | 'minify' | 'sourceMap' | 'analyzeDependencies' // properties not overridden by Vite, but does not make sense to set by end users
|
|
2187
2207
|
| 'inputSourceMap' | 'projectRoot'>;
|
|
2188
2208
|
//#endregion
|
|
2189
|
-
//#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.
|
|
2209
|
+
//#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.1_13c54df9746541450cb8a36e10ae9cdd/node_modules/vite/types/internal/cssPreprocessorOptions.d.ts
|
|
2190
2210
|
/* eslint-enable @typescript-eslint/ban-ts-comment */
|
|
2191
2211
|
// https://github.com/type-challenges/type-challenges/issues/29285
|
|
2192
2212
|
type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;
|
|
@@ -2209,7 +2229,7 @@ declare global {
|
|
|
2209
2229
|
interface HTMLLinkElement {}
|
|
2210
2230
|
}
|
|
2211
2231
|
//#endregion
|
|
2212
|
-
//#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.
|
|
2232
|
+
//#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.1_13c54df9746541450cb8a36e10ae9cdd/node_modules/vite/types/metadata.d.ts
|
|
2213
2233
|
interface ChunkMetadata {
|
|
2214
2234
|
importedAssets: Set<string>;
|
|
2215
2235
|
importedCss: Set<string>;
|
|
@@ -2242,7 +2262,7 @@ declare module 'rollup' {
|
|
|
2242
2262
|
}
|
|
2243
2263
|
}
|
|
2244
2264
|
//#endregion
|
|
2245
|
-
//#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.
|
|
2265
|
+
//#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.1_13c54df9746541450cb8a36e10ae9cdd/node_modules/vite/dist/node/index.d.ts
|
|
2246
2266
|
interface Alias {
|
|
2247
2267
|
find: string | RegExp;
|
|
2248
2268
|
replacement: string;
|
|
@@ -5757,9 +5777,6 @@ interface FrameworkPlugin extends Analyzer {
|
|
|
5757
5777
|
*/
|
|
5758
5778
|
//#endregion
|
|
5759
5779
|
//#region src/index.d.ts
|
|
5760
|
-
/**
|
|
5761
|
-
* 定义一个名为 reactFrameworkPlugin 的框架插件工厂对象
|
|
5762
|
-
*/
|
|
5763
5780
|
declare const reactFrameworkPlugin: FrameworkPluginFactory;
|
|
5764
5781
|
/**
|
|
5765
5782
|
* 导出默认的 reactFrameworkPlugin 对象
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import e from"node:path";import t from"node:url";import n from"@vitejs/plugin-react";import{polyfillNode as r}from"esbuild-plugin-polyfill-node";import i from"node:fs";import{createAnalyzer as a}from"@cfp-studio/analyzer-api/analyzer";import{createFileSystemReader as o,createStackedReader as s}from"@cfp-studio/vfs";import c from"typescript";import{decodePreviewableId as l,defineSpecialTypes as u,generatePreviewableId as d}from"@cfp-studio/analyzer-api";import{parseSerializableValue as f}from"@cfp-studio/serializable-values";import{extractArgs as p,extractCsf3Stories as m,extractStoriesInfo as h,resolvePreviewableId as g}from"@cfp-studio/storybook-helpers";import{EMPTY_OBJECT_TYPE as _,NODE_TYPE as v,UNKNOWN_TYPE as y,arrayType as b,dereferenceType as x,functionType as S,helpers as C,maybeOptionalType as w,objectType as T,stripUnusedTypes as E}from"@cfp-studio/type-analyzer";function D(e,t){for(let n of e.statements)if(c.isExpressionStatement(n)&&c.isBinaryExpression(n.expression)&&n.expression.operatorToken.kind===c.SyntaxKind.EqualsToken&&c.isPropertyAccessExpression(n.expression.left)&&c.isIdentifier(n.expression.left.expression)){if(n.expression.left.expression.text!==t)continue;if(n.expression.left.name.text===`propTypes`)return n.expression.right}else if(c.isClassDeclaration(n)&&n.name){if(n.name.text!==t)continue;for(let e of n.members)if(c.isPropertyDeclaration(e)&&e.initializer&&e.name.getText()===`propTypes`)return e.initializer}return null}function O(e,t,n,r,i){let a=t.sourceFile(n),o=null;a&&(o=D(a,r));let s=null;return s=o?A(t,o):k(e,t,i),{props:s.type,types:{...s.collected}}}function k(e,t,n){let r=n.getParameters()[0];if(!r)return{type:_,collected:{}};if(!r.valueDeclaration)return{type:y,collected:{}};let i=t.checker.getTypeOfSymbolAtLocation(r,r.valueDeclaration);try{let{type:e,collected:n}=t.resolveType(i);if([e]=x(e,n,[]),e.kind===`object`&&c.isParameter(r.valueDeclaration)&&c.isObjectBindingPattern(r.valueDeclaration.name)){let t=r.valueDeclaration.name,n=new Set,i=new Set,a=!1;for(let e of t.elements){if(e.dotDotDotToken){a=!0;break}let t=e.propertyName||e.name;if(!c.isIdentifier(t)){a=!0;break}n.add(t.text),e.initializer&&i.add(t.text)}a||(e=T(Object.fromEntries(Object.entries(e.fields).filter(([e])=>n.has(e)).map(([e,t])=>[e,w(t,i.has(e))]))))}return{type:e,collected:n}}catch(n){e.warn(`Unable to resolve props type for ${t.checker.typeToString(i)}`),e.error(n.toString())}return{type:y,collected:{}}}function A(e,t){let n=e.checker.getTypeAtLocation(t),r={},i={};for(let t of n.getProperties())r[t.name]=(()=>{if(!t.valueDeclaration)return y;let n=e.checker.getTypeAtLocation(t.valueDeclaration),r=e.resolveTypeArguments(n),a=r.types[0];return a?(i={...i,...r.collected},w(a,n.symbol.name===`Requireable`)):y})();let a=T(r);return{type:a,collected:E(i,[a])}}async function j(t,n,r,i){let a=n.sourceFile(i);if(!a)return[];let o=[];for(let e of a.statements)if(c.isExportAssignment(e)){if(c.isIdentifier(e.expression))continue;o.push([`default`,e,e.expression])}else if(c.isVariableStatement(e))for(let t of e.declarationList.declarations)!c.isIdentifier(t.name)||!t.initializer||o.push([t.name.text,e,t.initializer]);else if(c.isFunctionDeclaration(e)){let t=!!e.modifiers?.find(e=>e.kind===c.SyntaxKind.ExportKeyword)&&!!e.modifiers?.find(e=>e.kind===c.SyntaxKind.DefaultKeyword),n=e.name?.text;(t||n)&&o.push([n||`default`,e,e])}else c.isClassDeclaration(e)&&e.name&&o.push([e.name.text,e,e]);let s=h(a);t.debug(`**storiresInfo: ${s}`);let u=[],g=p(a),_=C.extractExportedNames(a);async function v(e,a,o){if(o===`default`&&s)return null;let c=g[o],l=o===`default`||!!_[o],u=N(n.checker,a);if(s&&l&&(c||u?.parameters.length===0)){let i=M(t,n,r,s.component);return{...e,analyze:async()=>({args:c?{sourcePosition:{start:c.getStart(),end:c.getEnd()},value:await f(c)}:null}),associatedComponent:i}}return u?{...e,exported:l,analyze:async()=>O(t,n,i,o,u)}:null}for(let[t,n,a]of o){let o=await v({id:d({filePath:e.relative(r,i),name:t}),sourcePosition:{start:n.getStart(),end:n.getEnd()}},a,t);o&&u.push(o)}return[...u,...await m(r,n,a,async i=>{let{filePath:a}=l(i),o=(await j(t,n,r,e.join(r,a))).find(e=>e.id===i);return!o||!(`exported`in o)?{props:y,types:{}}:o.analyze()})]}function M(t,n,r,i){let a=g(r,n.checker,i);return i&&a?{id:a,analyze:async()=>{let o=N(n.checker,i);if(!o)return{props:y,types:{}};let{filePath:s,name:c}=l(a);return O(t,n,e.join(r,s),c,o)}}:null}function N(e,t){let n=e.getTypeAtLocation(t);if(n.flags===c.TypeFlags.Any&&c.isCallExpression(t)&&c.isPropertyAccessExpression(t.expression)&&c.isIdentifier(t.expression.name)&&t.expression.name.text===`bind`){let r=e.getSymbolAtLocation(t.expression.expression);r?.valueDeclaration&&c.isVariableDeclaration(r.valueDeclaration)&&r.valueDeclaration.initializer&&(n=e.getTypeAtLocation(r.valueDeclaration.initializer))}for(let e of n.getCallSignatures())if(P(e.getReturnType()))return e;if(n.symbol){let r=e.getTypeOfSymbolAtLocation(n.symbol,t);for(let e of r.getConstructSignatures())if(e.getReturnType().getProperty(`render`))return e}return null}function P(e){return!!I(e)}const F=new Set([`Element`,`ReactElement`]);function I(e){if(e.isUnion()){for(let t of e.types)if(I(t))return!0}return F.has(e.symbol?.getEscapedName().toString())}const L=u({Component:v,ComponentClass:S(v),FunctionComponent:S(v),ReactElement:v,ReactNode:v,ReactNodeArray:b(v),ReactPortal:v}),R=a({typeAnalyzerOptions:({reader:n,rootDir:r})=>{let a=t.fileURLToPath(new URL(`.`,import.meta.url)),l=e.join(a,`..`,`types`),u=e.join(a,`..`,`node_modules`),d=i.existsSync(l)?l:u;return{reader:s([n,o({mapping:{from:d,to:e.join(r,`node_modules`)},watch:!1}),...d===l?[o({mapping:{from:l,to:e.join(r,`node_modules`,`@types`)},watch:!1})]:[]]),specialTypes:L,tsCompilerOptions:{jsx:c.JsxEmit.ReactJSX,jsxImportSource:`react`}}},crawlFile:({logger:e,resolver:t,rootDir:n,absoluteFilePath:r})=>j(e,t,n,r)}),z={line:`data-inspector-line`,column:`data-inspector-column`,relativePath:`data-inspector-relative-path`};function B(e){return e.includes(z.relativePath)}function V(e,t){if(!e.includes(`<`)||B(e))return;let n=c.createSourceFile(t.relativePath,e,c.ScriptTarget.Latest,!0,c.ScriptKind.TSX),r=[],i=e=>{if(c.isJsxElement(e)||c.isJsxSelfClosingElement(e)){let i=c.isJsxElement(e)?e.openingElement:e,a=i.tagName.getStart(n),{line:o,character:s}=n.getLineAndCharacterOfPosition(a),l=i.tagName.getEnd(),u=` ${z.line}="${o+1}" ${z.column}="${s+1}" ${z.relativePath}="${H(t.relativePath)}"`;r.push({at:l,text:u})}c.forEachChild(e,i)};if(i(n),r.length===0)return;r.sort((e,t)=>t.at-e.at);let a=e;for(let{at:e,text:t}of r)a=a.slice(0,e)+t+a.slice(e);return a}function H(e){return e.replace(/&/g,`&`).replace(/"/g,`"`)}const U=/import (\* as )?React[ ,]/;function W(){return{name:`previewjs:react-js-imports`,async transform(t,n){return n.includes(`node_modules`)||e.extname(n)!==`.js`||U.test(t)?t:`import React from 'react';${t}`}}}function G(){let t=``;return{name:`previewjs:inspector-attrs`,enforce:`pre`,configResolved(e){t=e.root},transform(n,r){let i=r.split(`?`)[0]??r;if(!/\.[jt]sx$/.test(i)||i.includes(`node_modules`)||i.includes(`__previewjs__`))return;let a=V(n,{relativePath:e.relative(t,i).split(e.sep).join(`/`)});if(a!==void 0)return{code:a,map:null}}}}const K={info:{apiVersion:5,name:`@cfp-studio/plugin-react`},isCompatible:async e=>{let t=await e.react?.readInstalledVersion();if(!t)return!1;let[n,r]=t.split(`.`).map(e=>Number.parseInt(e));return Number.isNaN(n)||Number.isNaN(r)?!1:n>=17||n===16&&r>=14},async create({rootDir:i,reader:a,logger:o,dependencies:s}){let c=t.fileURLToPath(new URL(`.`,import.meta.url)),l=R({rootDir:i,reader:a,logger:o});return{defaultWrapperPath:`__previewjs__/Wrapper.tsx`,previewDirPath:e.join(c,`..`,`preview`),...l,viteConfig:e=>{let t=e.find(e=>e.name.startsWith(`vite:react-`));return{optimizeDeps:{esbuildOptions:{plugins:s.next?[r()]:[]}},resolve:{alias:{"react-native":`react-native-web`}},plugins:[G(),W(),...e,...t?[]:[n()],{name:`previewjs:update-react-import`,async transform(e,t){if(!t.endsWith(`__previewjs_internal__/renderer/index.tsx`))return;let n=Number.parseInt(await s.react?.readInstalledVersion()||`0`);return e.replace(/__PREVIEWJS_PLUGIN_REACT_IMPORT_PATH__/g,n>=18?`./render-18`:`./render-16`)}}]}}}}};export{K as default};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cfp-studio/plugin-react",
|
|
3
|
-
"version": "17.3.
|
|
3
|
+
"version": "17.3.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
"esbuild-plugin-polyfill-node": "0.3.0",
|
|
17
17
|
"typescript": "5.9.3",
|
|
18
18
|
"@cfp-studio/analyzer-api": "0.1.13",
|
|
19
|
-
"@cfp-studio/type-analyzer": "10.1.8",
|
|
20
19
|
"@cfp-studio/serializable-values": "9.0.8",
|
|
21
20
|
"@cfp-studio/storybook-helpers": "6.0.2",
|
|
22
|
-
"@cfp-studio/
|
|
21
|
+
"@cfp-studio/type-analyzer": "10.1.8",
|
|
22
|
+
"@cfp-studio/vfs": "2.1.7"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@playwright/test": "1.64.1",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"vite": "6.4.5",
|
|
35
35
|
"vitest": "5.0.5",
|
|
36
36
|
"@cfp-studio/core": "1.0.6",
|
|
37
|
-
"@cfp-studio/
|
|
38
|
-
"@cfp-studio/
|
|
37
|
+
"@cfp-studio/iframe": "18.4.1",
|
|
38
|
+
"@cfp-studio/testing": "14.0.1"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
|
41
41
|
"node": ">=20"
|
|
@@ -2,48 +2,42 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
import { Component } from "react";
|
|
3
3
|
|
|
4
4
|
type ErrorBoundaryProps = {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
renderId: number;
|
|
6
|
+
children: ReactNode;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
let errorBoundaryInstance: ErrorBoundary | null = null;
|
|
10
10
|
|
|
11
|
-
export async function expectErrorBoundary(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
20
|
-
if (shouldAbortRender()) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return errorBoundaryInstance;
|
|
11
|
+
export async function expectErrorBoundary(renderId: number, shouldAbortRender: () => boolean) {
|
|
12
|
+
while (!errorBoundaryInstance || errorBoundaryInstance.props.renderId !== renderId) {
|
|
13
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
14
|
+
if (shouldAbortRender()) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return errorBoundaryInstance;
|
|
25
19
|
}
|
|
26
20
|
|
|
27
21
|
export class ErrorBoundary extends Component<ErrorBoundaryProps> {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
22
|
+
state = {
|
|
23
|
+
error: null as Error | null,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
constructor(props: ErrorBoundaryProps) {
|
|
27
|
+
super(props);
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
29
|
+
errorBoundaryInstance = this;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static getDerivedStateFromError(error: Error) {
|
|
33
|
+
return { error };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
render() {
|
|
37
|
+
if (this.state.error) {
|
|
38
|
+
return <></>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return this.props.children;
|
|
42
|
+
}
|
|
49
43
|
}
|
package/preview/index.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { GetPropsFn, RendererLoader } from "@cfp-studio/iframe";
|
|
2
|
-
import React from "react";
|
|
3
|
-
import { ErrorBoundary, expectErrorBoundary } from "./error-boundary";
|
|
4
2
|
// @ts-ignore
|
|
5
3
|
import { render } from "__PREVIEWJS_PLUGIN_REACT_IMPORT_PATH__";
|
|
4
|
+
import React from "react";
|
|
5
|
+
|
|
6
|
+
import { ErrorBoundary, expectErrorBoundary } from "./error-boundary";
|
|
6
7
|
import { rootContainer } from "./root";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -17,78 +18,70 @@ import { rootContainer } from "./root";
|
|
|
17
18
|
* @returns 一个对象,包含渲染函数和 JSX 工厂函数。
|
|
18
19
|
*/
|
|
19
20
|
export const loadRenderer: RendererLoader = async ({
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
wrapperModule,
|
|
22
|
+
wrapperName,
|
|
23
|
+
previewableModule,
|
|
24
|
+
previewableName,
|
|
25
|
+
renderId,
|
|
26
|
+
shouldAbortRender,
|
|
26
27
|
}) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
throw errorBoundary.state.error;
|
|
87
|
-
}
|
|
88
|
-
if (Previewable.play) {
|
|
89
|
-
await Previewable.play({ canvasElement: rootContainer });
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
jsxFactory: React.createElement,
|
|
93
|
-
};
|
|
28
|
+
const isStoryModule = !!previewableModule.default?.component;
|
|
29
|
+
const Wrapper = wrapperModule?.[wrapperName || "Wrapper"] || React.Fragment;
|
|
30
|
+
const Previewable =
|
|
31
|
+
previewableModule[previewableName] ||
|
|
32
|
+
previewableModule.PreviewJsEval(`try { return ${previewableName}; } catch { return null; }`);
|
|
33
|
+
if (!Previewable) {
|
|
34
|
+
throw new Error(`No component or story named '${previewableName}'`);
|
|
35
|
+
}
|
|
36
|
+
const decorators = [...(Previewable.decorators || []), ...(previewableModule.default?.decorators || [])];
|
|
37
|
+
const RenderComponent = isStoryModule
|
|
38
|
+
? typeof Previewable === "function"
|
|
39
|
+
? Previewable
|
|
40
|
+
: Previewable.render ||
|
|
41
|
+
Previewable.component ||
|
|
42
|
+
previewableModule.default?.render ||
|
|
43
|
+
previewableModule.default?.component ||
|
|
44
|
+
Previewable
|
|
45
|
+
: Previewable;
|
|
46
|
+
const Renderer = (props: any) => {
|
|
47
|
+
return (
|
|
48
|
+
<ErrorBoundary renderId={renderId}>
|
|
49
|
+
<Wrapper>
|
|
50
|
+
{decorators.reduce(
|
|
51
|
+
(component, decorator) => () => decorator(component),
|
|
52
|
+
() => <RenderComponent {...props} />,
|
|
53
|
+
)()}
|
|
54
|
+
</Wrapper>
|
|
55
|
+
</ErrorBoundary>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
render: async (getProps: GetPropsFn) => {
|
|
60
|
+
if (shouldAbortRender()) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
await render(
|
|
64
|
+
<Renderer
|
|
65
|
+
{...getProps({
|
|
66
|
+
presetGlobalProps: previewableModule.default?.args || {},
|
|
67
|
+
presetProps: Previewable.args || {},
|
|
68
|
+
})}
|
|
69
|
+
/>,
|
|
70
|
+
);
|
|
71
|
+
if (shouldAbortRender()) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const errorBoundary = await expectErrorBoundary(renderId, shouldAbortRender);
|
|
75
|
+
if (!errorBoundary) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (errorBoundary.state.error) {
|
|
79
|
+
throw errorBoundary.state.error;
|
|
80
|
+
}
|
|
81
|
+
if (Previewable.play) {
|
|
82
|
+
await Previewable.play({ canvasElement: rootContainer });
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
jsxFactory: React.createElement,
|
|
86
|
+
};
|
|
94
87
|
};
|
package/preview/render-16.tsx
CHANGED
package/preview/render-18.tsx
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { Root } from "react-dom/client";
|
|
2
2
|
import { createRoot } from "react-dom/client";
|
|
3
|
+
|
|
3
4
|
import { rootContainer } from "./root";
|
|
4
5
|
|
|
5
6
|
let root: Root | null = null;
|
|
6
7
|
export function render(node: JSX.Element) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
if (!root) {
|
|
9
|
+
root = createRoot(rootContainer);
|
|
10
|
+
}
|
|
11
|
+
root.render(node);
|
|
11
12
|
}
|
package/preview/tsconfig.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
"extends": "../../../../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"jsx": "preserve"
|
|
7
|
+
},
|
|
8
|
+
"exclude": ["types"]
|
|
9
9
|
}
|