@fragmentsx/runtime-react 0.1.0 → 0.2.0-dev-20260901074721
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 +62 -11
- package/dist/index.d.ts +106 -6
- package/dist/index.es.js +150 -104
- package/dist/index.umd.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,12 +8,11 @@ React.
|
|
|
8
8
|
## Что это решает
|
|
9
9
|
|
|
10
10
|
Скомпилированный сервером фрагмент — это ESM-артефакт, который нужно
|
|
11
|
-
загрузить, смонтировать в дерево React, подключить его стили и дать
|
|
11
|
+
загрузить, смонтировать в дерево React, подключить его стили и дать доступ
|
|
12
12
|
к метрикам и переменным области. `runtime-react` закрывает весь этот путь:
|
|
13
13
|
|
|
14
|
-
- `FragmentsProvider`
|
|
15
|
-
|
|
16
|
-
- `Area` / `FragmentRenderer` — резолвинг и рендер области/фрагмента;
|
|
14
|
+
- `FragmentsProvider` — контекст клиента на дерево приложения;
|
|
15
|
+
- `Area` / `FragmentRenderer` — резолвинг области и рендер фрагмента;
|
|
17
16
|
- `useFragmentComponent`, `useFragmentStylesheet`, `FragmentStylesheet` —
|
|
18
17
|
подгрузка компонента и его CSS;
|
|
19
18
|
- `useFragmentMetrics`, `useArea`, `useProjectProperty` — доступ к метрикам,
|
|
@@ -25,27 +24,79 @@ React.
|
|
|
25
24
|
pnpm add @fragmentsx/runtime-react @fragmentsx/fragments-client react react-dom
|
|
26
25
|
```
|
|
27
26
|
|
|
27
|
+
Пакет объявляет `react` в `peerDependencies` (`^17 || ^18 || ^19`).
|
|
28
|
+
|
|
29
|
+
Если приложение собирается через Vite — поставьте ещё плагин, который
|
|
30
|
+
резолвит bare imports внутри загружаемых артефактов:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm add -D @fragmentsx/vite-plugin
|
|
34
|
+
```
|
|
35
|
+
|
|
28
36
|
## Использование
|
|
29
37
|
|
|
30
38
|
```tsx
|
|
31
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
createBrowserClient,
|
|
41
|
+
FragmentsProvider,
|
|
42
|
+
Area,
|
|
43
|
+
} from '@fragmentsx/runtime-react';
|
|
32
44
|
|
|
33
45
|
const client = createBrowserClient({
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
apiToken: import.meta.env.VITE_FRAGMENTS_API_TOKEN,
|
|
47
|
+
backendEndpoint: 'https://api.example.com',
|
|
36
48
|
});
|
|
37
49
|
|
|
38
50
|
function App() {
|
|
39
51
|
return (
|
|
40
52
|
<FragmentsProvider client={client}>
|
|
41
|
-
<Area
|
|
53
|
+
<Area areaCode="home" />
|
|
42
54
|
</FragmentsProvider>
|
|
43
55
|
);
|
|
44
56
|
}
|
|
45
57
|
```
|
|
46
58
|
|
|
59
|
+
`apiToken` обязателен. `backendEndpoint` — источник манифеста области; если
|
|
60
|
+
не передан, клиент бьётся в `window.location.origin`, что удобно при
|
|
61
|
+
same-origin проксировании `/api` на публичный API площадки.
|
|
62
|
+
|
|
63
|
+
`Area` сама разрешает область и внутри себя обёрнута в `Suspense` — пока
|
|
64
|
+
манифест грузится, поддерево пустое. Под капотом:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
<Area areaCode> → GET <backendEndpoint>/api/area/<code>/compiled
|
|
68
|
+
→ manifest.fragments[*].moduleUrl → import(артефакт)
|
|
69
|
+
→ <FragmentStylesheet href={cssUrl}/> + рендер фрагмента
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Рендер конкретного фрагмента
|
|
73
|
+
|
|
74
|
+
Когда область разрешать не нужно, используйте `FragmentRenderer` напрямую:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { FragmentRenderer } from '@fragmentsx/runtime-react';
|
|
78
|
+
|
|
79
|
+
<FragmentRenderer fragmentId={42} props={{ title: 'Привет' }} />
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`AreaProps` — `{ areaCode: string; options?: { timeout?, pause?, ssr? } }`.
|
|
83
|
+
`FragmentRendererProps` — `{ fragmentId: number; props?; globalCssUrl?;
|
|
84
|
+
projectProperties?; onError? }`.
|
|
85
|
+
|
|
86
|
+
## Обработка ошибок
|
|
87
|
+
|
|
88
|
+
Пакет реэкспортирует типизированные ошибки транспорта, чтобы их можно было
|
|
89
|
+
различать в границах ошибок приложения:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import {
|
|
93
|
+
FragmentsAreaError,
|
|
94
|
+
FragmentsApiVersionError,
|
|
95
|
+
FragmentsLoadError,
|
|
96
|
+
} from '@fragmentsx/runtime-react';
|
|
97
|
+
```
|
|
98
|
+
|
|
47
99
|
## Пара с fragments-client
|
|
48
100
|
|
|
49
|
-
`runtime-react` зависит от `@fragmentsx/fragments-client` как от
|
|
50
|
-
ядра
|
|
51
|
-
и обновлять пакеты стоит вместе.
|
|
101
|
+
`runtime-react` зависит от `@fragmentsx/fragments-client` как от
|
|
102
|
+
транспортного ядра — устанавливать и обновлять пакеты стоит вместе.
|
package/dist/index.d.ts
CHANGED
|
@@ -20,13 +20,33 @@ import { ReactNode } from 'react';
|
|
|
20
20
|
export declare const Area: FC<AreaProps>;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Сентинел нерезолвленной области — возвращается вместо чтения `$area.read`,
|
|
24
|
+
* когда на сервере `options.ssr === false`, либо когда `options.pause === true`
|
|
25
|
+
* (suspense-режим).
|
|
26
|
+
*
|
|
27
|
+
* Совместим с `UseAreaResult` по форме (тип возврата хука не расширяется),
|
|
28
|
+
* поэтому правка остаётся minor: потребитель, передающий `rootFragmentId`
|
|
29
|
+
* в проп типа `number`, продолжает компилироваться. Заморожен — сравнение
|
|
30
|
+
* по ссылке в `Area.tsx` не должно ломаться мутацией снаружи.
|
|
31
|
+
*/
|
|
32
|
+
export declare const AREA_UNRESOLVED: UseAreaResult;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Props для Area компонента (D-09, сокращён Phase 73 B-13; расширен quick 260830-rvp):
|
|
24
36
|
* - areaCode — строковый код области (Area.code в backend schema)
|
|
25
37
|
* - options.pause — пропустить рендер (conditional skip per legacy contract)
|
|
26
38
|
* - options.timeout — network timeout для $area.resolve fetch
|
|
27
39
|
* - options.ssr — отключить auto-resolve в SSR environment (default `true`);
|
|
28
40
|
* в browser auto-resolve запускается всегда. Параллель к render-react
|
|
29
41
|
* Area options.ssr.
|
|
42
|
+
* - options.props (INT-03) — объект мержится поверх серверных props (ключи
|
|
43
|
+
* потребителя выигрывают), функция вызывается с серверными props и её
|
|
44
|
+
* результат уходит в артефакт целиком. Обе формы поддержаны намеренно —
|
|
45
|
+
* паритет с legacy render-react, с которого мигрирует потребитель.
|
|
46
|
+
* - options.fallback (INT-04) — узел, видимый до разрешения области; default
|
|
47
|
+
* `null` (прежнее поведение).
|
|
48
|
+
* - options.onError (INT-04) — вызывается при броске внутри поддерева;
|
|
49
|
+
* default — только dev-лог границы (прежнее поведение).
|
|
30
50
|
*/
|
|
31
51
|
export declare interface AreaProps {
|
|
32
52
|
areaCode: string;
|
|
@@ -34,6 +54,9 @@ export declare interface AreaProps {
|
|
|
34
54
|
timeout?: number;
|
|
35
55
|
pause?: boolean;
|
|
36
56
|
ssr?: boolean;
|
|
57
|
+
props?: Record<string, unknown> | ((serverProps: Record<string, unknown>) => Record<string, unknown>);
|
|
58
|
+
fallback?: ReactNode;
|
|
59
|
+
onError?: (error: Error) => void;
|
|
37
60
|
};
|
|
38
61
|
}
|
|
39
62
|
|
|
@@ -146,9 +169,10 @@ declare interface FragmentStylesheetProps {
|
|
|
146
169
|
*
|
|
147
170
|
* @param href - CDN URL стилевого файла
|
|
148
171
|
* @param key - уникальный ключ для dedup (например `chunk:201` или `fragment:42:abc`)
|
|
149
|
-
* @returns
|
|
172
|
+
* @returns handle — вызываемый cleanup (при refCount = 0 удаляет `<link>` из
|
|
173
|
+
* head) с дополнительным свойством `loaded: Promise<void>`
|
|
150
174
|
*/
|
|
151
|
-
export declare const injectStylesheet: (href: string, key: string) =>
|
|
175
|
+
export declare const injectStylesheet: (href: string, key: string) => StylesheetHandle;
|
|
152
176
|
|
|
153
177
|
export declare const InstanceIdContext: Context<string | null>;
|
|
154
178
|
|
|
@@ -203,7 +227,49 @@ export declare interface ReachGoalInfo {
|
|
|
203
227
|
campaignId?: number;
|
|
204
228
|
}
|
|
205
229
|
|
|
206
|
-
|
|
230
|
+
/**
|
|
231
|
+
* Ref-counted `<link rel="stylesheet">` injector для compiled fragment CSS delivery (Req #11).
|
|
232
|
+
*
|
|
233
|
+
* **Single source of truth для ref-count dedup**: $cssChunks (client-core) делегирует
|
|
234
|
+
* DOM injection сюда через factory parameter (BLOCKER-4 architectural decision).
|
|
235
|
+
* Module-level registry гарантирует что за один bundle-scope один key = один <link>.
|
|
236
|
+
*
|
|
237
|
+
* Использование:
|
|
238
|
+
* ```ts
|
|
239
|
+
* const handle = injectStylesheet('https://cdn.example.com/chunk.css', 'chunk:201');
|
|
240
|
+
* await handle.loaded; // готовность CSS (load / error / таймаут — quick-260831-etq)
|
|
241
|
+
* // ... позже при unmount:
|
|
242
|
+
* handle();
|
|
243
|
+
* ```
|
|
244
|
+
*
|
|
245
|
+
* Ref-count semantics:
|
|
246
|
+
* - Первый вызов с key → создаёт `<link>`, refCount = 1
|
|
247
|
+
* - Повторный вызов с тем же key → refCount++, новый `<link>` НЕ создаётся
|
|
248
|
+
* - Cleanup → refCount--; при refCount = 0 → `<link>` удаляется из head
|
|
249
|
+
*
|
|
250
|
+
* Канал готовности (quick-260831-etq, D-5/D-6): возвращаемый handle несёт
|
|
251
|
+
* `loaded: Promise<void>`, который резолвится по `load`, по `error` (сбой
|
|
252
|
+
* трактуется как «готово» — показать без стилей лучше, чем зависнуть) или по
|
|
253
|
+
* истечении `STYLESHEET_READY_TIMEOUT_MS`, если ни одно событие не пришло
|
|
254
|
+
* (недоступный/молчащий CDN). Промис никогда не отвергается.
|
|
255
|
+
*
|
|
256
|
+
* SSR safety: в Node (isBrowser === false) возвращает свежий noop-handle с уже
|
|
257
|
+
* разрешённым `loaded` — общий экспорт `noop` не мутируется.
|
|
258
|
+
*/
|
|
259
|
+
/** Канал готовности CSS у ref-counted injectStylesheet handle. */
|
|
260
|
+
declare type StylesheetHandle = (() => void) & {
|
|
261
|
+
loaded: Promise<void>;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/** Suspense-режим (по умолчанию либо явный `suspense: true`) — прежний throw-протокол. */
|
|
265
|
+
export declare function useArea(areaCode: string, options?: UseAreaOptions & {
|
|
266
|
+
suspense?: true;
|
|
267
|
+
}): UseAreaResult;
|
|
268
|
+
|
|
269
|
+
/** Не-suspense режим (INT-05) — явный `suspense: false` возвращает состояние вместо throw. */
|
|
270
|
+
export declare function useArea(areaCode: string, options: UseAreaOptions & {
|
|
271
|
+
suspense: false;
|
|
272
|
+
}): UseAreaState;
|
|
207
273
|
|
|
208
274
|
/**
|
|
209
275
|
* Опции хука. По дизайну API параллелен render-react useArea:
|
|
@@ -211,17 +277,31 @@ export declare function useArea(areaCode: string, options?: UseAreaOptions): Use
|
|
|
211
277
|
* - `ssr` — стартовать ли auto-resolve на сервере (default `true`, чтобы
|
|
212
278
|
* Next.js getServerSideProps мог hydration'ить с прогретым кэшем).
|
|
213
279
|
* В browser environment auto-resolve всегда включён.
|
|
280
|
+
* - `pause` (INT-06) — пропустить резолв и чтение кэша вовсе; паритет
|
|
281
|
+
* с `AreaProps.options.pause`.
|
|
282
|
+
* - `suspense` (INT-05) — `false` переключает хук на `UseAreaState` вместо
|
|
283
|
+
* throw-протокола; по умолчанию `true` (прежнее поведение).
|
|
214
284
|
*/
|
|
215
|
-
declare interface UseAreaOptions {
|
|
285
|
+
export declare interface UseAreaOptions {
|
|
216
286
|
timeout?: number;
|
|
217
287
|
ssr?: boolean;
|
|
288
|
+
pause?: boolean;
|
|
289
|
+
suspense?: boolean;
|
|
218
290
|
}
|
|
219
291
|
|
|
220
292
|
/**
|
|
221
293
|
* Result shape возвращаемый useArea — зеркалирует AreaResolveResult из client-core.
|
|
222
294
|
*/
|
|
223
|
-
declare interface UseAreaResult {
|
|
295
|
+
export declare interface UseAreaResult {
|
|
224
296
|
rootFragmentId: number;
|
|
297
|
+
/**
|
|
298
|
+
* Quick 260831-dor (INT-10): id выбранного варианта — вход для атрибуции
|
|
299
|
+
* на стороне площадки-потребителя (префикс партнёрской ссылки трекера).
|
|
300
|
+
* `$area` уже нормализует отсутствие поля у backend'а в `null` (не `0` —
|
|
301
|
+
* `0` неотличим от настоящего id и молча испортил бы атрибуцию), поэтому
|
|
302
|
+
* поле обязательное и nullable, а не опциональное.
|
|
303
|
+
*/
|
|
304
|
+
variantId: number | null;
|
|
225
305
|
/**
|
|
226
306
|
* Phase 73, B-02/B-06: значения props корневого артефакта, разрешённые сервером
|
|
227
307
|
* (`resolveVariantProps`) — привязки `$ref` сюда не доезжают, только готовые значения.
|
|
@@ -244,6 +324,26 @@ declare interface UseAreaResult {
|
|
|
244
324
|
globalCssUrl: string | null;
|
|
245
325
|
}
|
|
246
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Состояние, возвращаемое `useArea(code, {suspense:false})` вместо throw.
|
|
329
|
+
*/
|
|
330
|
+
export declare interface UseAreaState {
|
|
331
|
+
status: UseAreaStatus;
|
|
332
|
+
data: UseAreaResult | null;
|
|
333
|
+
error: Error | null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Union статуса не-suspense режима (quick 260830-rvp, INT-05):
|
|
338
|
+
* - `idle` — пауза (`options.pause`) либо серверный пропуск (`ssr:false` на сервере).
|
|
339
|
+
* - `loading` — кэш пуст, резолв запущен (идемпотентно), данных ещё нет.
|
|
340
|
+
* - `ready` — терминальный кэш-хит, данные доступны.
|
|
341
|
+
* - `not-found` — терминальная ошибка резолва (сохранённая в rejected-кэше);
|
|
342
|
+
* отдельного статуса под сетевой сбой нет намеренно — оба исхода для площадки
|
|
343
|
+
* означают «показать свой шаблон», различие доступно через поле `error`.
|
|
344
|
+
*/
|
|
345
|
+
export declare type UseAreaStatus = "idle" | "loading" | "ready" | "not-found";
|
|
346
|
+
|
|
247
347
|
/**
|
|
248
348
|
* useCssChunk — no-op stub для compiled fragments.
|
|
249
349
|
*
|
package/dist/index.es.js
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import { createContext as
|
|
3
|
-
import { createFragmentsClient as
|
|
4
|
-
import { FragmentsApiVersionError as
|
|
5
|
-
const
|
|
1
|
+
import { jsx as l, jsxs as j, Fragment as I } from "react/jsx-runtime";
|
|
2
|
+
import { createContext as v, useContext as f, useMemo as P, useState as L, useEffect as F, useLayoutEffect as N, version as k, Component as b, Suspense as A } from "react";
|
|
3
|
+
import { AREA_GLOBAL_CSS_KEY as p, fragmentCssKey as O, fragmentFontsKey as U, createFragmentsClient as B } from "@fragmentsx/fragments-client";
|
|
4
|
+
import { FragmentsApiVersionError as oe, FragmentsAreaError as ie, FragmentsLoadError as fe } from "@fragmentsx/fragments-client";
|
|
5
|
+
const m = v(
|
|
6
6
|
null
|
|
7
7
|
);
|
|
8
|
-
|
|
9
|
-
function
|
|
10
|
-
return /* @__PURE__ */
|
|
8
|
+
m.displayName = "FragmentsClientContext";
|
|
9
|
+
function D(r) {
|
|
10
|
+
return /* @__PURE__ */ l(m.Provider, { value: r.value, children: r.children });
|
|
11
11
|
}
|
|
12
|
-
function
|
|
13
|
-
return
|
|
12
|
+
function K() {
|
|
13
|
+
return f(m);
|
|
14
14
|
}
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const
|
|
15
|
+
const S = v(null);
|
|
16
|
+
S.displayName = "InstanceIdContext";
|
|
17
|
+
const g = v(
|
|
18
18
|
null
|
|
19
19
|
);
|
|
20
|
-
|
|
21
|
-
function
|
|
20
|
+
g.displayName = "ModulesMapContext";
|
|
21
|
+
function V({
|
|
22
22
|
value: r,
|
|
23
23
|
children: e
|
|
24
24
|
}) {
|
|
25
|
-
return /* @__PURE__ */
|
|
25
|
+
return /* @__PURE__ */ l(g.Provider, { value: r, children: e });
|
|
26
26
|
}
|
|
27
|
-
function
|
|
28
|
-
const r =
|
|
29
|
-
return
|
|
27
|
+
function re() {
|
|
28
|
+
const r = f(m);
|
|
29
|
+
return P(
|
|
30
30
|
() => ({
|
|
31
31
|
reachGoal: (e) => {
|
|
32
32
|
var n, t;
|
|
@@ -36,48 +36,90 @@ function H() {
|
|
|
36
36
|
[r]
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
|
-
function
|
|
40
|
-
const e =
|
|
39
|
+
function G(r) {
|
|
40
|
+
const e = f(g);
|
|
41
41
|
if (!e)
|
|
42
42
|
throw new Error("[useFragmentComponent] No ModulesMapProvider in tree");
|
|
43
43
|
return e.read(r);
|
|
44
44
|
}
|
|
45
|
-
const
|
|
45
|
+
const Y = typeof window < "u", x = Object.freeze({
|
|
46
|
+
rootFragmentId: 0,
|
|
47
|
+
variantId: null,
|
|
48
|
+
props: {},
|
|
49
|
+
projectProperties: {},
|
|
50
|
+
globalCssUrl: null
|
|
51
|
+
});
|
|
46
52
|
function $(r, e) {
|
|
47
|
-
const n =
|
|
53
|
+
const n = f(m);
|
|
48
54
|
if (!n)
|
|
49
55
|
throw new Error("[useArea] No FragmentsClientProvider in tree");
|
|
50
56
|
const t = n.$area;
|
|
51
57
|
if (!(t != null && t.read))
|
|
52
58
|
throw new Error("[useArea] $area plugin not mounted");
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
const s = (e == null ? void 0 : e.suspense) ?? !0, d = (e == null ? void 0 : e.pause) === !0 || !Y && (e == null ? void 0 : e.ssr) === !1, a = (e == null ? void 0 : e.timeout) != null ? { timeout: e.timeout } : void 0, [, u] = L(0);
|
|
60
|
+
if (F(() => {
|
|
61
|
+
if (s || d) return;
|
|
62
|
+
let c = !0;
|
|
63
|
+
return Promise.resolve(t.resolve(r, a)).catch(() => {
|
|
64
|
+
}).finally(() => {
|
|
65
|
+
c && u((o) => o + 1);
|
|
66
|
+
}), () => {
|
|
67
|
+
c = !1;
|
|
68
|
+
};
|
|
69
|
+
}, [r, s, e == null ? void 0 : e.pause, e == null ? void 0 : e.timeout, e == null ? void 0 : e.ssr]), s)
|
|
70
|
+
return d ? x : (t.has(r) || Promise.resolve(t.resolve(r, a)).catch(() => {
|
|
71
|
+
}), t.read(r));
|
|
72
|
+
if (d)
|
|
73
|
+
return { status: "idle", data: null, error: null };
|
|
74
|
+
if (t.has(r))
|
|
75
|
+
try {
|
|
76
|
+
return { status: "ready", data: t.read(r), error: null };
|
|
77
|
+
} catch (c) {
|
|
78
|
+
return {
|
|
79
|
+
status: "not-found",
|
|
80
|
+
data: null,
|
|
81
|
+
error: c instanceof Error ? c : new Error(String(c))
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return Promise.resolve(t.resolve(r, a)).catch(() => {
|
|
85
|
+
}), { status: "loading", data: null, error: null };
|
|
58
86
|
}
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
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));
|
|
87
|
+
const w = typeof window < "u", z = 3e3, h = /* @__PURE__ */ new Map(), M = (r, e) => {
|
|
88
|
+
if (!w) {
|
|
89
|
+
const i = () => {
|
|
67
90
|
};
|
|
91
|
+
return i.loaded = Promise.resolve(), i;
|
|
92
|
+
}
|
|
93
|
+
const n = h.get(e);
|
|
94
|
+
if (n) {
|
|
95
|
+
n.refCount += 1;
|
|
96
|
+
const i = () => {
|
|
97
|
+
n.refCount -= 1, n.refCount === 0 && (n.clearReadyTimer(), n.linkEl.remove(), h.delete(e));
|
|
98
|
+
};
|
|
99
|
+
return i.loaded = n.loaded, i;
|
|
100
|
+
}
|
|
68
101
|
const t = document.createElement("link");
|
|
69
|
-
t.rel = "stylesheet", t.href = r, t.setAttribute("data-fragment-key", e)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
s
|
|
102
|
+
t.rel = "stylesheet", t.href = r, t.setAttribute("data-fragment-key", e);
|
|
103
|
+
let s;
|
|
104
|
+
const d = new Promise((i) => {
|
|
105
|
+
s = i;
|
|
106
|
+
}), a = () => {
|
|
107
|
+
c(), s();
|
|
108
|
+
}, u = setTimeout(a, z), c = () => clearTimeout(u);
|
|
109
|
+
t.addEventListener("load", a), t.addEventListener("error", a), document.head.appendChild(t);
|
|
110
|
+
const o = { refCount: 1, linkEl: t, loaded: d, clearReadyTimer: c };
|
|
111
|
+
h.set(e, o);
|
|
112
|
+
const y = () => {
|
|
113
|
+
o.refCount -= 1, o.refCount === 0 && (o.clearReadyTimer(), o.linkEl.remove(), h.delete(e));
|
|
73
114
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
115
|
+
return y.loaded = d, y;
|
|
116
|
+
}, H = w ? N : F;
|
|
117
|
+
function J(r, e) {
|
|
118
|
+
H(() => M(r, e), [r, e]);
|
|
77
119
|
}
|
|
78
|
-
const
|
|
79
|
-
function
|
|
80
|
-
return
|
|
120
|
+
const q = parseInt(k.split(".")[0], 10), Q = q >= 19;
|
|
121
|
+
function E({ href: r, fragmentKey: e }) {
|
|
122
|
+
return Q ? /* @__PURE__ */ l(
|
|
81
123
|
"link",
|
|
82
124
|
{
|
|
83
125
|
rel: "stylesheet",
|
|
@@ -85,13 +127,13 @@ function m({ href: r, fragmentKey: e }) {
|
|
|
85
127
|
precedence: "default",
|
|
86
128
|
"data-fragment-key": e
|
|
87
129
|
}
|
|
88
|
-
) : (
|
|
130
|
+
) : (J(r, e), null);
|
|
89
131
|
}
|
|
90
|
-
function
|
|
132
|
+
function te(r) {
|
|
91
133
|
}
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
class
|
|
134
|
+
const R = {}, C = v(null);
|
|
135
|
+
C.displayName = "ProjectPropertiesContext";
|
|
136
|
+
class T extends b {
|
|
95
137
|
constructor() {
|
|
96
138
|
super(...arguments), this.state = { hasError: !1 };
|
|
97
139
|
}
|
|
@@ -106,97 +148,101 @@ class y extends R {
|
|
|
106
148
|
return this.state.hasError ? null : this.props.children;
|
|
107
149
|
}
|
|
108
150
|
}
|
|
109
|
-
const
|
|
151
|
+
const _ = ({
|
|
110
152
|
fragmentId: r,
|
|
111
153
|
props: e,
|
|
112
154
|
globalCssUrl: n = null,
|
|
113
|
-
projectProperties: t =
|
|
155
|
+
projectProperties: t = R
|
|
114
156
|
}) => {
|
|
115
|
-
const s =
|
|
116
|
-
return /* @__PURE__ */
|
|
117
|
-
n != null && /* @__PURE__ */
|
|
118
|
-
|
|
157
|
+
const s = G(r), a = K().$fragments, u = a == null ? void 0 : a.getRef(r), c = a == null ? void 0 : a.getScopeClass(r), o = e ?? {};
|
|
158
|
+
return /* @__PURE__ */ j(I, { children: [
|
|
159
|
+
n != null && /* @__PURE__ */ l(
|
|
160
|
+
E,
|
|
119
161
|
{
|
|
120
162
|
href: n,
|
|
121
|
-
fragmentKey:
|
|
163
|
+
fragmentKey: p
|
|
122
164
|
}
|
|
123
165
|
),
|
|
124
|
-
u && /* @__PURE__ */
|
|
125
|
-
|
|
166
|
+
u && /* @__PURE__ */ l(
|
|
167
|
+
E,
|
|
126
168
|
{
|
|
127
169
|
href: u.cssUrl,
|
|
128
|
-
fragmentKey:
|
|
170
|
+
fragmentKey: O(r)
|
|
129
171
|
}
|
|
130
172
|
),
|
|
131
|
-
(u == null ? void 0 : u.fontsUrl) != null && /* @__PURE__ */
|
|
132
|
-
|
|
173
|
+
(u == null ? void 0 : u.fontsUrl) != null && /* @__PURE__ */ l(
|
|
174
|
+
E,
|
|
133
175
|
{
|
|
134
176
|
href: u.fontsUrl,
|
|
135
|
-
fragmentKey:
|
|
177
|
+
fragmentKey: U(r)
|
|
136
178
|
}
|
|
137
179
|
),
|
|
138
|
-
/* @__PURE__ */
|
|
180
|
+
/* @__PURE__ */ l(C.Provider, { value: t, children: /* @__PURE__ */ l(S.Provider, { value: null, children: /* @__PURE__ */ l(
|
|
139
181
|
"div",
|
|
140
182
|
{
|
|
141
|
-
className:
|
|
183
|
+
className: c,
|
|
142
184
|
style: { display: "contents" },
|
|
143
|
-
children: /* @__PURE__ */
|
|
185
|
+
children: /* @__PURE__ */ l(s, { ...o })
|
|
144
186
|
}
|
|
145
187
|
) }) })
|
|
146
188
|
] });
|
|
147
|
-
},
|
|
189
|
+
}, ne = ({ onError: r, ...e }) => /* @__PURE__ */ l(A, { fallback: null, children: /* @__PURE__ */ l(T, { onError: r, children: /* @__PURE__ */ l(_, { ...e }) }) }), W = ({ areaCode: r, options: e }) => {
|
|
148
190
|
if ((e == null ? void 0 : e.pause) === !0)
|
|
149
191
|
return null;
|
|
150
|
-
const
|
|
192
|
+
const n = $(r, {
|
|
151
193
|
timeout: e == null ? void 0 : e.timeout,
|
|
152
194
|
ssr: e == null ? void 0 : e.ssr
|
|
153
195
|
});
|
|
154
|
-
|
|
155
|
-
|
|
196
|
+
if (n === x)
|
|
197
|
+
return null;
|
|
198
|
+
const { rootFragmentId: t, props: s, projectProperties: d, globalCssUrl: a } = n, u = typeof (e == null ? void 0 : e.props) == "function" ? e.props(s) : e != null && e.props ? { ...s, ...e.props } : s;
|
|
199
|
+
return /* @__PURE__ */ l(
|
|
200
|
+
_,
|
|
156
201
|
{
|
|
157
|
-
fragmentId:
|
|
158
|
-
props:
|
|
159
|
-
globalCssUrl:
|
|
160
|
-
projectProperties:
|
|
202
|
+
fragmentId: t,
|
|
203
|
+
props: u,
|
|
204
|
+
globalCssUrl: a,
|
|
205
|
+
projectProperties: d
|
|
161
206
|
}
|
|
162
207
|
);
|
|
163
|
-
},
|
|
164
|
-
function
|
|
165
|
-
const n =
|
|
208
|
+
}, le = ({ areaCode: r, options: e }) => /* @__PURE__ */ l(A, { fallback: (e == null ? void 0 : e.fallback) ?? null, children: /* @__PURE__ */ l(T, { onError: e == null ? void 0 : e.onError, children: /* @__PURE__ */ l(W, { areaCode: r, options: e }) }) });
|
|
209
|
+
function ae({ client: r, children: e }) {
|
|
210
|
+
const n = P(
|
|
166
211
|
() => ({
|
|
167
212
|
read: (t) => r.$fragments.read(t)
|
|
168
213
|
}),
|
|
169
214
|
[r]
|
|
170
215
|
);
|
|
171
|
-
return /* @__PURE__ */
|
|
216
|
+
return /* @__PURE__ */ l(D, { value: r, children: /* @__PURE__ */ l(V, { value: n, children: e }) });
|
|
172
217
|
}
|
|
173
|
-
function
|
|
174
|
-
return (
|
|
218
|
+
function se(r) {
|
|
219
|
+
return (f(C) ?? R)[r];
|
|
175
220
|
}
|
|
176
|
-
function
|
|
177
|
-
return
|
|
221
|
+
function ue(r) {
|
|
222
|
+
return B({ ...r, inject: M });
|
|
178
223
|
}
|
|
179
224
|
export {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
oe as
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
225
|
+
x as AREA_UNRESOLVED,
|
|
226
|
+
le as Area,
|
|
227
|
+
ne as FragmentRenderer,
|
|
228
|
+
E as FragmentStylesheet,
|
|
229
|
+
oe as FragmentsApiVersionError,
|
|
230
|
+
ie as FragmentsAreaError,
|
|
231
|
+
m as FragmentsClientContext,
|
|
232
|
+
D as FragmentsClientProvider,
|
|
233
|
+
fe as FragmentsLoadError,
|
|
234
|
+
ae as FragmentsProvider,
|
|
235
|
+
S as InstanceIdContext,
|
|
236
|
+
g as ModulesMapContext,
|
|
237
|
+
V as ModulesMapProvider,
|
|
238
|
+
C as ProjectPropertiesContext,
|
|
239
|
+
ue as createBrowserClient,
|
|
240
|
+
R as defaultProjectProperties,
|
|
241
|
+
M as injectStylesheet,
|
|
196
242
|
$ as useArea,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
243
|
+
te as useCssChunk,
|
|
244
|
+
G as useFragmentComponent,
|
|
245
|
+
re as useFragmentMetrics,
|
|
246
|
+
J as useFragmentStylesheet,
|
|
247
|
+
se as useProjectProperty
|
|
202
248
|
};
|
package/dist/index.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(t,
|
|
1
|
+
(function(t,s){typeof exports=="object"&&typeof module<"u"?s(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"],s):(t=typeof globalThis<"u"?globalThis:t||self,s(t.FragmentsRuntimeReact={},t.jsxRuntime,t.React,t.fragmentsClient))})(this,function(t,s,u,m){"use strict";const g=u.createContext(null);g.displayName="FragmentsClientContext";function M(r){return s.jsx(g.Provider,{value:r.value,children:r.children})}function N(){return u.useContext(g)}const y=u.createContext(null);y.displayName="InstanceIdContext";const E=u.createContext(null);E.displayName="ModulesMapContext";function j({value:r,children:e}){return s.jsx(E.Provider,{value:r,children:e})}function k(){const r=u.useContext(g);return u.useMemo(()=>({reachGoal:e=>{var l,n;(n=(l=r==null?void 0:r.$metrics)==null?void 0:l.reachGoal)==null||n.call(l,e)}}),[r])}function w(r){const e=u.useContext(E);if(!e)throw new Error("[useFragmentComponent] No ModulesMapProvider in tree");return e.read(r)}const x=typeof window<"u",F=Object.freeze({rootFragmentId:0,variantId:null,props:{},projectProperties:{},globalCssUrl:null});function T(r,e){const l=u.useContext(g);if(!l)throw new Error("[useArea] No FragmentsClientProvider in tree");const n=l.$area;if(!(n!=null&&n.read))throw new Error("[useArea] $area plugin not mounted");const c=(e==null?void 0:e.suspense)??!0,i=(e==null?void 0:e.pause)===!0||!x&&(e==null?void 0:e.ssr)===!1,a=(e==null?void 0:e.timeout)!=null?{timeout:e.timeout}:void 0,[,d]=u.useState(0);if(u.useEffect(()=>{if(c||i)return;let o=!0;return Promise.resolve(n.resolve(r,a)).catch(()=>{}).finally(()=>{o&&d(f=>f+1)}),()=>{o=!1}},[r,c,e==null?void 0:e.pause,e==null?void 0:e.timeout,e==null?void 0:e.ssr]),c)return i?F:(n.has(r)||Promise.resolve(n.resolve(r,a)).catch(()=>{}),n.read(r));if(i)return{status:"idle",data:null,error:null};if(n.has(r))try{return{status:"ready",data:n.read(r),error:null}}catch(o){return{status:"not-found",data:null,error:o instanceof Error?o:new Error(String(o))}}return Promise.resolve(n.resolve(r,a)).catch(()=>{}),{status:"loading",data:null,error:null}}const _=typeof window<"u",U=3e3,v=new Map,A=(r,e)=>{if(!_){const h=()=>{};return h.loaded=Promise.resolve(),h}const l=v.get(e);if(l){l.refCount+=1;const h=()=>{l.refCount-=1,l.refCount===0&&(l.clearReadyTimer(),l.linkEl.remove(),v.delete(e))};return h.loaded=l.loaded,h}const n=document.createElement("link");n.rel="stylesheet",n.href=r,n.setAttribute("data-fragment-key",e);let c;const i=new Promise(h=>{c=h}),a=()=>{o(),c()},d=setTimeout(a,U),o=()=>clearTimeout(d);n.addEventListener("load",a),n.addEventListener("error",a),document.head.appendChild(n);const f={refCount:1,linkEl:n,loaded:i,clearReadyTimer:o};v.set(e,f);const O=()=>{f.refCount-=1,f.refCount===0&&(f.clearReadyTimer(),f.linkEl.remove(),v.delete(e))};return O.loaded=i,O},B=_?u.useLayoutEffect:u.useEffect;function b(r,e){B(()=>A(r,e),[r,e])}const D=parseInt(u.version.split(".")[0],10)>=19;function C({href:r,fragmentKey:e}){return D?s.jsx("link",{rel:"stylesheet",href:r,precedence:"default","data-fragment-key":e}):(b(r,e),null)}function V(r){}const S={},P=u.createContext(null);P.displayName="ProjectPropertiesContext";class I extends u.Component{constructor(){super(...arguments),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(e,l){var n,c;process.env.NODE_ENV!=="production"&&console.error("[fragments] Area error:",e,l.componentStack),(c=(n=this.props).onError)==null||c.call(n,e)}render(){return this.state.hasError?null:this.props.children}}const L=({fragmentId:r,props:e,globalCssUrl:l=null,projectProperties:n=S})=>{const c=w(r),a=N().$fragments,d=a==null?void 0:a.getRef(r),o=a==null?void 0:a.getScopeClass(r),f=e??{};return s.jsxs(s.Fragment,{children:[l!=null&&s.jsx(C,{href:l,fragmentKey:m.AREA_GLOBAL_CSS_KEY}),d&&s.jsx(C,{href:d.cssUrl,fragmentKey:m.fragmentCssKey(r)}),(d==null?void 0:d.fontsUrl)!=null&&s.jsx(C,{href:d.fontsUrl,fragmentKey:m.fragmentFontsKey(r)}),s.jsx(P.Provider,{value:n,children:s.jsx(y.Provider,{value:null,children:s.jsx("div",{className:o,style:{display:"contents"},children:s.jsx(c,{...f})})})})]})},K=({onError:r,...e})=>s.jsx(u.Suspense,{fallback:null,children:s.jsx(I,{onError:r,children:s.jsx(L,{...e})})}),q=({areaCode:r,options:e})=>{if((e==null?void 0:e.pause)===!0)return null;const l=T(r,{timeout:e==null?void 0:e.timeout,ssr:e==null?void 0:e.ssr});if(l===F)return null;const{rootFragmentId:n,props:c,projectProperties:i,globalCssUrl:a}=l,d=typeof(e==null?void 0:e.props)=="function"?e.props(c):e!=null&&e.props?{...c,...e.props}:c;return s.jsx(L,{fragmentId:n,props:d,globalCssUrl:a,projectProperties:i})},G=({areaCode:r,options:e})=>s.jsx(u.Suspense,{fallback:(e==null?void 0:e.fallback)??null,children:s.jsx(I,{onError:e==null?void 0:e.onError,children:s.jsx(q,{areaCode:r,options:e})})});function R({client:r,children:e}){const l=u.useMemo(()=>({read:n=>r.$fragments.read(n)}),[r]);return s.jsx(M,{value:r,children:s.jsx(j,{value:l,children:e})})}function Y(r){return(u.useContext(P)??S)[r]}function J(r){return m.createFragmentsClient({...r,inject:A})}Object.defineProperty(t,"FragmentsApiVersionError",{enumerable:!0,get:()=>m.FragmentsApiVersionError}),Object.defineProperty(t,"FragmentsAreaError",{enumerable:!0,get:()=>m.FragmentsAreaError}),Object.defineProperty(t,"FragmentsLoadError",{enumerable:!0,get:()=>m.FragmentsLoadError}),t.AREA_UNRESOLVED=F,t.Area=G,t.FragmentRenderer=K,t.FragmentStylesheet=C,t.FragmentsClientContext=g,t.FragmentsClientProvider=M,t.FragmentsProvider=R,t.InstanceIdContext=y,t.ModulesMapContext=E,t.ModulesMapProvider=j,t.ProjectPropertiesContext=P,t.createBrowserClient=J,t.defaultProjectProperties=S,t.injectStylesheet=A,t.useArea=T,t.useCssChunk=V,t.useFragmentComponent=w,t.useFragmentMetrics=k,t.useFragmentStylesheet=b,t.useProjectProperty=Y,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fragmentsx/runtime-react",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0-dev-20260901074721",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@graph-state/core": "^0.13.3",
|
|
35
|
-
"@fragmentsx/fragments-client": "0.
|
|
35
|
+
"@fragmentsx/fragments-client": "0.2.0-dev-20260901074721"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@testing-library/jest-dom": "^6.6.3",
|