@persona/layout-editor-remote-module 0.5.3 → 0.6.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 +9 -14
- package/dist/bridge-frame.mjs +1 -1
- package/dist/bridge-host.mjs +1 -1
- package/dist/{define-D0nvEX_B.d.mts → define-CUJkJjCk.d.mts} +16 -59
- package/dist/index.d.mts +5 -8
- package/dist/index.mjs +1 -1
- package/dist/{protocol-BgGlnuFn.mjs → protocol-BQsn3N3N.mjs} +3 -8
- package/dist/{schema-Bc3ZwB8y.mjs → schema-DoUyVRjU.mjs} +4 -9
- package/dist/vite-plugin.d.mts +3 -19
- package/dist/vite-plugin.mjs +41 -294
- package/package.json +2 -5
- package/skills/getting-started/SKILL.md +15 -11
package/README.md
CHANGED
|
@@ -10,11 +10,7 @@ without editing the host.
|
|
|
10
10
|
yarn add @persona/layout-editor-remote-module
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
`vite
|
|
14
|
-
directly, and declaring it as a peer means it resolves to the same copy `vite` already pulls in
|
|
15
|
-
rather than a second version. With Yarn's `node-modules` linker the hoisted transitive copy usually
|
|
16
|
-
satisfies it; add `esbuild` to your own `devDependencies` if your package manager warns about an
|
|
17
|
-
unmet peer.
|
|
13
|
+
`vite` and `zod` are peer dependencies.
|
|
18
14
|
|
|
19
15
|
The optional `react` and `react-dom` peer dependencies are required only when using the React bridge
|
|
20
16
|
components.
|
|
@@ -42,9 +38,8 @@ export default defineConfig({
|
|
|
42
38
|
allowAddingToLayout: true,
|
|
43
39
|
layouts: ['case'],
|
|
44
40
|
defaultSize: { case: { width: 4, height: 20 } },
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
render: { entry: './src/layout-modules/risk-summary/render.tsx' },
|
|
41
|
+
// The host loads this HTML page in an iframe.
|
|
42
|
+
render: { entry: './src/layout-modules/risk-summary/index.html' },
|
|
48
43
|
}),
|
|
49
44
|
],
|
|
50
45
|
}),
|
|
@@ -55,9 +50,9 @@ export default defineConfig({
|
|
|
55
50
|
An empty or omitted `layouts` makes the module available in every layout. When `layouts` lists
|
|
56
51
|
specific layouts, `defaultSize` must provide an entry for each one.
|
|
57
52
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
Entries must be `.html` documents, which the host loads in an iframe. Non-HTML entries fail during
|
|
54
|
+
Vite config resolution. See `LayoutModuleEntry`'s doc comment in `src/define.ts` for the full
|
|
55
|
+
contract, including the optional `path` field for a client-routed SPA.
|
|
61
56
|
|
|
62
57
|
## Communicating with the host
|
|
63
58
|
|
|
@@ -141,9 +136,9 @@ The bridge's security model is intentionally narrow:
|
|
|
141
136
|
`init` until the frame answers.
|
|
142
137
|
- Messages use a versioned protocol that stays compatible with frames built against older releases
|
|
143
138
|
(from the release after 0.5.1): receivers ignore message types they don't know and strip keys they
|
|
144
|
-
don't know, and fields are never removed, renamed or narrowed. `context.layoutType`
|
|
145
|
-
|
|
146
|
-
|
|
139
|
+
don't know, and fields are never removed, renamed or narrowed. `context.layoutType` can be a
|
|
140
|
+
layout type added later, so handle values you don't know. Credentials remain in the iframe URL and
|
|
141
|
+
are never handled through bridge messages.
|
|
147
142
|
- `extraParams` rejects reserved names and credential-like keys.
|
|
148
143
|
|
|
149
144
|
### Consuming a manifest
|
package/dist/bridge-frame.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as resolveAllowedHostOrigins, n as SOURCE, o as candidateParentOrigin, r as parseHostToModuleMessage, s as personaDashboardHostOrigins, t as PROTOCOL_VERSION } from "./protocol-
|
|
1
|
+
import { c as resolveAllowedHostOrigins, n as SOURCE, o as candidateParentOrigin, r as parseHostToModuleMessage, s as personaDashboardHostOrigins, t as PROTOCOL_VERSION } from "./protocol-BQsn3N3N.mjs";
|
|
2
2
|
import { Component, useEffect, useRef, useState } from "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
|
package/dist/bridge-host.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as assertNotSelfOrigin, i as parseModuleToHostMessage, n as SOURCE, t as PROTOCOL_VERSION } from "./protocol-
|
|
1
|
+
import { a as assertNotSelfOrigin, i as parseModuleToHostMessage, n as SOURCE, t as PROTOCOL_VERSION } from "./protocol-BQsn3N3N.mjs";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
|
|
@@ -19,15 +19,11 @@ declare const moduleBadgeSchema: z.ZodEnum<{
|
|
|
19
19
|
new: "new";
|
|
20
20
|
}>;
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* iframes it instead) are mutually exclusive — a delivery is one or the
|
|
24
|
-
* other, never both, never neither.
|
|
22
|
+
* The module's main view is a page URL that the host iframes.
|
|
25
23
|
*/
|
|
26
|
-
declare const remoteDeliverySchema: z.
|
|
27
|
-
code: z.ZodString;
|
|
28
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
24
|
+
declare const remoteDeliverySchema: z.ZodObject<{
|
|
29
25
|
document: z.ZodString;
|
|
30
|
-
}, z.core.$strip
|
|
26
|
+
}, z.core.$strip>;
|
|
31
27
|
declare const remoteModuleRegistrationManifestV1Schema: z.ZodObject<{
|
|
32
28
|
allowAddingToLayout: z.ZodBoolean;
|
|
33
29
|
allowDynamicHeight: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -64,12 +60,9 @@ declare const remoteModuleRegistrationManifestV1Schema: z.ZodObject<{
|
|
|
64
60
|
}>>>;
|
|
65
61
|
manifestVersion: z.ZodLiteral<"v1">;
|
|
66
62
|
remote: z.ZodObject<{
|
|
67
|
-
|
|
68
|
-
render: z.ZodUnion<readonly [z.ZodObject<{
|
|
69
|
-
code: z.ZodString;
|
|
70
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
63
|
+
render: z.ZodObject<{
|
|
71
64
|
document: z.ZodString;
|
|
72
|
-
}, z.core.$strip
|
|
65
|
+
}, z.core.$strip>;
|
|
73
66
|
}, z.core.$strip>;
|
|
74
67
|
uiConfigSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
75
68
|
}, z.core.$strip>;
|
|
@@ -89,36 +82,8 @@ type LayoutType = z.infer<typeof layoutTypeSchema>;
|
|
|
89
82
|
type ModuleBadge = z.infer<typeof moduleBadgeSchema>;
|
|
90
83
|
/** Mirrors the host's module-library categories. */
|
|
91
84
|
type ModuleCategory = z.infer<typeof moduleCategorySchema>;
|
|
92
|
-
/**
|
|
93
|
-
* Options passed to a remote module's `defaultConfig` export. Narrower than the
|
|
94
|
-
* host's default-config options; host-internal flags are not exposed to remote
|
|
95
|
-
* modules.
|
|
96
|
-
*/
|
|
97
|
-
interface RemoteModuleDefaultConfigOptions {
|
|
98
|
-
/** Can be a layout type added after the module's release; handle values you don't know. */
|
|
99
|
-
layoutType: LayoutType | (string & {});
|
|
100
|
-
position?: {
|
|
101
|
-
x: number;
|
|
102
|
-
y: number;
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
/** See `RemoteModuleRegistrationManifestV1['remote']['render']`. */
|
|
85
|
+
/** A page URL loaded by the host in an iframe. */
|
|
106
86
|
type RemoteModuleDelivery = z.infer<typeof remoteDeliverySchema>;
|
|
107
|
-
/**
|
|
108
|
-
* Conventional named exports a `{ code }` delivery's module may provide. All
|
|
109
|
-
* optional and feature-detected by name — see the "one URL, conventional
|
|
110
|
-
* named exports" design decision. Rendering/sandboxing `render` is out of
|
|
111
|
-
* scope for this package; only `defaultConfig` has a concrete consumer here
|
|
112
|
-
* (the host's default-config loader). Not applicable to a `document`
|
|
113
|
-
* delivery — nothing is ever imported for those, so none of these exports are
|
|
114
|
-
* reachable.
|
|
115
|
-
*/
|
|
116
|
-
interface RemoteModuleExports {
|
|
117
|
-
defaultConfig?: (opts: RemoteModuleDefaultConfigOptions) => {
|
|
118
|
-
config: Json;
|
|
119
|
-
};
|
|
120
|
-
render?: unknown;
|
|
121
|
-
}
|
|
122
87
|
/**
|
|
123
88
|
* Wire format for a remotely-defined layout module. Must stay
|
|
124
89
|
* JSON-serializable: no functions, no ReactNode, no zod.
|
|
@@ -138,9 +103,8 @@ type JsonValue = Json;
|
|
|
138
103
|
* it carries a source/page path (`render`, resolved relative to the Vite root)
|
|
139
104
|
* and a live zod schema (`configSchema`), which
|
|
140
105
|
* the Vite plugin (./vite.ts) resolves into a `RemoteModuleRegistrationManifestV1`
|
|
141
|
-
* at build time:
|
|
142
|
-
*
|
|
143
|
-
* becomes JSON Schema via `z.toJSONSchema()`.
|
|
106
|
+
* at build time: an html entry becomes its (possibly `path`-overridden)
|
|
107
|
+
* served URL, and `configSchema` becomes JSON Schema via `z.toJSONSchema()`.
|
|
144
108
|
*/
|
|
145
109
|
interface LayoutModuleDefinitionInput {
|
|
146
110
|
/**
|
|
@@ -189,24 +153,17 @@ interface LayoutModuleDefinitionInput {
|
|
|
189
153
|
* definition file anymore; modules are configured directly wherever
|
|
190
154
|
* `personaLayoutModules()` is called (typically vite.config.ts).
|
|
191
155
|
*
|
|
192
|
-
* `entry`
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* entries (e.g. its main `index.html`) and doing nothing extra if so —
|
|
197
|
-
* only injecting it into the build when it isn't already there.
|
|
198
|
-
* - anything else (`.ts`/`.tsx`/`.js`/`.jsx`) — a `code` delivery: built
|
|
199
|
-
* into a chunk and executed directly in the host's own React tree.
|
|
200
|
-
* Exports are feature-detected by conventional name — see
|
|
201
|
-
* `RemoteModuleExports` in types.ts.
|
|
156
|
+
* `entry` must point to an `.html` document that the host iframes. The plugin
|
|
157
|
+
* ensures Vite builds/serves the file, detecting whether it's already one of
|
|
158
|
+
* the app's own build entries (e.g. its main `index.html`) and injecting it
|
|
159
|
+
* into the build only when it isn't already there.
|
|
202
160
|
*
|
|
203
|
-
* `path` is
|
|
204
|
-
*
|
|
161
|
+
* `path` is for a client-routed SPA whose `index.html` already renders the
|
|
162
|
+
* whole app: rather than iframing
|
|
205
163
|
* the SPA's root, this names the LOGICAL route its own router should land on
|
|
206
164
|
* (e.g. `/embed/runs/1`) — the final `document` URL IS that path, not the
|
|
207
165
|
* html file's physical output path, since a static/SPA-fallback server
|
|
208
|
-
* serves the same `index.html` either way.
|
|
209
|
-
* non-`.html` entry.
|
|
166
|
+
* serves the same `index.html` either way.
|
|
210
167
|
*/
|
|
211
168
|
interface LayoutModuleEntry {
|
|
212
169
|
entry: string;
|
|
@@ -221,4 +178,4 @@ interface LayoutModuleEntry {
|
|
|
221
178
|
*/
|
|
222
179
|
declare function defineLayoutModule(input: LayoutModuleDefinitionInput): LayoutModuleDefinitionInput;
|
|
223
180
|
//#endregion
|
|
224
|
-
export { JsonSchemaObject as a, ModuleCategory as c,
|
|
181
|
+
export { JsonSchemaObject as a, ModuleCategory as c, RemoteModuleRegistrationManifestV1Schema as d, remoteModuleRegistrationManifestV1Schema as f, Json as i, RemoteModuleDelivery as l, LayoutModuleEntry as n, LayoutType as o, defineLayoutModule as r, ModuleBadge as s, LayoutModuleDefinitionInput as t, RemoteModuleRegistrationManifestV1 as u };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as JsonSchemaObject, c as ModuleCategory, d as
|
|
1
|
+
import { a as JsonSchemaObject, c as ModuleCategory, d as RemoteModuleRegistrationManifestV1Schema, f as remoteModuleRegistrationManifestV1Schema, i as Json, l as RemoteModuleDelivery, n as LayoutModuleEntry, o as LayoutType, r as defineLayoutModule, s as ModuleBadge, t as LayoutModuleDefinitionInput, u as RemoteModuleRegistrationManifestV1 } from "./define-CUJkJjCk.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
//#region src/manifest-path.d.ts
|
|
@@ -10,7 +10,7 @@ import { z } from "zod";
|
|
|
10
10
|
* one place that actually uses it) so it can also be re-exported from the
|
|
11
11
|
* runtime-safe `.` entry — a consumer wiring up something like CORS for the
|
|
12
12
|
* manifest route in its own server, outside of vite.config.ts, needs this
|
|
13
|
-
* default without pulling in
|
|
13
|
+
* default without pulling in Vite.
|
|
14
14
|
*/
|
|
15
15
|
declare const DEFAULT_MANIFEST_PATH = "/_layout_modules_manifest.json";
|
|
16
16
|
//#endregion
|
|
@@ -65,12 +65,9 @@ declare const remoteModuleManifestDocumentSchema: z.ZodObject<{
|
|
|
65
65
|
}>>>;
|
|
66
66
|
manifestVersion: z.ZodLiteral<"v1">;
|
|
67
67
|
remote: z.ZodObject<{
|
|
68
|
-
|
|
69
|
-
render: z.ZodUnion<readonly [z.ZodObject<{
|
|
70
|
-
code: z.ZodString;
|
|
71
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
68
|
+
render: z.ZodObject<{
|
|
72
69
|
document: z.ZodString;
|
|
73
|
-
}, z.core.$strip
|
|
70
|
+
}, z.core.$strip>;
|
|
74
71
|
}, z.core.$strip>;
|
|
75
72
|
uiConfigSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
76
73
|
}, z.core.$strip>>;
|
|
@@ -95,4 +92,4 @@ declare function parseRemoteModuleManifest(json: unknown): RemoteModuleManifestD
|
|
|
95
92
|
/** Non-throwing variant of `parseRemoteModuleManifest`. */
|
|
96
93
|
declare function safeParseRemoteModuleManifest(json: unknown): RemoteModuleManifestParseResult;
|
|
97
94
|
//#endregion
|
|
98
|
-
export { DEFAULT_MANIFEST_PATH, type Json, type JsonSchemaObject, type LayoutModuleDefinitionInput, type LayoutModuleEntry, type LayoutType, type ModuleBadge, type ModuleCategory, type
|
|
95
|
+
export { DEFAULT_MANIFEST_PATH, type Json, type JsonSchemaObject, type LayoutModuleDefinitionInput, type LayoutModuleEntry, type LayoutType, type ModuleBadge, type ModuleCategory, type RemoteModuleDelivery, type RemoteModuleManifestDocument, RemoteModuleManifestParseError, type RemoteModuleManifestParseResult, type RemoteModuleRegistrationManifestV1, type RemoteModuleRegistrationManifestV1Schema, defineLayoutModule, parseRemoteModuleManifest, remoteModuleManifestDocumentSchema, remoteModuleRegistrationManifestV1Schema, safeParseRemoteModuleManifest };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as DEFAULT_MANIFEST_PATH, r as defineLayoutModule, t as remoteModuleRegistrationManifestV1Schema } from "./schema-
|
|
1
|
+
import { n as DEFAULT_MANIFEST_PATH, r as defineLayoutModule, t as remoteModuleRegistrationManifestV1Schema } from "./schema-DoUyVRjU.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
//#region src/parse.ts
|
|
@@ -63,15 +63,10 @@ const gridSizeSchema = z.object({
|
|
|
63
63
|
width: z.number().int().min(1).max(MAX_GRID_COLUMNS)
|
|
64
64
|
});
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
* iframes it instead) are mutually exclusive — a delivery is one or the
|
|
68
|
-
* other, never both, never neither.
|
|
66
|
+
* The module's main view is a page URL that the host iframes.
|
|
69
67
|
*/
|
|
70
|
-
const remoteDeliverySchema = z.
|
|
71
|
-
const remoteEntrySchema = z.object({
|
|
72
|
-
css: z.array(z.string()).optional(),
|
|
73
|
-
render: remoteDeliverySchema
|
|
74
|
-
});
|
|
68
|
+
const remoteDeliverySchema = z.object({ document: z.string().min(1) });
|
|
69
|
+
const remoteEntrySchema = z.object({ render: remoteDeliverySchema });
|
|
75
70
|
/**
|
|
76
71
|
* Loose check shared by `configSchema` and `uiConfigSchema` — this validates
|
|
77
72
|
* the manifest envelope, not the arbitrary object either one carries. Both
|
|
@@ -21,7 +21,7 @@ function defineLayoutModule(input) {
|
|
|
21
21
|
* one place that actually uses it) so it can also be re-exported from the
|
|
22
22
|
* runtime-safe `.` entry — a consumer wiring up something like CORS for the
|
|
23
23
|
* manifest route in its own server, outside of vite.config.ts, needs this
|
|
24
|
-
* default without pulling in
|
|
24
|
+
* default without pulling in Vite.
|
|
25
25
|
*/
|
|
26
26
|
const DEFAULT_MANIFEST_PATH = "/_layout_modules_manifest.json";
|
|
27
27
|
|
|
@@ -61,15 +61,10 @@ const gridSizeSchema = z.object({
|
|
|
61
61
|
width: z.number().int().min(1).max(MAX_GRID_COLUMNS)
|
|
62
62
|
});
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
65
|
-
* iframes it instead) are mutually exclusive — a delivery is one or the
|
|
66
|
-
* other, never both, never neither.
|
|
64
|
+
* The module's main view is a page URL that the host iframes.
|
|
67
65
|
*/
|
|
68
|
-
const remoteDeliverySchema = z.
|
|
69
|
-
const remoteEntrySchema = z.object({
|
|
70
|
-
css: z.array(z.string()).optional(),
|
|
71
|
-
render: remoteDeliverySchema
|
|
72
|
-
});
|
|
66
|
+
const remoteDeliverySchema = z.object({ document: z.string().min(1) });
|
|
67
|
+
const remoteEntrySchema = z.object({ render: remoteDeliverySchema });
|
|
73
68
|
/**
|
|
74
69
|
* Loose check shared by `configSchema` and `uiConfigSchema` — this validates
|
|
75
70
|
* the manifest envelope, not the arbitrary object either one carries. Both
|
package/dist/vite-plugin.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as LayoutModuleEntry, r as defineLayoutModule, t as LayoutModuleDefinitionInput } from "./define-
|
|
1
|
+
import { n as LayoutModuleEntry, r as defineLayoutModule, t as LayoutModuleDefinitionInput } from "./define-CUJkJjCk.mjs";
|
|
2
2
|
import { Plugin } from "vite";
|
|
3
3
|
|
|
4
4
|
//#region src/vite-plugin.d.ts
|
|
@@ -12,28 +12,12 @@ interface PersonaLayoutModulesOptions {
|
|
|
12
12
|
* either form resolves to the same path.
|
|
13
13
|
*/
|
|
14
14
|
manifestPath?: string;
|
|
15
|
-
/**
|
|
16
|
-
* Plugins the production build of `code`-delivered layout-module entries
|
|
17
|
-
* needs (e.g. `react()`, `tailwindcss()`) — called once. These entries are
|
|
18
|
-
* built via a SEPARATE, isolated `vite.build()` call (see the
|
|
19
|
-
* `closeBundle` hook), deliberately not part of the app's own build graph
|
|
20
|
-
* — see its doc comment for why — so the app's own plugin list isn't
|
|
21
|
-
* inherited automatically. Omit if your modules only need plain TS/JSX
|
|
22
|
-
* (Vite's built-in esbuild transform already handles that with no plugin).
|
|
23
|
-
* `document` (`.html`) entries never need this — they run through the
|
|
24
|
-
* app's own build graph and its own configured plugins directly.
|
|
25
|
-
*/
|
|
26
|
-
modulePlugins?: () => Plugin[];
|
|
27
15
|
/** Every module this app registers — passed directly, not discovered. */
|
|
28
16
|
modules: LayoutModuleDefinitionInput[];
|
|
29
17
|
}
|
|
30
18
|
/**
|
|
31
|
-
* Vite plugin that
|
|
32
|
-
*
|
|
33
|
-
* wires every `code`-delivered entry into the build as an addressable ESM
|
|
34
|
-
* chunk, ensures every `document`-delivered (`.html`) entry is actually
|
|
35
|
-
* built/served, and emits a `RemoteModuleRegistrationManifestV1[]` document
|
|
36
|
-
* the host can consume.
|
|
19
|
+
* Vite plugin that ensures declared module documents are built/served and
|
|
20
|
+
* emits a `RemoteModuleRegistrationManifestV1[]` document for the host.
|
|
37
21
|
*
|
|
38
22
|
* Modules are configured directly here, not discovered by globbing the
|
|
39
23
|
* source tree — a module IS whatever's in `options.modules`, full stop.
|
package/dist/vite-plugin.mjs
CHANGED
|
@@ -1,129 +1,13 @@
|
|
|
1
|
-
import { n as DEFAULT_MANIFEST_PATH, r as defineLayoutModule, t as remoteModuleRegistrationManifestV1Schema } from "./schema-
|
|
1
|
+
import { n as DEFAULT_MANIFEST_PATH, r as defineLayoutModule, t as remoteModuleRegistrationManifestV1Schema } from "./schema-DoUyVRjU.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { build } from "esbuild";
|
|
4
3
|
import { existsSync } from "node:fs";
|
|
5
|
-
import { mkdir,
|
|
4
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
5
|
import path from "node:path";
|
|
7
|
-
import { build as build$1 } from "vite";
|
|
8
|
-
import { tmpdir } from "node:os";
|
|
9
6
|
|
|
10
|
-
//#region src/remote-shims.ts
|
|
11
|
-
/**
|
|
12
|
-
* Specifiers a remote module bundle must NOT carry its own copy of — each is
|
|
13
|
-
* redirected (via esbuild/Vite `alias`) to a tiny CJS shim that reads the
|
|
14
|
-
* host's own instance off `globalThis.__PERSONA_REMOTE_MODULE_DEPS__` instead
|
|
15
|
-
* of bundling the real package.
|
|
16
|
-
*
|
|
17
|
-
* Why this exists: hooks require the calling code's React module instance to
|
|
18
|
-
* be the exact one actively rendering the tree. A remote module built with
|
|
19
|
-
* its own bundled `react` (the default, before this) creates a SECOND,
|
|
20
|
-
* unrelated React instance — any hook call from code using that instance
|
|
21
|
-
* (including styled-components' own `useContext` inside `ThemeProvider`, and
|
|
22
|
-
* anything in `@persona/design-system`, which itself just imports `react`)
|
|
23
|
-
* reads a dispatcher that was never set, producing exactly
|
|
24
|
-
* "Cannot read properties of null (reading 'useContext')". Sharing the
|
|
25
|
-
* instance via a runtime global — rather than bundling it — fixes this for
|
|
26
|
-
* every remote module, not just one that happens to avoid hooks.
|
|
27
|
-
*
|
|
28
|
-
* `styled-components` needs the same treatment for a subtler reason: even
|
|
29
|
-
* with a shared React, a SEPARATE `styled-components` package instance would
|
|
30
|
-
* call `React.createContext()` itself and produce a DIFFERENT Context object
|
|
31
|
-
* than the host's — a `<ThemeProvider>` from one instance is invisible to
|
|
32
|
-
* `styled.div` definitions from the other, no matter how the React inside
|
|
33
|
-
* them is shared.
|
|
34
|
-
*/
|
|
35
|
-
/**
|
|
36
|
-
* Order matters: Vite's alias resolution (via its wrapped
|
|
37
|
-
* `@rollup/plugin-alias`) treats a string `find` as a PREFIX match, not just
|
|
38
|
-
* exact — `importee === find || importee.startsWith(find + '/')` — and takes
|
|
39
|
-
* the FIRST entry (in object key / array order) that matches. Since
|
|
40
|
-
* `'react/jsx-runtime'.startsWith('react/')` is true, listing `'react'`
|
|
41
|
-
* before `'react/jsx-runtime'` would make every `react/jsx-runtime` import
|
|
42
|
-
* resolve against the 'react' shim's path (e.g.
|
|
43
|
-
* `.../react.cjs/jsx-runtime`, ENOTDIR) instead of its own shim. The more
|
|
44
|
-
* specific specifier must come first.
|
|
45
|
-
*/
|
|
46
|
-
const SHIMMABLE_SPECIFIERS = [
|
|
47
|
-
"react/jsx-runtime",
|
|
48
|
-
"react/jsx-dev-runtime",
|
|
49
|
-
"react",
|
|
50
|
-
"react-dom",
|
|
51
|
-
"styled-components"
|
|
52
|
-
];
|
|
53
|
-
const HOST_DEPS_GLOBAL = "__PERSONA_REMOTE_MODULE_DEPS__";
|
|
54
|
-
/**
|
|
55
|
-
* `module.exports = ...` (not a static ESM re-export) deliberately — an ESM
|
|
56
|
-
* `export` list has to be known at author time, but the shimmed value's
|
|
57
|
-
* actual shape lives on a runtime global. esbuild's own CJS interop
|
|
58
|
-
* (`__toESM`) forwards named-import access to a CJS module's properties
|
|
59
|
-
* dynamically, at RUNTIME, regardless of what the bundler could see
|
|
60
|
-
* statically — the same mechanism that already makes `import { useState }
|
|
61
|
-
* from 'react'` work today, since React itself ships as CJS.
|
|
62
|
-
*
|
|
63
|
-
* Consuming apps with `esModuleInterop` off in tsconfig (allowed by TS —
|
|
64
|
-
* `allowSyntheticDefaultImports` permits the import *syntax* without it) get
|
|
65
|
-
* esbuild's "Node mode" CJS interop for a DEFAULT import of an aliased CJS
|
|
66
|
-
* file: `target.default = mod` (the whole `module.exports`), unconditionally
|
|
67
|
-
* — `mod.__esModule` is NOT consulted in that mode, so it can't be used to
|
|
68
|
-
* steer this. That's a correct model for a module whose "the module" and "the
|
|
69
|
-
* default export" are the same thing (true for `react`/`react-dom`, which
|
|
70
|
-
* have no own `.default`). It's wrong for `styled-components`: its
|
|
71
|
-
* module.exports has BOTH a whole-module shape (ThemeProvider, css, ... as
|
|
72
|
-
* plain properties) AND a separate own `.default` (the callable `styled` tag
|
|
73
|
-
* function) — Node mode's `target.default = mod` picks the whole-module
|
|
74
|
-
* shape, so `styled.div`/`.attrs(...)` throws on `undefined`.
|
|
75
|
-
*
|
|
76
|
-
* The fix: make OUR module.exports itself resolve the ambiguity — if the
|
|
77
|
-
* host's real module has its own `.default` distinct from itself, export
|
|
78
|
-
* THAT (with the rest of the module's named properties copied onto it, so
|
|
79
|
-
* `styled.ThemeProvider`-style access — not used by styled-components itself,
|
|
80
|
-
* but keeps this general — and named imports both still resolve). Mutating
|
|
81
|
-
* the host's real default value (rather than building a fresh wrapper) is
|
|
82
|
-
* deliberate: styled-components' internals sometimes compare a component
|
|
83
|
-
* against the exact `styled` function reference (e.g. `isStyledComponent`);
|
|
84
|
-
* a copy would break that. Ordinary functions/objects aren't frozen, so this
|
|
85
|
-
* is safe for every specifier actually on this list.
|
|
86
|
-
*/
|
|
87
|
-
function shimModuleSource(specifier) {
|
|
88
|
-
const globalAccess = `globalThis.${HOST_DEPS_GLOBAL} && globalThis.${HOST_DEPS_GLOBAL}[${JSON.stringify(specifier)}]`;
|
|
89
|
-
const missingMessage = `[persona-layout-modules] the host did not expose "${specifier}" via globalThis.${HOST_DEPS_GLOBAL} before this module loaded`;
|
|
90
|
-
return [
|
|
91
|
-
`var __persona_dep = ${globalAccess} || (() => { throw new Error(${JSON.stringify(missingMessage)}); })();`,
|
|
92
|
-
`var __persona_base = (__persona_dep && 'default' in __persona_dep && __persona_dep.default !== __persona_dep) ? __persona_dep.default : __persona_dep;`,
|
|
93
|
-
`module.exports = Object.assign(__persona_base, __persona_dep, { __esModule: true, default: __persona_base });`,
|
|
94
|
-
""
|
|
95
|
-
].join("\n");
|
|
96
|
-
}
|
|
97
|
-
let cachedShimPaths;
|
|
98
|
-
/**
|
|
99
|
-
* Writes one `.cjs` shim file per specifier to a temp dir (once per process
|
|
100
|
-
* — the content is static, not per-request) and returns a specifier -> path
|
|
101
|
-
* map suitable for esbuild's/Vite's `alias` option. `.cjs` is deliberate: it
|
|
102
|
-
* forces CommonJS treatment regardless of any ambient `package.json#type`.
|
|
103
|
-
*/
|
|
104
|
-
function ensureRemoteShimFiles() {
|
|
105
|
-
if (!cachedShimPaths) cachedShimPaths = (async () => {
|
|
106
|
-
const dir = await mkdtemp(path.join(tmpdir(), "persona-layout-module-shims-"));
|
|
107
|
-
const entries = await Promise.all(SHIMMABLE_SPECIFIERS.map(async (specifier) => {
|
|
108
|
-
const fileName = `${specifier.replace(/[\\/]/g, "_")}.cjs`;
|
|
109
|
-
const filePath = path.join(dir, fileName);
|
|
110
|
-
await writeFile(filePath, shimModuleSource(specifier), "utf8");
|
|
111
|
-
return [specifier, filePath];
|
|
112
|
-
}));
|
|
113
|
-
return Object.fromEntries(entries);
|
|
114
|
-
})();
|
|
115
|
-
return cachedShimPaths;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
//#endregion
|
|
119
7
|
//#region src/vite-plugin.ts
|
|
120
8
|
/**
|
|
121
|
-
* Vite plugin that
|
|
122
|
-
*
|
|
123
|
-
* wires every `code`-delivered entry into the build as an addressable ESM
|
|
124
|
-
* chunk, ensures every `document`-delivered (`.html`) entry is actually
|
|
125
|
-
* built/served, and emits a `RemoteModuleRegistrationManifestV1[]` document
|
|
126
|
-
* the host can consume.
|
|
9
|
+
* Vite plugin that ensures declared module documents are built/served and
|
|
10
|
+
* emits a `RemoteModuleRegistrationManifestV1[]` document for the host.
|
|
127
11
|
*
|
|
128
12
|
* Modules are configured directly here, not discovered by globbing the
|
|
129
13
|
* source tree — a module IS whatever's in `options.modules`, full stop.
|
|
@@ -139,63 +23,27 @@ function personaLayoutModules(options) {
|
|
|
139
23
|
if (definitions.length === 0 || envConfig?.command !== "build") return;
|
|
140
24
|
if (envConfig.consumer !== "client") return;
|
|
141
25
|
const outDir = path.isAbsolute(envConfig.build.outDir) ? envConfig.build.outDir : path.resolve(root, envConfig.build.outDir);
|
|
142
|
-
const input = {};
|
|
143
|
-
for (const definition of definitions) if (definition.render.kind === "code") input[entryNameFor(definition)] = definition.render.absolutePath;
|
|
144
|
-
let emitted = [];
|
|
145
|
-
if (Object.keys(input).length > 0) {
|
|
146
|
-
const shimAlias = await ensureRemoteShimFiles();
|
|
147
|
-
const result = await build$1({
|
|
148
|
-
base: envConfig.base,
|
|
149
|
-
build: {
|
|
150
|
-
emptyOutDir: false,
|
|
151
|
-
outDir,
|
|
152
|
-
rollupOptions: {
|
|
153
|
-
input,
|
|
154
|
-
preserveEntrySignatures: "exports-only"
|
|
155
|
-
},
|
|
156
|
-
write: true
|
|
157
|
-
},
|
|
158
|
-
configFile: false,
|
|
159
|
-
esbuild: { jsx: "automatic" },
|
|
160
|
-
logLevel: "warn",
|
|
161
|
-
mode: "production",
|
|
162
|
-
plugins: options.modulePlugins?.() ?? [],
|
|
163
|
-
resolve: { alias: {
|
|
164
|
-
...resolveAliasForEsbuild(envConfig),
|
|
165
|
-
...shimAlias
|
|
166
|
-
} },
|
|
167
|
-
root
|
|
168
|
-
});
|
|
169
|
-
emitted = (Array.isArray(result) ? result : [result]).flatMap((output) => "output" in output ? output.output : []);
|
|
170
|
-
}
|
|
171
26
|
const base = envConfig.base ?? "/";
|
|
172
|
-
const manifests =
|
|
173
|
-
for (const definition of definitions) {
|
|
174
|
-
const render = resolveManifestDelivery(definition, definition.render, emitted, root, base);
|
|
175
|
-
const css = /* @__PURE__ */ new Set();
|
|
176
|
-
if (render.chunk) collectCss(render.chunk, css);
|
|
27
|
+
const manifests = definitions.map((definition) => {
|
|
177
28
|
const manifest = {
|
|
178
29
|
...buildManifestBase(definition),
|
|
179
|
-
remote: {
|
|
180
|
-
css: css.size > 0 ? [...css].map((fileName) => joinUrl(base, fileName)) : void 0,
|
|
181
|
-
render: render.value
|
|
182
|
-
}
|
|
30
|
+
remote: { render: { document: documentUrlFor(root, base, definition.render) } }
|
|
183
31
|
};
|
|
184
|
-
const
|
|
185
|
-
if (!
|
|
186
|
-
|
|
187
|
-
}
|
|
32
|
+
const result = remoteModuleRegistrationManifestV1Schema.safeParse(manifest);
|
|
33
|
+
if (!result.success) throw new Error(`[persona-layout-modules] ${definition.definitionFilePath}: invalid manifest for "${definition.input.key}":\n${z.prettifyError(result.error)}`);
|
|
34
|
+
return manifest;
|
|
35
|
+
});
|
|
188
36
|
assertUniqueKeys(manifests);
|
|
189
37
|
const manifestFilePath = path.join(outDir, manifestPath);
|
|
190
38
|
await mkdir(path.dirname(manifestFilePath), { recursive: true });
|
|
191
39
|
await writeFile(manifestFilePath, JSON.stringify({ modules: manifests }, null, 2), "utf8");
|
|
192
40
|
},
|
|
193
|
-
|
|
41
|
+
config(config) {
|
|
194
42
|
root = config.root ? path.resolve(config.root) : process.cwd();
|
|
195
43
|
definitions = options.modules.map((input) => ({
|
|
196
44
|
definitionFilePath: input.key,
|
|
197
45
|
input,
|
|
198
|
-
render: resolveEntry(root, input.render)
|
|
46
|
+
render: resolveEntry(root, input.key, input.render)
|
|
199
47
|
}));
|
|
200
48
|
},
|
|
201
49
|
configEnvironment(name, envConfig, env) {
|
|
@@ -205,7 +53,7 @@ function personaLayoutModules(options) {
|
|
|
205
53
|
const toInject = {};
|
|
206
54
|
for (const definition of definitions) {
|
|
207
55
|
const delivery = definition.render;
|
|
208
|
-
if (
|
|
56
|
+
if (!existingInputs.has(delivery.absolutePath)) {
|
|
209
57
|
toInject[entryNameFor(definition)] = delivery.absolutePath;
|
|
210
58
|
existingInputs.add(delivery.absolutePath);
|
|
211
59
|
}
|
|
@@ -220,47 +68,28 @@ function personaLayoutModules(options) {
|
|
|
220
68
|
for (const definition of definitions) checkDeliveryExists(definition, definition.render);
|
|
221
69
|
},
|
|
222
70
|
configureServer(server) {
|
|
223
|
-
server.middlewares.use(
|
|
224
|
-
if (req.url
|
|
225
|
-
const base = resolvedConfig.base ?? "/";
|
|
226
|
-
const devManifests = definitions.map((definition$1) => ({
|
|
227
|
-
...buildManifestBase(definition$1),
|
|
228
|
-
remote: { render: devManifestDelivery(root, base, definition$1, definition$1.render) }
|
|
229
|
-
}));
|
|
230
|
-
res.setHeader("Content-Type", "application/json");
|
|
231
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
232
|
-
res.end(JSON.stringify({ modules: devManifests }, null, 2));
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
const bundleTarget = matchDevBundleRequest(req.url, definitions);
|
|
236
|
-
if (!bundleTarget) {
|
|
71
|
+
server.middlewares.use((req, res, next) => {
|
|
72
|
+
if (req.url !== `/${manifestPath}`) {
|
|
237
73
|
next();
|
|
238
74
|
return;
|
|
239
75
|
}
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
server.config.logger.error(`[persona-layout-modules] failed to dev-bundle ${definition.definitionFilePath}: ${String(error)}`);
|
|
249
|
-
res.statusCode = 500;
|
|
250
|
-
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
251
|
-
res.end(`[persona-layout-modules] failed to bundle: ${String(error)}`);
|
|
252
|
-
}
|
|
76
|
+
const base = resolvedConfig.base ?? "/";
|
|
77
|
+
const devManifests = definitions.map((definition) => ({
|
|
78
|
+
...buildManifestBase(definition),
|
|
79
|
+
remote: { render: { document: documentUrlFor(root, base, definition.render) } }
|
|
80
|
+
}));
|
|
81
|
+
res.setHeader("Content-Type", "application/json");
|
|
82
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
83
|
+
res.end(JSON.stringify({ modules: devManifests }, null, 2));
|
|
253
84
|
});
|
|
254
85
|
},
|
|
255
86
|
name: "persona-layout-modules"
|
|
256
87
|
};
|
|
257
88
|
}
|
|
258
|
-
const DEV_BUNDLE_PREFIX = "/@persona-layout-module";
|
|
259
89
|
/**
|
|
260
|
-
* Every absolute path Vite is
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
* Used to decide whether a `document` entry needs injecting at all.
|
|
90
|
+
* Every absolute path Vite is already going to build, per the app's
|
|
91
|
+
* `build.rollupOptions.input` (or the implicit default — Vite treats
|
|
92
|
+
* `<root>/index.html` as the one entry when nothing is configured).
|
|
264
93
|
*/
|
|
265
94
|
function absoluteInputPaths(config, root) {
|
|
266
95
|
const input = config.build?.rollupOptions?.input;
|
|
@@ -296,50 +125,16 @@ function buildManifestBase(definition) {
|
|
|
296
125
|
uiConfigSchema: input.uiConfigSchema
|
|
297
126
|
};
|
|
298
127
|
}
|
|
299
|
-
/**
|
|
300
|
-
* Bundles a single `code` entry (with its dependencies inlined) for dev-mode
|
|
301
|
-
* cross-origin serving. Deliberately re-run on every request rather than
|
|
302
|
-
* cached — these are small files and esbuild is fast enough that freshness
|
|
303
|
-
* on every reload matters more than shaving milliseconds.
|
|
304
|
-
*/
|
|
305
|
-
async function bundleForDev(absolutePath, root, resolvedConfig) {
|
|
306
|
-
const shimAlias = await ensureRemoteShimFiles();
|
|
307
|
-
const [output] = (await build({
|
|
308
|
-
absWorkingDir: root,
|
|
309
|
-
alias: {
|
|
310
|
-
...resolveAliasForEsbuild(resolvedConfig),
|
|
311
|
-
...shimAlias
|
|
312
|
-
},
|
|
313
|
-
bundle: true,
|
|
314
|
-
entryPoints: [absolutePath],
|
|
315
|
-
format: "esm",
|
|
316
|
-
jsx: "automatic",
|
|
317
|
-
sourcemap: "inline",
|
|
318
|
-
write: false
|
|
319
|
-
})).outputFiles;
|
|
320
|
-
if (!output) throw new Error("esbuild produced no output");
|
|
321
|
-
return output.text;
|
|
322
|
-
}
|
|
323
128
|
function checkDeliveryExists(definition, delivery) {
|
|
324
129
|
if (!existsSync(delivery.absolutePath)) throw new Error(`[persona-layout-modules] ${definition.definitionFilePath}: render "${delivery.sourcePath}" does not resolve to a file (${delivery.absolutePath})`);
|
|
325
130
|
}
|
|
326
|
-
function collectCss(chunk, into) {
|
|
327
|
-
for (const fileName of chunk.viteMetadata?.importedCss ?? []) into.add(fileName);
|
|
328
|
-
}
|
|
329
|
-
function devBundleUrl(definition) {
|
|
330
|
-
return `${DEV_BUNDLE_PREFIX}/${entryNameFor(definition)}.js`;
|
|
331
|
-
}
|
|
332
|
-
function devManifestDelivery(root, base, definition, delivery) {
|
|
333
|
-
if (delivery.kind === "document") return { document: documentUrlFor(root, base, delivery) };
|
|
334
|
-
return { code: devBundleUrl(definition) };
|
|
335
|
-
}
|
|
336
131
|
/**
|
|
337
|
-
* A
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
132
|
+
* A document's manifest URL: the author's `path` override when given (a
|
|
133
|
+
* logical SPA route, not a file location — used as-is, never joined with
|
|
134
|
+
* `base`), otherwise the html file's path relative to root (which Vite
|
|
135
|
+
* preserves in its build output for multi-page entries) joined with `base`.
|
|
136
|
+
* Vite's dev server serves files at the same `base`-relative paths, so dev and
|
|
137
|
+
* build share this.
|
|
343
138
|
*/
|
|
344
139
|
function documentUrlFor(root, base, delivery) {
|
|
345
140
|
if (delivery.path) return delivery.path;
|
|
@@ -348,72 +143,24 @@ function documentUrlFor(root, base, delivery) {
|
|
|
348
143
|
function entryNameFor(definition) {
|
|
349
144
|
return `layout-module--${definition.input.key}`;
|
|
350
145
|
}
|
|
351
|
-
function findChunkByFacadeModule(emitted, facadeModuleId) {
|
|
352
|
-
for (const file of emitted) if (file.type === "chunk" && file.facadeModuleId === facadeModuleId) return file;
|
|
353
|
-
}
|
|
354
146
|
function joinUrl(base, fileName) {
|
|
355
147
|
return `${base.endsWith("/") ? base : `${base}/`}${fileName}`.replace(/\/{2,}/g, "/");
|
|
356
148
|
}
|
|
357
|
-
function
|
|
358
|
-
if (
|
|
359
|
-
const name = url.slice(24, -3);
|
|
360
|
-
for (const definition of definitions) if (definition.render.kind === "code" && name === entryNameFor(definition)) return {
|
|
361
|
-
absolutePath: definition.render.absolutePath,
|
|
362
|
-
definition
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Vite normalizes `resolve.alias` (however the user wrote it — object or
|
|
367
|
-
* array) into an array of `{ find, replacement }`. esbuild's own `alias`
|
|
368
|
-
* option only accepts exact string keys (no RegExp, no partial-path
|
|
369
|
-
* matching) — a real limitation this doesn't try to hide, just the common
|
|
370
|
-
* case: a plain string-to-string alias map, which many apps use.
|
|
371
|
-
*/
|
|
372
|
-
function resolveAliasForEsbuild(resolvedConfig) {
|
|
373
|
-
const alias = {};
|
|
374
|
-
for (const entry of resolvedConfig.resolve?.alias ?? []) if (typeof entry.find === "string") alias[entry.find] = entry.replacement;
|
|
375
|
-
return alias;
|
|
376
|
-
}
|
|
377
|
-
/** `.html` → `document`; anything else (`.ts`/`.tsx`/`.js`/`.jsx`) → `code`. */
|
|
378
|
-
function resolveEntry(root, spec) {
|
|
379
|
-
const absolutePath = path.resolve(root, spec.entry);
|
|
380
|
-
if (absolutePath.endsWith(".html")) return {
|
|
381
|
-
absolutePath,
|
|
382
|
-
kind: "document",
|
|
383
|
-
path: spec.path,
|
|
384
|
-
sourcePath: spec.entry
|
|
385
|
-
};
|
|
149
|
+
function resolveEntry(root, key, spec) {
|
|
150
|
+
if (path.extname(spec.entry) !== ".html") throw new Error(`[persona-layout-modules] ${key}: render "${spec.entry}" must be an .html document — only document-delivered modules are supported`);
|
|
386
151
|
return {
|
|
387
|
-
absolutePath,
|
|
388
|
-
|
|
152
|
+
absolutePath: path.resolve(root, spec.entry),
|
|
153
|
+
path: spec.path,
|
|
389
154
|
sourcePath: spec.entry
|
|
390
155
|
};
|
|
391
156
|
}
|
|
392
157
|
/**
|
|
393
|
-
* Resolves the `render` delivery into its final manifest value for the
|
|
394
|
-
* PRODUCTION path. `document` uses `documentUrlFor` and touches nothing else —
|
|
395
|
-
* no chunk lookup, since it was never part of this plugin's own isolated
|
|
396
|
-
* build. `code` becomes `{ code: <hashed chunk URL> }`, throwing if the
|
|
397
|
-
* isolated build didn't emit a chunk for it. Also returns the chunk itself
|
|
398
|
-
* (when there is one) so the caller can collect its CSS without a second
|
|
399
|
-
* lookup.
|
|
400
|
-
*/
|
|
401
|
-
function resolveManifestDelivery(definition, delivery, emitted, root, base) {
|
|
402
|
-
if (delivery.kind === "document") return { value: { document: documentUrlFor(root, base, delivery) } };
|
|
403
|
-
const chunk = findChunkByFacadeModule(emitted, delivery.absolutePath);
|
|
404
|
-
if (!chunk) throw new Error(`[persona-layout-modules] ${definition.definitionFilePath}: could not locate the emitted chunk for render "${delivery.sourcePath}" in the isolated module build.`);
|
|
405
|
-
return {
|
|
406
|
-
chunk,
|
|
407
|
-
value: { code: joinUrl(base, chunk.fileName) }
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
/**
|
|
411
158
|
* Normalizes whatever shape `build.rollupOptions.input` currently has (unset,
|
|
412
|
-
* a string, an array, or already an object) into the object form, so
|
|
413
|
-
* injected
|
|
414
|
-
* dropping
|
|
415
|
-
* `index.html` entry
|
|
416
|
-
*
|
|
159
|
+
* a string, an array, or already an object) into the object form, so an
|
|
160
|
+
* injected document entry can be merged in as an additional key WITHOUT
|
|
161
|
+
* dropping what was already configured — including the implicit default
|
|
162
|
+
* `index.html` entry, which would otherwise silently disappear the moment this
|
|
163
|
+
* plugin assigns an object here.
|
|
417
164
|
*/
|
|
418
165
|
function toInputRecord(input, root) {
|
|
419
166
|
if (input == null) return { index: path.join(root, "index.html") };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@persona/layout-editor-remote-module",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Shared manifest format, Vite plugin, and host/iframe bridge for remotely-defined dashboard layout modules in Persona dashboard apps.",
|
|
5
5
|
"license": "SEE LICENSE",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
"release": "semantic-release"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"esbuild": ">=0.21.0 <1",
|
|
42
41
|
"react": "^17 || ^18 || ^19",
|
|
43
42
|
"react-dom": "^17 || ^18 || ^19",
|
|
44
43
|
"vite": "^6 || ^7 || ^8",
|
|
@@ -68,10 +67,8 @@
|
|
|
68
67
|
"playwright": "1.62.1",
|
|
69
68
|
"react": "^18.3.1",
|
|
70
69
|
"react-dom": "^18.3.1",
|
|
71
|
-
"rollup": "^4.0.0",
|
|
72
70
|
"semantic-release": "^25.0.7",
|
|
73
71
|
"semantic-release-monorepo": "^8.0.2",
|
|
74
|
-
"styled-components": "^5.3.5",
|
|
75
72
|
"tsdown": "^0.18.1",
|
|
76
73
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
77
74
|
"vite": "^7.0.0",
|
|
@@ -95,7 +92,7 @@
|
|
|
95
92
|
"@semantic-release/exec",
|
|
96
93
|
{
|
|
97
94
|
"prepareCmd": "yarn version ${nextRelease.version}",
|
|
98
|
-
"publishCmd": "yarn npm publish --
|
|
95
|
+
"publishCmd": "yarn npm publish --access public"
|
|
99
96
|
}
|
|
100
97
|
],
|
|
101
98
|
"@semantic-release/github"
|
|
@@ -20,7 +20,7 @@ metadata:
|
|
|
20
20
|
|
|
21
21
|
Shared manifest format and Vite plugin for remotely-defined dashboard layout modules — lets Persona
|
|
22
22
|
dashboard apps declare a layout module directly in a typical app's `vite.config.ts`, without editing
|
|
23
|
-
the host. The plugin
|
|
23
|
+
the host. The plugin ensures each module's HTML document is built and emits a
|
|
24
24
|
`{ modules: RemoteModuleRegistrationManifestV1[] }` JSON document the host fetches and validates.
|
|
25
25
|
|
|
26
26
|
This package is publicly available from the npm registry.
|
|
@@ -31,10 +31,8 @@ This package is publicly available from the npm registry.
|
|
|
31
31
|
yarn add @persona/layout-editor-remote-module
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
`vite
|
|
35
|
-
|
|
36
|
-
if your package manager warns about an unmet peer — the plugin's dev-mode bundling calls esbuild
|
|
37
|
-
directly and should resolve to the same copy Vite already pulls in.
|
|
34
|
+
`vite` and `zod` are peer dependencies; `react` / `react-dom` (`^17 || ^18 || ^19`) are optional
|
|
35
|
+
peers needed only by the `bridge-frame` entry.
|
|
38
36
|
|
|
39
37
|
## Setup (vite.config.ts)
|
|
40
38
|
|
|
@@ -82,11 +80,10 @@ not relative to `vite.config.ts`'s own directory when `root` is overridden elsew
|
|
|
82
80
|
|
|
83
81
|
### Document delivery — iframed page
|
|
84
82
|
|
|
85
|
-
A module's `render.entry` must end in `.html`: the host iframes
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
names the logical route the SPA's own router should land on, not the file's build output path:
|
|
83
|
+
A module's `render.entry` must end in `.html`: the host iframes the page. Any other extension fails
|
|
84
|
+
during Vite config resolution with a clear error. Use `path` for a client-routed SPA whose
|
|
85
|
+
`index.html` already renders the whole app — it names the logical route the SPA's own router should
|
|
86
|
+
land on, not the file's build output path:
|
|
90
87
|
|
|
91
88
|
```ts
|
|
92
89
|
defineLayoutModule({
|
|
@@ -449,6 +446,13 @@ requirement, and the host falls back to its own default size.
|
|
|
449
446
|
|
|
450
447
|
Source: `src/schema.ts` (`remoteModuleRegistrationManifestV1Schema` `.refine`).
|
|
451
448
|
|
|
449
|
+
### [MEDIUM] `render.entry` is not an HTML document
|
|
450
|
+
|
|
451
|
+
The entry must be an `.html` document. Other extensions fail in `config()` before the plugin checks
|
|
452
|
+
whether the file exists:
|
|
453
|
+
|
|
454
|
+
`[persona-layout-modules] <key>: render "<entry>" must be an .html document — only document-delivered modules are supported`
|
|
455
|
+
|
|
452
456
|
### [MEDIUM] `render.entry` doesn't resolve to a real file
|
|
453
457
|
|
|
454
458
|
`personaLayoutModules` fails fast in `configResolved` — before running a full build — when an entry
|
|
@@ -478,7 +482,7 @@ personaLayoutModules({ manifestPath: '/manifests/my-app.json', modules: [] });
|
|
|
478
482
|
|
|
479
483
|
Code outside `vite.config.ts` that needs the default path (e.g. a subapp server adding CORS on the
|
|
480
484
|
manifest route) should import the constant from the runtime-safe `.` entry rather than hardcoding it
|
|
481
|
-
— this avoids pulling in
|
|
485
|
+
— this avoids pulling in Vite:
|
|
482
486
|
|
|
483
487
|
```ts
|
|
484
488
|
import { DEFAULT_MANIFEST_PATH } from '@persona/layout-editor-remote-module';
|