@fragmentsx/runtime-react 0.1.0
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 +51 -0
- package/dist/index.d.ts +290 -0
- package/dist/index.es.js +202 -0
- package/dist/index.umd.js +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @fragmentsx/runtime-react
|
|
2
|
+
|
|
3
|
+
React-рантайм для встраивания опубликованных фрагментов Fragments в
|
|
4
|
+
приложение потребителя. Строится поверх `@fragmentsx/fragments-client` —
|
|
5
|
+
берёт готовый клиент и раскладывает его в провайдеры, хуки и компоненты
|
|
6
|
+
React.
|
|
7
|
+
|
|
8
|
+
## Что это решает
|
|
9
|
+
|
|
10
|
+
Скомпилированный сервером фрагмент — это ESM-артефакт, который нужно
|
|
11
|
+
загрузить, смонтировать в дерево React, подключить его стили и дать доступу
|
|
12
|
+
к метрикам и переменным области. `runtime-react` закрывает весь этот путь:
|
|
13
|
+
|
|
14
|
+
- `FragmentsProvider` + `FragmentsClientProvider` — контекст клиента на
|
|
15
|
+
дерево приложения;
|
|
16
|
+
- `Area` / `FragmentRenderer` — резолвинг и рендер области/фрагмента;
|
|
17
|
+
- `useFragmentComponent`, `useFragmentStylesheet`, `FragmentStylesheet` —
|
|
18
|
+
подгрузка компонента и его CSS;
|
|
19
|
+
- `useFragmentMetrics`, `useArea`, `useProjectProperty` — доступ к метрикам,
|
|
20
|
+
данным области и project-переменным площадки внутри фрагмента.
|
|
21
|
+
|
|
22
|
+
## Установка
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add @fragmentsx/runtime-react @fragmentsx/fragments-client react react-dom
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Использование
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { createBrowserClient, FragmentsProvider, Area } from '@fragmentsx/runtime-react';
|
|
32
|
+
|
|
33
|
+
const client = createBrowserClient({
|
|
34
|
+
apiUrl: 'https://api.fragments.example.com',
|
|
35
|
+
projectId: 'my-project',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function App() {
|
|
39
|
+
return (
|
|
40
|
+
<FragmentsProvider client={client}>
|
|
41
|
+
<Area code="home" />
|
|
42
|
+
</FragmentsProvider>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Пара с fragments-client
|
|
48
|
+
|
|
49
|
+
`runtime-react` зависит от `@fragmentsx/fragments-client` как от транспортного
|
|
50
|
+
ядра (`workspace:*` внутри монорепозитория, `^0.1.0` из npm) — устанавливать
|
|
51
|
+
и обновлять пакеты стоит вместе.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
import { Context } from 'react';
|
|
3
|
+
import { createFragmentsClient } from '../../fragments-client/src/index.ts';
|
|
4
|
+
import { CreateFragmentsClientOptions } from '../../fragments-client/src/index.ts';
|
|
5
|
+
import { FC } from 'react';
|
|
6
|
+
import { FragmentsApiVersionError } from '../../fragments-client/src/index.ts';
|
|
7
|
+
import { FragmentsAreaError } from '../../fragments-client/src/index.ts';
|
|
8
|
+
import { FragmentsLoadError } from '../../fragments-client/src/index.ts';
|
|
9
|
+
import { GraphState } from '@graph-state/core';
|
|
10
|
+
import { JSX } from 'react/jsx-runtime';
|
|
11
|
+
import { ReactElement } from 'react';
|
|
12
|
+
import { ReactNode } from 'react';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Area — публичный компонент для рендеринга compiled fragment в consumer app.
|
|
16
|
+
*
|
|
17
|
+
* Req #4: Area + useArea оба public exports из @fragmentsx/runtime-react.
|
|
18
|
+
* Req #9: требует FragmentsProvider в tree (FragmentsClientContext + ModulesMapContext).
|
|
19
|
+
*/
|
|
20
|
+
export declare const Area: FC<AreaProps>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Props для Area компонента (D-09, сокращён Phase 73 B-13):
|
|
24
|
+
* - areaCode — строковый код области (Area.code в backend schema)
|
|
25
|
+
* - options.pause — пропустить рендер (conditional skip per legacy contract)
|
|
26
|
+
* - options.timeout — network timeout для $area.resolve fetch
|
|
27
|
+
* - options.ssr — отключить auto-resolve в SSR environment (default `true`);
|
|
28
|
+
* в browser auto-resolve запускается всегда. Параллель к render-react
|
|
29
|
+
* Area options.ssr.
|
|
30
|
+
*/
|
|
31
|
+
export declare interface AreaProps {
|
|
32
|
+
areaCode: string;
|
|
33
|
+
options?: {
|
|
34
|
+
timeout?: number;
|
|
35
|
+
pause?: boolean;
|
|
36
|
+
ssr?: boolean;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Browser-specific options — identical к CreateFragmentsClientOptions, но без `inject`.
|
|
42
|
+
* `inject` pre-populated через `injectStylesheet` из runtime-react автоматически.
|
|
43
|
+
*/
|
|
44
|
+
declare type BrowserClientOptions = Omit<CreateFragmentsClientOptions, "inject">;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Создаёт FragmentsClient с pre-populated inject = injectStylesheet (BLOCKER-4-wiring bridge).
|
|
48
|
+
*
|
|
49
|
+
* @param opts - все поля CreateFragmentsClientOptions кроме inject
|
|
50
|
+
* @returns FragmentsClient готовый к использованию в browser environments
|
|
51
|
+
*/
|
|
52
|
+
export declare function createBrowserClient(opts: BrowserClientOptions): ReturnType<typeof createFragmentsClient>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Дефолтное значение, когда Provider не установлен (потребитель вне
|
|
56
|
+
* `FragmentRenderer`, например изолированный юнит-тест компонента).
|
|
57
|
+
*/
|
|
58
|
+
export declare const defaultProjectProperties: Record<string, unknown>;
|
|
59
|
+
|
|
60
|
+
export declare interface FragmentMetrics {
|
|
61
|
+
reachGoal: (info: ReachGoalInfo) => void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Публичный компонент — тело композиции за собственными Suspense + границей
|
|
66
|
+
* ошибок. `Area` тело не использует напрямую (у неё Suspense/граница уже
|
|
67
|
+
* стоят вокруг `useArea`); preview и прочие потребители без `areaCode`
|
|
68
|
+
* используют этот компонент целиком.
|
|
69
|
+
*/
|
|
70
|
+
export declare const FragmentRenderer: FC<FragmentRendererProps>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Контракт тела композиции (D-03). `globalCssUrl` — то, что сегодня отдаёт
|
|
74
|
+
* `useArea`; у preview (без области) это будет статика DRAFT-манифеста.
|
|
75
|
+
* `props` — единственный канал значений (Phase 73, B-02/B-06): готовые
|
|
76
|
+
* значения props корневого артефакта, разрешённые сервером либо переданные
|
|
77
|
+
* потребителем напрямую (preview).
|
|
78
|
+
*/
|
|
79
|
+
export declare interface FragmentRendererProps {
|
|
80
|
+
fragmentId: number;
|
|
81
|
+
props?: Record<string, unknown>;
|
|
82
|
+
globalCssUrl?: string | null;
|
|
83
|
+
/** Словарь project-переменных площадки (манифест области или блок данных preview); ключ — имя переменной, значение — голое (D-72-02). */
|
|
84
|
+
projectProperties?: Record<string, unknown>;
|
|
85
|
+
onError?: (error: Error) => void;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { FragmentsApiVersionError }
|
|
89
|
+
|
|
90
|
+
export { FragmentsAreaError }
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Псевдоним для GraphState — возвращаемый тип `createFragmentsClient(...)` из
|
|
94
|
+
* `@fragmentsx/client-core`. Compiled Fragment.tsx не зависит от client-core
|
|
95
|
+
* напрямую: он импортирует тип отсюда и читает client через
|
|
96
|
+
* `FragmentsClientContext` (D-07).
|
|
97
|
+
*/
|
|
98
|
+
export declare type FragmentsClient = GraphState;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Single source of truth для FragmentsClient в compiled output.
|
|
102
|
+
*
|
|
103
|
+
* Значение `null` означает «client ещё не установлен» — defensive в
|
|
104
|
+
* `useFragmentMetrics` чтобы reachGoal был no-op (preview / SSG-первая загрузка
|
|
105
|
+
* до hydration).
|
|
106
|
+
*/
|
|
107
|
+
export declare const FragmentsClientContext: Context<FragmentsClient | null>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* React-компонент-обёртка для установки FragmentsClient в Context. Эмиссия
|
|
111
|
+
* compiled-output её не вызывает — это app-level wiring entry point.
|
|
112
|
+
*/
|
|
113
|
+
export declare function FragmentsClientProvider(props: {
|
|
114
|
+
value: FragmentsClient;
|
|
115
|
+
children: ReactNode;
|
|
116
|
+
}): JSX.Element;
|
|
117
|
+
|
|
118
|
+
export { FragmentsLoadError }
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Unified Provider для compiled fragments.
|
|
122
|
+
*
|
|
123
|
+
* Одновременно устанавливает:
|
|
124
|
+
* - FragmentsClientContext: client — для useArea cache lookup + $metrics.reachGoal
|
|
125
|
+
* - ModulesMapContext: {read} cache-reader — для useFragmentComponent delegation
|
|
126
|
+
*
|
|
127
|
+
* useMemo критично — Context value identity stability (без него cache invalidation
|
|
128
|
+
* на каждый render).
|
|
129
|
+
*/
|
|
130
|
+
export declare function FragmentsProvider({ client, children }: FragmentsProviderProps): ReactElement;
|
|
131
|
+
|
|
132
|
+
declare interface FragmentsProviderProps {
|
|
133
|
+
client: FragmentsClient;
|
|
134
|
+
children: ReactNode;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export declare function FragmentStylesheet({ href, fragmentKey }: FragmentStylesheetProps): JSX.Element | null;
|
|
138
|
+
|
|
139
|
+
declare interface FragmentStylesheetProps {
|
|
140
|
+
href: string;
|
|
141
|
+
fragmentKey: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Инжектирует `<link rel="stylesheet">` в `document.head` с ref-count dedup.
|
|
146
|
+
*
|
|
147
|
+
* @param href - CDN URL стилевого файла
|
|
148
|
+
* @param key - уникальный ключ для dedup (например `chunk:201` или `fragment:42:abc`)
|
|
149
|
+
* @returns cleanup callback — при refCount = 0 удаляет `<link>` из head
|
|
150
|
+
*/
|
|
151
|
+
export declare const injectStylesheet: (href: string, key: string) => (() => void);
|
|
152
|
+
|
|
153
|
+
export declare const InstanceIdContext: Context<string | null>;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* React Context для инъекции cache-reader'а в дерево компонентов.
|
|
157
|
+
* Null default = «provider не установлен» (no-provider signal).
|
|
158
|
+
*/
|
|
159
|
+
export declare const ModulesMapContext: Context<ModulesMapContextValue | null>;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Значение ModulesMapContext. Единственная fn — cache-reader (D-05).
|
|
163
|
+
*
|
|
164
|
+
* @param id - числовой идентификатор фрагмента
|
|
165
|
+
* @returns ComponentType (cache hit) | throws Promise (pending) | throws FragmentsLoadError (missing)
|
|
166
|
+
*/
|
|
167
|
+
export declare interface ModulesMapContextValue {
|
|
168
|
+
read: (id: number) => ComponentType<any>;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* React-компонент-обёртка для установки ModulesMapContext в дерево.
|
|
173
|
+
* Value identity stability — ответственность caller'а (FragmentsProvider обернёт
|
|
174
|
+
* `{read: (id) => client.$fragments.read(id)}` в useMemo, Plan 08).
|
|
175
|
+
*
|
|
176
|
+
* @param value - cache-reader объект (D-05)
|
|
177
|
+
* @param children - дочерние компоненты
|
|
178
|
+
*/
|
|
179
|
+
export declare function ModulesMapProvider({ value, children, }: ModulesMapProviderProps): ReactElement;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Props для ModulesMapProvider.
|
|
183
|
+
* Caller (FragmentsProvider) передаёт useMemo'нутый объект value.
|
|
184
|
+
* Для unit-тестов: raw {read: mockFn} без useMemo (Req #9 standalone escape hatch).
|
|
185
|
+
*/
|
|
186
|
+
declare interface ModulesMapProviderProps {
|
|
187
|
+
value: ModulesMapContextValue;
|
|
188
|
+
children: ReactNode;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export declare const ProjectPropertiesContext: Context<Record<string, unknown> | null>;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Payload для `reachGoal`. Phase 3 НЕ автоинъектит scope-поля
|
|
195
|
+
* (variantId / areaId / campaignId) — caller передаёт явно, типизация
|
|
196
|
+
* сообщает о доступных полях. Совпадает по shape с
|
|
197
|
+
* `client-core.metricsPlugin GoalInfo` (см. metrics/index.ts).
|
|
198
|
+
*/
|
|
199
|
+
export declare interface ReachGoalInfo {
|
|
200
|
+
goalId: number;
|
|
201
|
+
variantId?: number;
|
|
202
|
+
areaId?: number;
|
|
203
|
+
campaignId?: number;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export declare function useArea(areaCode: string, options?: UseAreaOptions): UseAreaResult;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Опции хука. По дизайну API параллелен render-react useArea:
|
|
210
|
+
* - `timeout` — fetch timeout (передаётся в `$area.resolve`).
|
|
211
|
+
* - `ssr` — стартовать ли auto-resolve на сервере (default `true`, чтобы
|
|
212
|
+
* Next.js getServerSideProps мог hydration'ить с прогретым кэшем).
|
|
213
|
+
* В browser environment auto-resolve всегда включён.
|
|
214
|
+
*/
|
|
215
|
+
declare interface UseAreaOptions {
|
|
216
|
+
timeout?: number;
|
|
217
|
+
ssr?: boolean;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Result shape возвращаемый useArea — зеркалирует AreaResolveResult из client-core.
|
|
222
|
+
*/
|
|
223
|
+
declare interface UseAreaResult {
|
|
224
|
+
rootFragmentId: number;
|
|
225
|
+
/**
|
|
226
|
+
* Phase 73, B-02/B-06: значения props корневого артефакта, разрешённые сервером
|
|
227
|
+
* (`resolveVariantProps`) — привязки `$ref` сюда не доезжают, только готовые значения.
|
|
228
|
+
* `$area` уже нормализует отсутствие поля у backend'а в `{}`, поэтому поле
|
|
229
|
+
* обязательное — потребителю не нужна повторная optional-проверка.
|
|
230
|
+
*/
|
|
231
|
+
props: Record<string, unknown>;
|
|
232
|
+
/**
|
|
233
|
+
* Phase 72 D-72-01/D-72-02: словарь project-переменных площадки, шейкнутый по
|
|
234
|
+
* факту использования — вход для `useProjectProperty` из `@fragmentsx/runtime-react`.
|
|
235
|
+
* `$area` уже нормализует отсутствие поля у backend'а в `{}` (план 72-05,
|
|
236
|
+
* редукция), поэтому поле обязательное — повторная optional-проверка
|
|
237
|
+
* потребителю не нужна.
|
|
238
|
+
*/
|
|
239
|
+
projectProperties: Record<string, unknown>;
|
|
240
|
+
/**
|
|
241
|
+
* URL к глобальному baseline reset CSS ([data-key^="Text"], [data-key^="Fragment"]).
|
|
242
|
+
* `null` — backend не отдал global reset; baseline применяться не будет.
|
|
243
|
+
*/
|
|
244
|
+
globalCssUrl: string | null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* useCssChunk — no-op stub для compiled fragments.
|
|
249
|
+
*
|
|
250
|
+
* @deprecated TODO(runtime-react): удалить вместе с compiler emission.
|
|
251
|
+
*
|
|
252
|
+
* Контекст: fragment-compiler (Phase 4 Plan 04-03) эмитит
|
|
253
|
+
* `import { useCssChunk } from "@fragmentsx/runtime-react"` + lifted calls
|
|
254
|
+
* `useCssChunk(<chunkId>);` в Fragment init body — отражая runtime-контракт
|
|
255
|
+
* interpreter'а (`packages/render-react/src/nodes/CssChunk/hooks/useCssChunk.ts`).
|
|
256
|
+
*
|
|
257
|
+
* В compiled production-pipeline CSS chunks инжектятся раньше — на уровне
|
|
258
|
+
* `$area.resolve → $cssChunks.populate → injectStylesheet` (см.
|
|
259
|
+
* `packages/client-core/src/compiled/$area/index.ts:186-187` и
|
|
260
|
+
* `packages/client-core/src/compiled/$cssChunks/index.ts:74-86`). К моменту mount
|
|
261
|
+
* fragment'а `<link rel="stylesheet">` уже в DOM (с ref-count dedup в injectStylesheet
|
|
262
|
+
* registry). Per-fragment hook не делает дополнительной работы.
|
|
263
|
+
*
|
|
264
|
+
* Будущая работа (удаление stub'а):
|
|
265
|
+
* 1. Compiler: убрать lifted `useCssChunk` emission из composeComponent.ts
|
|
266
|
+
* (cssChunks discovery остаётся для $cssChunks.populate manifest gen на backend).
|
|
267
|
+
* 2. Runtime: удалить этот файл и его export из index.ts.
|
|
268
|
+
* 3. Альтернатива (если потребуется per-fragment ref-count cleanup) — заменить
|
|
269
|
+
* stub на useEffect с ref-count в $cssChunks для proper unmount cleanup.
|
|
270
|
+
*
|
|
271
|
+
* Открыто как deferred item в `.planning/workstreams/fragment-compiler` —
|
|
272
|
+
* "runtime-react: убрать useCssChunk stub после compiler emission cleanup".
|
|
273
|
+
*/
|
|
274
|
+
export declare function useCssChunk(_chunkId: number): void;
|
|
275
|
+
|
|
276
|
+
export declare function useFragmentComponent(fragmentId: number): ComponentType<any>;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Возвращает обёртку над client.$metrics.reachGoal.
|
|
280
|
+
*
|
|
281
|
+
* - Без `<FragmentsClientProvider>` — reachGoal no-op (не бросает).
|
|
282
|
+
* - С provider — делегирует на `client.$metrics.reachGoal(info)`.
|
|
283
|
+
*/
|
|
284
|
+
export declare function useFragmentMetrics(): FragmentMetrics;
|
|
285
|
+
|
|
286
|
+
export declare function useFragmentStylesheet(href: string, key: string): void;
|
|
287
|
+
|
|
288
|
+
export declare function useProjectProperty(name: string): unknown;
|
|
289
|
+
|
|
290
|
+
export { }
|
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { jsx as o, jsxs as M, Fragment as j } from "react/jsx-runtime";
|
|
2
|
+
import { createContext as f, useContext as a, useMemo as g, useLayoutEffect as N, useEffect as S, version as I, Component as R, Suspense as C } from "react";
|
|
3
|
+
import { createFragmentsClient as k } from "@fragmentsx/fragments-client";
|
|
4
|
+
import { FragmentsApiVersionError as ne, FragmentsAreaError as oe, FragmentsLoadError as se } from "@fragmentsx/fragments-client";
|
|
5
|
+
const c = f(
|
|
6
|
+
null
|
|
7
|
+
);
|
|
8
|
+
c.displayName = "FragmentsClientContext";
|
|
9
|
+
function _(r) {
|
|
10
|
+
return /* @__PURE__ */ o(c.Provider, { value: r.value, children: r.children });
|
|
11
|
+
}
|
|
12
|
+
function b() {
|
|
13
|
+
return a(c);
|
|
14
|
+
}
|
|
15
|
+
const v = f(null);
|
|
16
|
+
v.displayName = "InstanceIdContext";
|
|
17
|
+
const h = f(
|
|
18
|
+
null
|
|
19
|
+
);
|
|
20
|
+
h.displayName = "ModulesMapContext";
|
|
21
|
+
function B({
|
|
22
|
+
value: r,
|
|
23
|
+
children: e
|
|
24
|
+
}) {
|
|
25
|
+
return /* @__PURE__ */ o(h.Provider, { value: r, children: e });
|
|
26
|
+
}
|
|
27
|
+
function H() {
|
|
28
|
+
const r = a(c);
|
|
29
|
+
return g(
|
|
30
|
+
() => ({
|
|
31
|
+
reachGoal: (e) => {
|
|
32
|
+
var n, t;
|
|
33
|
+
(t = (n = r == null ? void 0 : r.$metrics) == null ? void 0 : n.reachGoal) == null || t.call(n, e);
|
|
34
|
+
}
|
|
35
|
+
}),
|
|
36
|
+
[r]
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
function L(r) {
|
|
40
|
+
const e = a(h);
|
|
41
|
+
if (!e)
|
|
42
|
+
throw new Error("[useFragmentComponent] No ModulesMapProvider in tree");
|
|
43
|
+
return e.read(r);
|
|
44
|
+
}
|
|
45
|
+
const U = typeof window < "u";
|
|
46
|
+
function $(r, e) {
|
|
47
|
+
const n = a(c);
|
|
48
|
+
if (!n)
|
|
49
|
+
throw new Error("[useArea] No FragmentsClientProvider in tree");
|
|
50
|
+
const t = n.$area;
|
|
51
|
+
if (!(t != null && t.read))
|
|
52
|
+
throw new Error("[useArea] $area plugin not mounted");
|
|
53
|
+
if ((U || ((e == null ? void 0 : e.ssr) ?? !0)) && !t.has(r)) {
|
|
54
|
+
const i = (e == null ? void 0 : e.timeout) != null ? { timeout: e.timeout } : void 0;
|
|
55
|
+
t.resolve(r, i);
|
|
56
|
+
}
|
|
57
|
+
return t.read(r);
|
|
58
|
+
}
|
|
59
|
+
const E = typeof window < "u", D = () => {
|
|
60
|
+
}, d = /* @__PURE__ */ new Map(), F = (r, e) => {
|
|
61
|
+
if (!E)
|
|
62
|
+
return D;
|
|
63
|
+
const n = d.get(e);
|
|
64
|
+
if (n)
|
|
65
|
+
return n.refCount += 1, () => {
|
|
66
|
+
n.refCount -= 1, n.refCount === 0 && (n.linkEl.remove(), d.delete(e));
|
|
67
|
+
};
|
|
68
|
+
const t = document.createElement("link");
|
|
69
|
+
t.rel = "stylesheet", t.href = r, t.setAttribute("data-fragment-key", e), document.head.appendChild(t);
|
|
70
|
+
const s = { refCount: 1, linkEl: t };
|
|
71
|
+
return d.set(e, s), () => {
|
|
72
|
+
s.refCount -= 1, s.refCount === 0 && (s.linkEl.remove(), d.delete(e));
|
|
73
|
+
};
|
|
74
|
+
}, O = E ? N : S;
|
|
75
|
+
function V(r, e) {
|
|
76
|
+
O(() => F(r, e), [r, e]);
|
|
77
|
+
}
|
|
78
|
+
const G = parseInt(I.split(".")[0], 10), K = G >= 19;
|
|
79
|
+
function m({ href: r, fragmentKey: e }) {
|
|
80
|
+
return K ? /* @__PURE__ */ o(
|
|
81
|
+
"link",
|
|
82
|
+
{
|
|
83
|
+
rel: "stylesheet",
|
|
84
|
+
href: r,
|
|
85
|
+
precedence: "default",
|
|
86
|
+
"data-fragment-key": e
|
|
87
|
+
}
|
|
88
|
+
) : (V(r, e), null);
|
|
89
|
+
}
|
|
90
|
+
function Q(r) {
|
|
91
|
+
}
|
|
92
|
+
const P = {}, p = f(null);
|
|
93
|
+
p.displayName = "ProjectPropertiesContext";
|
|
94
|
+
class y extends R {
|
|
95
|
+
constructor() {
|
|
96
|
+
super(...arguments), this.state = { hasError: !1 };
|
|
97
|
+
}
|
|
98
|
+
static getDerivedStateFromError() {
|
|
99
|
+
return { hasError: !0 };
|
|
100
|
+
}
|
|
101
|
+
componentDidCatch(e, n) {
|
|
102
|
+
var t, s;
|
|
103
|
+
process.env.NODE_ENV !== "production" && console.error("[fragments] Area error:", e, n.componentStack), (s = (t = this.props).onError) == null || s.call(t, e);
|
|
104
|
+
}
|
|
105
|
+
render() {
|
|
106
|
+
return this.state.hasError ? null : this.props.children;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const x = ({
|
|
110
|
+
fragmentId: r,
|
|
111
|
+
props: e,
|
|
112
|
+
globalCssUrl: n = null,
|
|
113
|
+
projectProperties: t = P
|
|
114
|
+
}) => {
|
|
115
|
+
const s = L(r), l = b().$fragments, u = l == null ? void 0 : l.getRef(r), w = l == null ? void 0 : l.getScopeClass(r), A = e ?? {};
|
|
116
|
+
return /* @__PURE__ */ M(j, { children: [
|
|
117
|
+
n != null && /* @__PURE__ */ o(
|
|
118
|
+
m,
|
|
119
|
+
{
|
|
120
|
+
href: n,
|
|
121
|
+
fragmentKey: "global"
|
|
122
|
+
}
|
|
123
|
+
),
|
|
124
|
+
u && /* @__PURE__ */ o(
|
|
125
|
+
m,
|
|
126
|
+
{
|
|
127
|
+
href: u.cssUrl,
|
|
128
|
+
fragmentKey: `fragment:${r}:css`
|
|
129
|
+
}
|
|
130
|
+
),
|
|
131
|
+
(u == null ? void 0 : u.fontsUrl) != null && /* @__PURE__ */ o(
|
|
132
|
+
m,
|
|
133
|
+
{
|
|
134
|
+
href: u.fontsUrl,
|
|
135
|
+
fragmentKey: `fragment:${r}:fonts`
|
|
136
|
+
}
|
|
137
|
+
),
|
|
138
|
+
/* @__PURE__ */ o(p.Provider, { value: t, children: /* @__PURE__ */ o(v.Provider, { value: null, children: /* @__PURE__ */ o(
|
|
139
|
+
"div",
|
|
140
|
+
{
|
|
141
|
+
className: w,
|
|
142
|
+
style: { display: "contents" },
|
|
143
|
+
children: /* @__PURE__ */ o(s, { ...A })
|
|
144
|
+
}
|
|
145
|
+
) }) })
|
|
146
|
+
] });
|
|
147
|
+
}, W = ({ onError: r, ...e }) => /* @__PURE__ */ o(C, { fallback: null, children: /* @__PURE__ */ o(y, { onError: r, children: /* @__PURE__ */ o(x, { ...e }) }) }), T = ({ areaCode: r, options: e }) => {
|
|
148
|
+
if ((e == null ? void 0 : e.pause) === !0)
|
|
149
|
+
return null;
|
|
150
|
+
const { rootFragmentId: n, props: t, projectProperties: s, globalCssUrl: i } = $(r, {
|
|
151
|
+
timeout: e == null ? void 0 : e.timeout,
|
|
152
|
+
ssr: e == null ? void 0 : e.ssr
|
|
153
|
+
});
|
|
154
|
+
return /* @__PURE__ */ o(
|
|
155
|
+
x,
|
|
156
|
+
{
|
|
157
|
+
fragmentId: n,
|
|
158
|
+
props: t,
|
|
159
|
+
globalCssUrl: i,
|
|
160
|
+
projectProperties: s
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
}, X = ({ areaCode: r, options: e }) => /* @__PURE__ */ o(C, { fallback: null, children: /* @__PURE__ */ o(y, { children: /* @__PURE__ */ o(T, { areaCode: r, options: e }) }) });
|
|
164
|
+
function Y({ client: r, children: e }) {
|
|
165
|
+
const n = g(
|
|
166
|
+
() => ({
|
|
167
|
+
read: (t) => r.$fragments.read(t)
|
|
168
|
+
}),
|
|
169
|
+
[r]
|
|
170
|
+
);
|
|
171
|
+
return /* @__PURE__ */ o(_, { value: r, children: /* @__PURE__ */ o(B, { value: n, children: e }) });
|
|
172
|
+
}
|
|
173
|
+
function Z(r) {
|
|
174
|
+
return (a(p) ?? P)[r];
|
|
175
|
+
}
|
|
176
|
+
function ee(r) {
|
|
177
|
+
return k({ ...r, inject: F });
|
|
178
|
+
}
|
|
179
|
+
export {
|
|
180
|
+
X as Area,
|
|
181
|
+
W as FragmentRenderer,
|
|
182
|
+
m as FragmentStylesheet,
|
|
183
|
+
ne as FragmentsApiVersionError,
|
|
184
|
+
oe as FragmentsAreaError,
|
|
185
|
+
c as FragmentsClientContext,
|
|
186
|
+
_ as FragmentsClientProvider,
|
|
187
|
+
se as FragmentsLoadError,
|
|
188
|
+
Y as FragmentsProvider,
|
|
189
|
+
v as InstanceIdContext,
|
|
190
|
+
h as ModulesMapContext,
|
|
191
|
+
B as ModulesMapProvider,
|
|
192
|
+
p as ProjectPropertiesContext,
|
|
193
|
+
ee as createBrowserClient,
|
|
194
|
+
P as defaultProjectProperties,
|
|
195
|
+
F as injectStylesheet,
|
|
196
|
+
$ as useArea,
|
|
197
|
+
Q as useCssChunk,
|
|
198
|
+
L as useFragmentComponent,
|
|
199
|
+
H as useFragmentMetrics,
|
|
200
|
+
V as useFragmentStylesheet,
|
|
201
|
+
Z as useProjectProperty
|
|
202
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(t,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("react/jsx-runtime"),require("react"),require("@fragmentsx/fragments-client")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","@fragmentsx/fragments-client"],n):(t=typeof globalThis<"u"?globalThis:t||self,n(t.FragmentsRuntimeReact={},t.jsxRuntime,t.React,t.fragmentsClient))})(this,function(t,n,u,d){"use strict";const a=u.createContext(null);a.displayName="FragmentsClientContext";function y(r){return n.jsx(a.Provider,{value:r.value,children:r.children})}function b(){return u.useContext(a)}const F=u.createContext(null);F.displayName="InstanceIdContext";const f=u.createContext(null);f.displayName="ModulesMapContext";function E({value:r,children:e}){return n.jsx(f.Provider,{value:r,children:e})}function x(){const r=u.useContext(a);return u.useMemo(()=>({reachGoal:e=>{var o,s;(s=(o=r==null?void 0:r.$metrics)==null?void 0:o.reachGoal)==null||s.call(o,e)}}),[r])}function p(r){const e=u.useContext(f);if(!e)throw new Error("[useFragmentComponent] No ModulesMapProvider in tree");return e.read(r)}const I=typeof window<"u";function M(r,e){const o=u.useContext(a);if(!o)throw new Error("[useArea] No FragmentsClientProvider in tree");const s=o.$area;if(!(s!=null&&s.read))throw new Error("[useArea] $area plugin not mounted");if((I||((e==null?void 0:e.ssr)??!0))&&!s.has(r)){const h=(e==null?void 0:e.timeout)!=null?{timeout:e.timeout}:void 0;s.resolve(r,h)}return s.read(r)}const j=typeof window<"u",N=()=>{},m=new Map,P=(r,e)=>{if(!j)return N;const o=m.get(e);if(o)return o.refCount+=1,()=>{o.refCount-=1,o.refCount===0&&(o.linkEl.remove(),m.delete(e))};const s=document.createElement("link");s.rel="stylesheet",s.href=r,s.setAttribute("data-fragment-key",e),document.head.appendChild(s);const l={refCount:1,linkEl:s};return m.set(e,l),()=>{l.refCount-=1,l.refCount===0&&(l.linkEl.remove(),m.delete(e))}},k=j?u.useLayoutEffect:u.useEffect;function A(r,e){k(()=>P(r,e),[r,e])}const O=parseInt(u.version.split(".")[0],10)>=19;function g({href:r,fragmentKey:e}){return O?n.jsx("link",{rel:"stylesheet",href:r,precedence:"default","data-fragment-key":e}):(A(r,e),null)}function _(r){}const v={},C=u.createContext(null);C.displayName="ProjectPropertiesContext";class w extends u.Component{constructor(){super(...arguments),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(e,o){var s,l;process.env.NODE_ENV!=="production"&&console.error("[fragments] Area error:",e,o.componentStack),(l=(s=this.props).onError)==null||l.call(s,e)}render(){return this.state.hasError?null:this.props.children}}const S=({fragmentId:r,props:e,globalCssUrl:o=null,projectProperties:s=v})=>{const l=p(r),c=b().$fragments,i=c==null?void 0:c.getRef(r),q=c==null?void 0:c.getScopeClass(r),D=e??{};return n.jsxs(n.Fragment,{children:[o!=null&&n.jsx(g,{href:o,fragmentKey:"global"}),i&&n.jsx(g,{href:i.cssUrl,fragmentKey:`fragment:${r}:css`}),(i==null?void 0:i.fontsUrl)!=null&&n.jsx(g,{href:i.fontsUrl,fragmentKey:`fragment:${r}:fonts`}),n.jsx(C.Provider,{value:s,children:n.jsx(F.Provider,{value:null,children:n.jsx("div",{className:q,style:{display:"contents"},children:n.jsx(l,{...D})})})})]})},T=({onError:r,...e})=>n.jsx(u.Suspense,{fallback:null,children:n.jsx(w,{onError:r,children:n.jsx(S,{...e})})}),B=({areaCode:r,options:e})=>{if((e==null?void 0:e.pause)===!0)return null;const{rootFragmentId:o,props:s,projectProperties:l,globalCssUrl:h}=M(r,{timeout:e==null?void 0:e.timeout,ssr:e==null?void 0:e.ssr});return n.jsx(S,{fragmentId:o,props:s,globalCssUrl:h,projectProperties:l})},L=({areaCode:r,options:e})=>n.jsx(u.Suspense,{fallback:null,children:n.jsx(w,{children:n.jsx(B,{areaCode:r,options:e})})});function U({client:r,children:e}){const o=u.useMemo(()=>({read:s=>r.$fragments.read(s)}),[r]);return n.jsx(y,{value:r,children:n.jsx(E,{value:o,children:e})})}function V(r){return(u.useContext(C)??v)[r]}function $(r){return d.createFragmentsClient({...r,inject:P})}Object.defineProperty(t,"FragmentsApiVersionError",{enumerable:!0,get:()=>d.FragmentsApiVersionError}),Object.defineProperty(t,"FragmentsAreaError",{enumerable:!0,get:()=>d.FragmentsAreaError}),Object.defineProperty(t,"FragmentsLoadError",{enumerable:!0,get:()=>d.FragmentsLoadError}),t.Area=L,t.FragmentRenderer=T,t.FragmentStylesheet=g,t.FragmentsClientContext=a,t.FragmentsClientProvider=y,t.FragmentsProvider=U,t.InstanceIdContext=F,t.ModulesMapContext=f,t.ModulesMapProvider=E,t.ProjectPropertiesContext=C,t.createBrowserClient=$,t.defaultProjectProperties=v,t.injectStylesheet=P,t.useArea=M,t.useCssChunk=_,t.useFragmentComponent=p,t.useFragmentMetrics=x,t.useFragmentStylesheet=A,t.useProjectProperty=V,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fragmentsx/runtime-react",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/FireworksX/fragments.git",
|
|
9
|
+
"directory": "packages/runtime-react"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.umd.js",
|
|
20
|
+
"module": "./dist/index.es.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.es.js",
|
|
26
|
+
"require": "./dist/index.umd.js",
|
|
27
|
+
"source": "./src/index.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@graph-state/core": "^0.13.3",
|
|
35
|
+
"@fragmentsx/fragments-client": "0.1.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
39
|
+
"@testing-library/react": "^16.0.1",
|
|
40
|
+
"@types/react": "^19.0.0",
|
|
41
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
42
|
+
"jsdom": "^27.4.0",
|
|
43
|
+
"react": "^19.0.0",
|
|
44
|
+
"react-dom": "^19.2.3",
|
|
45
|
+
"typescript": "^5.5.4",
|
|
46
|
+
"vite": "^6.1.0",
|
|
47
|
+
"vite-plugin-dts": "^4.5.0",
|
|
48
|
+
"vitest": "^3.1.1"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"clear": "rm -r -f ./dist",
|
|
52
|
+
"build": "vite build",
|
|
53
|
+
"dev": "vite build -w",
|
|
54
|
+
"test": "vitest run --config ./vitest.config.ts",
|
|
55
|
+
"test:watch": "vitest --config ./vitest.config.ts",
|
|
56
|
+
"lint": "eslint . --ext .ts,.tsx --fix"
|
|
57
|
+
}
|
|
58
|
+
}
|