@antelopejs/dms-frontend 0.4.0 → 0.5.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 +16 -5
- package/package.json +1 -1
- package/templates/vue/DmsDynamicPage.vue +1 -3
- package/templates/vue/frontend-module.ts +78 -84
package/README.md
CHANGED
|
@@ -188,17 +188,28 @@ The SDK also exposes `use` for Vue plugins. Entries execute by descending manife
|
|
|
188
188
|
|
|
189
189
|
Within one setup, the first module to register a name keeps it. `ajs-dms dev` sets every module up again each time a change to a layer reaches the server renderer, into empty registries, so the next server render uses the edited component without a restart. A `setup` may therefore run more than once in a process: it should only register, and keep no state of its own between runs.
|
|
190
190
|
|
|
191
|
-
###
|
|
191
|
+
### Component prefix
|
|
192
192
|
|
|
193
|
-
|
|
193
|
+
Every registered component joins the application's global components and resolves by name anywhere, in templates and in backend page trees. To keep the components of two modules from colliding, a module declares a `componentPrefix`, put in front of the name of every component it registers:
|
|
194
194
|
|
|
195
195
|
```ts
|
|
196
|
-
|
|
196
|
+
const frontendModule: DmsFrontendModule = {
|
|
197
|
+
componentPrefix: "Acme",
|
|
198
|
+
setup(sdk) {
|
|
199
|
+
sdk.registerComponent("Banner", Banner); // registered as AcmeBanner
|
|
200
|
+
},
|
|
201
|
+
};
|
|
197
202
|
```
|
|
198
203
|
|
|
199
|
-
|
|
204
|
+
The full name, prefix included, is the one a template uses (`<AcmeBanner>`) and a backend sends (`CustomComponent("AcmeBanner")`, or `acme-banner`). Names are compared ignoring case, a leading `lazy` and non-alphanumeric characters; a leading `Dms` is no longer dropped, since it is the DMS module's own prefix.
|
|
205
|
+
|
|
206
|
+
When two modules register the same name, the module of higher priority keeps it and a warning names both. A module that means to replace another's component registers its full name without its own prefix, which is not reported:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
sdk.registerComponent("AcmeBanner", MyBanner, { prefix: false });
|
|
210
|
+
```
|
|
200
211
|
|
|
201
|
-
|
|
212
|
+
A module's `app/build/` directory is never auto-imported (see below): its components are registered so backend pages can name them, but they are the module's internals, not a surface other modules should build on.
|
|
202
213
|
|
|
203
214
|
### Auto-imports (`dms.frontend.build.ts`)
|
|
204
215
|
|
package/package.json
CHANGED
|
@@ -37,7 +37,6 @@ interface PageRoute {
|
|
|
37
37
|
interface PagePayload {
|
|
38
38
|
layout?: PageLayout;
|
|
39
39
|
route?: PageRoute;
|
|
40
|
-
module?: string;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
interface DynamicPageProps {
|
|
@@ -56,8 +55,7 @@ function resolveDefinition(
|
|
|
56
55
|
return {
|
|
57
56
|
id,
|
|
58
57
|
component: definition.componentName
|
|
59
|
-
? resolveDmsComponent(definition.componentName
|
|
60
|
-
"div"
|
|
58
|
+
? resolveDmsComponent(definition.componentName) || "div"
|
|
61
59
|
: "div",
|
|
62
60
|
componentName: definition.componentName,
|
|
63
61
|
options: definition.options,
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
type Component,
|
|
16
16
|
type ComponentPublicInstance,
|
|
17
17
|
type ComputedRef,
|
|
18
|
+
camelize,
|
|
19
|
+
capitalize,
|
|
18
20
|
computed,
|
|
19
21
|
defineComponent,
|
|
20
22
|
type EffectScope,
|
|
@@ -51,12 +53,6 @@ export interface DmsPagePayload {
|
|
|
51
53
|
layoutUrl?: string;
|
|
52
54
|
layout?: DmsPageLayout;
|
|
53
55
|
shared?: DmsSharedPagePayload;
|
|
54
|
-
/**
|
|
55
|
-
* The frontend module that owns the page's component tree, by the name the
|
|
56
|
-
* backend added it under (its manifest name). Components that module
|
|
57
|
-
* registered with `{ private: true }` resolve in this page's tree only.
|
|
58
|
-
*/
|
|
59
|
-
module?: string;
|
|
60
56
|
[key: string]: unknown;
|
|
61
57
|
}
|
|
62
58
|
export interface DmsSharedPagePayload {
|
|
@@ -236,12 +232,11 @@ export interface DmsPluginRegistrationOptions {
|
|
|
236
232
|
export type DmsComponentPreloader = () => Promise<unknown>;
|
|
237
233
|
export interface DmsComponentRegistrationOptions {
|
|
238
234
|
/**
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
* precedence over a public component of the same name. Defaults to `false`.
|
|
235
|
+
* Registers the component under `name` as given, without the module's
|
|
236
|
+
* `componentPrefix`: the way to replace a component another module
|
|
237
|
+
* registers, from a module of higher priority. Defaults to `true`.
|
|
243
238
|
*/
|
|
244
|
-
|
|
239
|
+
prefix?: boolean;
|
|
245
240
|
}
|
|
246
241
|
export interface DmsFrontendSdk {
|
|
247
242
|
options: DmsModuleOptions;
|
|
@@ -288,13 +283,17 @@ export interface DmsFrontendSdk {
|
|
|
288
283
|
use(plugin: Plugin): void;
|
|
289
284
|
}
|
|
290
285
|
export interface DmsFrontendModule {
|
|
286
|
+
/**
|
|
287
|
+
* Put in front of the name of every component the module registers, so
|
|
288
|
+
* the components of two modules never collide: with `"Acme"`,
|
|
289
|
+
* `registerComponent("Banner", ...)` registers `AcmeBanner`, the name the
|
|
290
|
+
* backend sends and templates use.
|
|
291
|
+
*/
|
|
292
|
+
componentPrefix?: string;
|
|
291
293
|
setup(sdk: DmsFrontendSdk): void | Promise<void>;
|
|
292
294
|
}
|
|
293
295
|
export interface DmsFrontendModuleRegistration {
|
|
294
|
-
/**
|
|
295
|
-
* The module's manifest name, which its backend pages carry as their
|
|
296
|
-
* `module`. Without it, the module's private components never resolve.
|
|
297
|
-
*/
|
|
296
|
+
/** The module's manifest name, which collision warnings name it by. */
|
|
298
297
|
name?: string;
|
|
299
298
|
module: DmsFrontendModule;
|
|
300
299
|
options: DmsModuleOptions;
|
|
@@ -315,8 +314,10 @@ export interface DmsRuntimeHooks {
|
|
|
315
314
|
interface DmsComponentRegistration {
|
|
316
315
|
name: string;
|
|
317
316
|
component: Component;
|
|
318
|
-
/** The registering module, for a
|
|
317
|
+
/** The registering module, for a collision warning. */
|
|
319
318
|
module?: string;
|
|
319
|
+
/** Registered without the module's prefix, to replace another module's. */
|
|
320
|
+
unprefixed?: boolean;
|
|
320
321
|
}
|
|
321
322
|
|
|
322
323
|
interface DmsFrontendEntry {
|
|
@@ -339,8 +340,6 @@ interface DmsPluginRegistration {
|
|
|
339
340
|
*/
|
|
340
341
|
interface DmsFrontendRegistry {
|
|
341
342
|
components: Map<string, DmsComponentRegistration>;
|
|
342
|
-
/** Private components, by registering module, then by normalized name. */
|
|
343
|
-
privateComponents: Map<string, Map<string, DmsComponentRegistration>>;
|
|
344
343
|
pages: Map<string, DmsFrontendEntry>;
|
|
345
344
|
dynamicPages: Map<string, DmsFrontendEntry>;
|
|
346
345
|
layouts: Map<string, DmsFrontendEntry>;
|
|
@@ -389,7 +388,6 @@ export interface DmsFrontendRuntime {
|
|
|
389
388
|
function createDmsFrontendRegistry(): DmsFrontendRegistry {
|
|
390
389
|
return {
|
|
391
390
|
components: new Map(),
|
|
392
|
-
privateComponents: new Map(),
|
|
393
391
|
pages: new Map(),
|
|
394
392
|
dynamicPages: new Map(),
|
|
395
393
|
layouts: new Map(),
|
|
@@ -1256,53 +1254,60 @@ export function dmsPageSuspenseProps(): Record<string, () => void> {
|
|
|
1256
1254
|
};
|
|
1257
1255
|
}
|
|
1258
1256
|
|
|
1257
|
+
/**
|
|
1258
|
+
* The key a component name is registered and resolved under: case, a leading
|
|
1259
|
+
* `lazy` and every non-alphanumeric character ignored, so PascalCase,
|
|
1260
|
+
* kebab-case and lowercase spellings match. Unlike `normalizeDmsName`, a
|
|
1261
|
+
* leading `Dms` is kept: it is the DMS module's prefix, and dropping it would
|
|
1262
|
+
* make `DmsBanner` and another module's `Banner` the same component.
|
|
1263
|
+
*/
|
|
1264
|
+
function normalizeDmsComponentName(name: string): string {
|
|
1265
|
+
return name
|
|
1266
|
+
.replace(/^lazy/i, "")
|
|
1267
|
+
.replace(/[^a-z0-9]/gi, "")
|
|
1268
|
+
.toLowerCase();
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
function moduleLabel(module: string | undefined): string {
|
|
1272
|
+
return module ? `module "${module}"` : "an unnamed module";
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* A component registered under a name already taken is ignored: the first
|
|
1277
|
+
* registration, from the module of higher priority, keeps the name. That is
|
|
1278
|
+
* how a module replaces another's component, by registering its full name
|
|
1279
|
+
* without a prefix, and only a collision neither side asked for is reported.
|
|
1280
|
+
*/
|
|
1259
1281
|
function registerDmsComponentEntry(
|
|
1260
1282
|
entries: Map<string, DmsComponentRegistration>,
|
|
1261
1283
|
registration: DmsComponentRegistration,
|
|
1262
1284
|
): void {
|
|
1263
|
-
const key =
|
|
1264
|
-
|
|
1285
|
+
const key = normalizeDmsComponentName(registration.name);
|
|
1286
|
+
const existing = entries.get(key);
|
|
1287
|
+
if (!existing) {
|
|
1288
|
+
entries.set(key, registration);
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
if (existing.unprefixed || registration.unprefixed) return;
|
|
1292
|
+
console.warn(
|
|
1293
|
+
`[dms] Component "${registration.name}" from ${moduleLabel(registration.module)} is ignored: ` +
|
|
1294
|
+
`${moduleLabel(existing.module)} already registers "${existing.name}". ` +
|
|
1295
|
+
"Give the modules different component prefixes.",
|
|
1296
|
+
);
|
|
1265
1297
|
}
|
|
1266
1298
|
/** Registers a component unless a higher-priority module already owns its name. */
|
|
1267
1299
|
export function registerDmsComponent(name: string, component: Component): void {
|
|
1268
1300
|
registerDmsComponentEntry(registry.components, { name, component });
|
|
1269
1301
|
}
|
|
1270
1302
|
/**
|
|
1271
|
-
* Resolves a registered component by
|
|
1272
|
-
*
|
|
1273
|
-
* module registered resolve there, ahead of a public one of the same name.
|
|
1274
|
-
* Without it, only public components resolve.
|
|
1303
|
+
* Resolves a registered component by its full name, prefix included. Case,
|
|
1304
|
+
* a leading `lazy` and non-alphanumeric characters are ignored.
|
|
1275
1305
|
*/
|
|
1276
|
-
export function resolveDmsComponent(
|
|
1277
|
-
name
|
|
1278
|
-
owner?: string,
|
|
1279
|
-
): Component | undefined {
|
|
1280
|
-
const key = normalizeDmsName(name);
|
|
1281
|
-
const own = owner
|
|
1282
|
-
? registry.privateComponents.get(owner)?.get(key)
|
|
1283
|
-
: undefined;
|
|
1284
|
-
return (own ?? registry.components.get(key))?.component;
|
|
1306
|
+
export function resolveDmsComponent(name: string): Component | undefined {
|
|
1307
|
+
return registry.components.get(normalizeDmsComponentName(name))?.component;
|
|
1285
1308
|
}
|
|
1286
1309
|
export const getDmsComponent = resolveDmsComponent;
|
|
1287
1310
|
|
|
1288
|
-
const PRIVATE_COMPONENT_SEPARATOR = "::";
|
|
1289
|
-
|
|
1290
|
-
function registeredComponents(): DmsComponentRegistration[] {
|
|
1291
|
-
return [
|
|
1292
|
-
...registry.components.values(),
|
|
1293
|
-
...[...registry.privateComponents.values()].flatMap((entries) => [
|
|
1294
|
-
...entries.values(),
|
|
1295
|
-
]),
|
|
1296
|
-
];
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
/** Names a registered component across the server render and hydration. */
|
|
1300
|
-
function componentReference({ name, module }: DmsComponentRegistration) {
|
|
1301
|
-
return module === undefined
|
|
1302
|
-
? name
|
|
1303
|
-
: `${module}${PRIVATE_COMPONENT_SEPARATOR}${name}`;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
1311
|
/**
|
|
1307
1312
|
* Records, by registered name, every registered async component a server
|
|
1308
1313
|
* render reaches, so the client can resolve the same ones before hydrating.
|
|
@@ -1312,9 +1317,9 @@ function componentReference({ name, module }: DmsComponentRegistration) {
|
|
|
1312
1317
|
*/
|
|
1313
1318
|
export function trackDmsAsyncComponents(app: App): Set<string> {
|
|
1314
1319
|
const names = new Map<Component, string>();
|
|
1315
|
-
|
|
1316
|
-
if ((
|
|
1317
|
-
names.set(
|
|
1320
|
+
registry.components.forEach(({ component, name }) => {
|
|
1321
|
+
if ((component as DmsAsyncComponent).__asyncLoader)
|
|
1322
|
+
names.set(component, name);
|
|
1318
1323
|
});
|
|
1319
1324
|
const rendered = new Set<string>();
|
|
1320
1325
|
app.mixin({
|
|
@@ -1357,18 +1362,10 @@ export function trackDmsServerScopes(app: App): () => void {
|
|
|
1357
1362
|
export async function resolveDmsAsyncComponents(
|
|
1358
1363
|
names: readonly string[],
|
|
1359
1364
|
): Promise<void> {
|
|
1360
|
-
const components = new Map(
|
|
1361
|
-
registeredComponents().map((registration) => [
|
|
1362
|
-
componentReference(registration),
|
|
1363
|
-
registration.component,
|
|
1364
|
-
]),
|
|
1365
|
-
);
|
|
1366
1365
|
await Promise.all(
|
|
1367
1366
|
names.map((name) =>
|
|
1368
1367
|
(
|
|
1369
|
-
(
|
|
1370
|
-
| DmsAsyncComponent
|
|
1371
|
-
| undefined
|
|
1368
|
+
resolveDmsComponent(name) as DmsAsyncComponent | undefined
|
|
1372
1369
|
)?.__asyncLoader?.(),
|
|
1373
1370
|
),
|
|
1374
1371
|
);
|
|
@@ -1410,7 +1407,6 @@ function collectRenderedComponentNames(
|
|
|
1410
1407
|
|
|
1411
1408
|
async function preloadDmsLayoutComponents(
|
|
1412
1409
|
layout: DmsPageLayout | undefined,
|
|
1413
|
-
owner: string | undefined,
|
|
1414
1410
|
): Promise<void> {
|
|
1415
1411
|
const names = new Set<string>();
|
|
1416
1412
|
collectRenderedComponentNames(layout?.layout, names);
|
|
@@ -1419,7 +1415,7 @@ async function preloadDmsLayoutComponents(
|
|
|
1419
1415
|
});
|
|
1420
1416
|
await Promise.all(
|
|
1421
1417
|
[...names].map((name) => {
|
|
1422
|
-
const component = resolveDmsComponent(name
|
|
1418
|
+
const component = resolveDmsComponent(name) as
|
|
1423
1419
|
| DmsAsyncComponent
|
|
1424
1420
|
| undefined;
|
|
1425
1421
|
return component?.__asyncLoader?.();
|
|
@@ -1561,8 +1557,7 @@ export async function preloadDmsPage(props: DmsPageProps): Promise<void> {
|
|
|
1561
1557
|
(entry?.component as DmsAsyncComponent | undefined)?.__asyncLoader?.(),
|
|
1562
1558
|
]),
|
|
1563
1559
|
);
|
|
1564
|
-
if (!props.error)
|
|
1565
|
-
await preloadDmsLayoutComponents(props.page.layout, props.page.module);
|
|
1560
|
+
if (!props.error) await preloadDmsLayoutComponents(props.page.layout);
|
|
1566
1561
|
}
|
|
1567
1562
|
|
|
1568
1563
|
export function useDmsInjection<T>(
|
|
@@ -1580,29 +1575,28 @@ function registerDmsFrontendEntry(
|
|
|
1580
1575
|
if (!entries.has(name)) entries.set(name, { component, preload });
|
|
1581
1576
|
}
|
|
1582
1577
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
): Map<string, DmsComponentRegistration> {
|
|
1587
|
-
const entries = target.privateComponents.get(owner) ?? new Map();
|
|
1588
|
-
target.privateComponents.set(owner, entries);
|
|
1589
|
-
return entries;
|
|
1578
|
+
/** `name` behind `prefix`, as a PascalCase component name. */
|
|
1579
|
+
function prefixedComponentName(prefix: string, name: string): string {
|
|
1580
|
+
return prefix ? `${prefix}${capitalize(camelize(name))}` : name;
|
|
1590
1581
|
}
|
|
1591
1582
|
|
|
1592
1583
|
function createSdk(
|
|
1593
1584
|
target: DmsFrontendRegistry,
|
|
1594
|
-
{
|
|
1585
|
+
{
|
|
1586
|
+
name: module,
|
|
1587
|
+
module: { componentPrefix = "" },
|
|
1588
|
+
options,
|
|
1589
|
+
}: DmsFrontendModuleRegistration,
|
|
1595
1590
|
): DmsFrontendSdk {
|
|
1596
1591
|
return {
|
|
1597
1592
|
options,
|
|
1598
|
-
registerComponent: (name, component,
|
|
1599
|
-
|
|
1600
|
-
?
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
: registerDmsComponentEntry(target.components, { name, component }),
|
|
1593
|
+
registerComponent: (name, component, { prefix = true } = {}) =>
|
|
1594
|
+
registerDmsComponentEntry(target.components, {
|
|
1595
|
+
name: prefix ? prefixedComponentName(componentPrefix, name) : name,
|
|
1596
|
+
component,
|
|
1597
|
+
module,
|
|
1598
|
+
unprefixed: !prefix,
|
|
1599
|
+
}),
|
|
1606
1600
|
registerPage: (name, component, preload) => {
|
|
1607
1601
|
const key = normalizeDmsPageKey(name);
|
|
1608
1602
|
registerDmsFrontendEntry(target.pages, key, component, preload);
|