@antelopejs/dms-frontend 0.9.1 → 0.9.3

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.
Files changed (103) hide show
  1. package/README.md +9 -1
  2. package/dist/cancellation.js +2 -1
  3. package/dist/config.js +11 -7
  4. package/dist/derived-outputs.js +6 -113
  5. package/dist/dev-modules.js +3 -2
  6. package/dist/discovery.js +19 -12
  7. package/dist/fs-sync.js +1 -2
  8. package/dist/internal/guards.d.ts +5 -0
  9. package/dist/internal/guards.js +22 -0
  10. package/dist/internal/locale-catalogs.d.ts +6 -0
  11. package/dist/internal/locale-catalogs.js +115 -0
  12. package/dist/layer-aliases.js +2 -1
  13. package/dist/layers.js +2 -1
  14. package/dist/locale-files.js +9 -4
  15. package/dist/manifest-secrets.js +11 -4
  16. package/dist/manifest.js +11 -5
  17. package/dist/materialize.js +6 -5
  18. package/dist/module-set.js +2 -4
  19. package/dist/ports.js +8 -1
  20. package/dist/private-values.js +26 -17
  21. package/dist/project-modules.js +17 -11
  22. package/dist/renderer-range.js +2 -1
  23. package/dist/server-process.js +17 -16
  24. package/dist/update-check.js +23 -16
  25. package/dist/verify-source-runner.js +2 -2
  26. package/dist/workspace-setup.js +1 -2
  27. package/dist/workspace.js +11 -10
  28. package/package.json +7 -4
  29. package/skills/dms-frontend/SKILL.md +1 -1
  30. package/skills/dms-frontend/cases.json +77 -0
  31. package/templates/vue/app-runtime.ts +88 -7
  32. package/templates/vue/client-chunking.mjs +0 -5
  33. package/templates/vue/client-server-guard.mjs +10 -13
  34. package/templates/vue/entry-weight.mjs +12 -14
  35. package/templates/vue/frontend-module.ts +159 -3563
  36. package/templates/vue/frontend-runtime/async-data-entry.ts +320 -0
  37. package/templates/vue/frontend-runtime/async-data.ts +404 -0
  38. package/templates/vue/frontend-runtime/components.ts +386 -0
  39. package/templates/vue/frontend-runtime/cookies.ts +125 -0
  40. package/templates/vue/frontend-runtime/data-access.ts +132 -0
  41. package/templates/vue/frontend-runtime/dms-error.ts +11 -0
  42. package/templates/vue/frontend-runtime/errors.ts +145 -0
  43. package/templates/vue/frontend-runtime/fetch.ts +306 -0
  44. package/templates/vue/frontend-runtime/module-registration.ts +247 -0
  45. package/templates/vue/frontend-runtime/paged-data.ts +191 -0
  46. package/templates/vue/frontend-runtime/payload.ts +172 -0
  47. package/templates/vue/frontend-runtime/route.ts +328 -0
  48. package/templates/vue/frontend-runtime/sdk.ts +300 -0
  49. package/templates/vue/frontend-runtime/state.ts +167 -0
  50. package/templates/vue/frontend-runtime/types.ts +361 -0
  51. package/templates/vue/frontend-runtime/ui.ts +247 -0
  52. package/templates/vue/globals.d.ts +5 -0
  53. package/templates/vue/head-order.mjs +11 -13
  54. package/templates/vue/icon-bundle.ts +31 -0
  55. package/templates/vue/message-subset.ts +65 -0
  56. package/templates/vue/network-error.ts +4 -0
  57. package/templates/vue/prepare-types.mjs +0 -8
  58. package/templates/vue/server/auth/backend.mjs +4 -2
  59. package/templates/vue/server/auth/establish-endpoints.mjs +14 -9
  60. package/templates/vue/server/auth/session.mjs +4 -3
  61. package/templates/vue/server/auth-hooks.mjs +6 -6
  62. package/templates/vue/server/backend-proxies.mjs +6 -6
  63. package/templates/vue/server/backend-response.mjs +15 -14
  64. package/templates/vue/server/built-files.mjs +10 -8
  65. package/templates/vue/server/cli-report.mjs +7 -5
  66. package/templates/vue/server/client-error.mjs +11 -8
  67. package/templates/vue/server/client-manifest.mjs +9 -8
  68. package/templates/vue/server/content.mjs +2 -1
  69. package/templates/vue/server/dev-private-files.mjs +20 -15
  70. package/templates/vue/server/dev-styles.mjs +22 -25
  71. package/templates/vue/server/email-bundle-watch.mjs +0 -12
  72. package/templates/vue/server/email.mjs +20 -15
  73. package/templates/vue/server/favicon.mjs +12 -11
  74. package/templates/vue/server/homepage.mjs +8 -10
  75. package/templates/vue/server/icons.mjs +18 -9
  76. package/templates/vue/server/in-place-render.mjs +6 -8
  77. package/templates/vue/server/listen-host.mjs +2 -2
  78. package/templates/vue/server/module-event.mjs +5 -5
  79. package/templates/vue/server/module-routes.mjs +11 -7
  80. package/templates/vue/server/module-server.mjs +8 -7
  81. package/templates/vue/server/module-testing.mjs +7 -7
  82. package/templates/vue/server/page-props.mjs +14 -14
  83. package/templates/vue/server/path-patterns.mjs +4 -4
  84. package/templates/vue/server/proxy-compression.mjs +21 -13
  85. package/templates/vue/server/proxy-headers.mjs +5 -3
  86. package/templates/vue/server/proxy-redirects.mjs +7 -6
  87. package/templates/vue/server/proxy-transfers.mjs +15 -11
  88. package/templates/vue/server/public-endpoints.mjs +27 -19
  89. package/templates/vue/server/public-options.mjs +9 -7
  90. package/templates/vue/server/request-failure.mjs +4 -6
  91. package/templates/vue/server/request-routing.mjs +8 -11
  92. package/templates/vue/server/request-target.mjs +6 -6
  93. package/templates/vue/server/route-rules.mjs +7 -5
  94. package/templates/vue/server/static-files.mjs +19 -10
  95. package/templates/vue/server/web-body.mjs +3 -4
  96. package/templates/vue/server.mjs +32 -19
  97. package/templates/vue/sfc-global-types.mjs +27 -24
  98. package/templates/vue/ssr-renderer.ts +10 -1
  99. package/templates/vue/ui-inertia-page.mjs +7 -7
  100. package/templates/vue/ui-kbd.mjs +10 -10
  101. package/templates/vue/ui-lazy-icons.mjs +44 -0
  102. package/templates/vue/ui-request-state.mjs +9 -9
  103. package/templates/vue/vite.config.ts +2 -0
@@ -4,6 +4,7 @@ import { useToast } from "@nuxt/ui/composables/useToast";
4
4
  import { useAppConfig } from "@nuxt/ui/runtime/vue/composables/useAppConfig";
5
5
  import { en as uiEn, fr as uiFr } from "@nuxt/ui/locale";
6
6
  import ui from "@nuxt/ui/vue-plugin";
7
+ import { router } from "@inertiajs/vue3";
7
8
  import { useHead } from "@unhead/vue";
8
9
  import type { VueHeadClient } from "@unhead/vue/types";
9
10
  import type { IconifyIcon } from "@iconify/vue";
@@ -64,7 +65,13 @@ import {
64
65
  supportedLocales,
65
66
  syncLocaleMessages,
66
67
  } from "./locales.generated";
67
- import { showNetworkErrors } from "./network-error";
68
+ import {
69
+ type DmsMessageSubset,
70
+ SSR_MESSAGES_ID,
71
+ recordMessageReads,
72
+ } from "./message-subset";
73
+ import { NETWORK_ERROR_KEYS, showNetworkErrors } from "./network-error";
74
+ import { loadIconBundle } from "./icon-bundle";
68
75
  import { createUiAppConfigMerger } from "./ui-app-config";
69
76
 
70
77
  export interface DmsInertiaSetupProps {
@@ -86,8 +93,16 @@ export interface DmsAppOptions {
86
93
  }
87
94
 
88
95
  export interface DmsConfiguredApp {
89
- /** The locale of the catalog the app shows its messages from now. */
96
+ /**
97
+ * The locale of the catalog the app shows its messages from now, none when
98
+ * the page is served the messages it read alone.
99
+ */
90
100
  catalogLocale: () => string | undefined;
101
+ /**
102
+ * On the server, for a page of a visitor who is not signed in: the messages
103
+ * the render read, which the page carries instead of the whole catalog.
104
+ */
105
+ messageSubset: () => DmsMessageSubset | undefined;
91
106
  mounted: () => Promise<void>;
92
107
  runtime: ReturnType<typeof createDmsFrontendRuntime>;
93
108
  }
@@ -238,8 +253,18 @@ const DmsPersistentInertiaPage = Object.assign(DmsInertiaPage, {
238
253
  layout: DmsPersistentLayout,
239
254
  });
240
255
 
241
- export function resolveDmsInertiaPage(): Component {
242
- return DmsPersistentInertiaPage;
256
+ /**
257
+ * What a visit waits for before it shows its page, set when the first one
258
+ * starts from a page that began without what the pages after it read: a
259
+ * subset of the catalog, the icon bundle. Inertia also resolves the first page
260
+ * once the app is set up, which must not wait for it.
261
+ */
262
+ let completeInitialAssets: (() => Promise<void>) | undefined;
263
+
264
+ export function resolveDmsInertiaPage(): Component | Promise<Component> {
265
+ return completeInitialAssets
266
+ ? completeInitialAssets().then(() => DmsPersistentInertiaPage)
267
+ : DmsPersistentInertiaPage;
243
268
  }
244
269
 
245
270
  function renderDmsPage(
@@ -310,13 +335,66 @@ export async function configureDmsApp(
310
335
  userLocale(options.initialPageProps.user) ??
311
336
  readDmsLocaleCookie(runtime, supportedLocales) ??
312
337
  DEFAULT_LOCALE;
313
- const messages = await loadLocaleMessages(locale);
338
+ const anonymous = !options.initialPageProps.user;
339
+ const subset = anonymous
340
+ ? readServerRendered<DmsMessageSubset | undefined>(
341
+ SSR_MESSAGES_ID,
342
+ undefined,
343
+ )
344
+ : undefined;
345
+ const started = subset?.locale === locale ? subset : undefined;
346
+ const messages = started?.messages ?? (await loadLocaleMessages(locale));
347
+ const recorder =
348
+ anonymous && typeof window === "undefined"
349
+ ? recordMessageReads(messages)
350
+ : undefined;
351
+ let loading: Promise<void> | undefined;
314
352
  const i18n = createI18n({
315
353
  legacy: false,
316
354
  locale,
317
355
  fallbackLocale: DEFAULT_LOCALE,
318
- messages: { ...localeMessages, [locale]: messages },
356
+ messages: started
357
+ ? { [locale]: messages }
358
+ : { ...localeMessages, [locale]: recorder?.messages ?? messages },
359
+ ...(started && {
360
+ missing: (_locale: string, key: string) => {
361
+ if (i18n.global.locale.value !== locale) return key;
362
+ void loadCompleteCatalog();
363
+ return "";
364
+ },
365
+ }),
319
366
  });
367
+ function loadCompleteCatalog(): Promise<void> {
368
+ if (i18n.global.locale.value !== locale) return Promise.resolve();
369
+ loading ??= loadLocaleMessages(locale).then(
370
+ (complete) => {
371
+ i18n.global.setLocaleMessage(locale, complete);
372
+ i18n.global.setMissingHandler(null);
373
+ },
374
+ () => {
375
+ loading = undefined;
376
+ },
377
+ );
378
+ return loading;
379
+ }
380
+ if (anonymous && typeof window !== "undefined") {
381
+ const complete = async (): Promise<void> => {
382
+ await Promise.all([
383
+ started ? loadCompleteCatalog() : undefined,
384
+ loadIconBundle(),
385
+ ]);
386
+ };
387
+ router.on("before", () => {
388
+ completeInitialAssets = complete;
389
+ void complete();
390
+ });
391
+ for (const type of ["pointerdown", "keydown", "focusin", "touchstart"])
392
+ addEventListener(type, () => void loadIconBundle(), {
393
+ once: true,
394
+ capture: true,
395
+ });
396
+ }
397
+ if (recorder) for (const key of NETWORK_ERROR_KEYS) i18n.global.te(key);
320
398
  // A translation changed in a layer during development reaches the page
321
399
  // without a reload. A server render imports the new catalogs instead.
322
400
  if (typeof window !== "undefined") syncLocaleMessages(i18n.global);
@@ -339,6 +417,7 @@ export async function configureDmsApp(
339
417
  // rendered: the page, its layout, and the components the render reached.
340
418
  // Hydration then adopts the server-rendered markup instead of discarding it.
341
419
  await Promise.all([
420
+ anonymous ? undefined : loadIconBundle(),
342
421
  preloadDmsPage(options.initialPageProps),
343
422
  resolveDmsAsyncComponents(
344
423
  readServerRendered<string[]>(SSR_ASYNC_COMPONENTS_ID, []),
@@ -352,7 +431,9 @@ export async function configureDmsApp(
352
431
  supportedLocales,
353
432
  );
354
433
  return {
355
- catalogLocale: () => catalogLocale(i18n.global.locale.value),
434
+ catalogLocale: () =>
435
+ recorder ? undefined : catalogLocale(i18n.global.locale.value),
436
+ messageSubset: () => recorder && { locale, messages: recorder.subset() },
356
437
  mounted,
357
438
  runtime,
358
439
  };
@@ -1,8 +1,3 @@
1
- // How the client bundle is cut into files, and what the build says about it.
2
- //
3
- // Kept beside vite.config.ts rather than inside it so the options can be
4
- // exercised on their own, the way head-order.mjs and client-server-guard.mjs are.
5
-
6
1
  /**
7
2
  * Rolldown's `output.codeSplitting` for the client build.
8
3
  *
@@ -1,16 +1,3 @@
1
- // Keeping server code out of the browser.
2
- //
3
- // A module's `dms.server.ts` and the files under its `server/` directories run
4
- // in the generated server only: they read private options, sign tokens, talk
5
- // to the backend with its secrets. A client file that imports one of them,
6
- // even statically and through an alias, would ship that code in the client
7
- // bundle. Rollup's manifest only lists entries and dynamic imports, so a check
8
- // on the built output misses a static import; the guard sits in the module
9
- // graph instead, where every resolved file passes.
10
- //
11
- // Kept beside vite.config.ts rather than inside it so the plugin can be
12
- // exercised on its own, the way head-order.mjs is.
13
-
14
1
  import { relative, sep } from "node:path";
15
2
 
16
3
  const SERVER_ENTRY = /(?:^|[\\/])dms\.server\.[cm]?[jt]sx?$/;
@@ -47,6 +34,16 @@ export function isServerSource(id, moduleRoots) {
47
34
  * Fail a client build, and a client request in development, that loads server
48
35
  * code. Server-side rendering and the server modules build load it through
49
36
  * SSR, which the guard leaves alone.
37
+ *
38
+ * Keeps server code out of the browser. A module's `dms.server.ts` and the
39
+ * files under its `server/` directories run in the generated server only: they
40
+ * read private options, sign tokens, talk to the backend with its secrets. A
41
+ * client file that imports one of them, even statically and through an alias,
42
+ * would ship that code in the client bundle. Rollup's manifest only lists
43
+ * entries and dynamic imports, so a check on the built output misses a static
44
+ * import; the guard sits in the module graph instead, where every resolved file
45
+ * passes. Kept beside vite.config.ts rather than inside it so the plugin can be
46
+ * exercised on its own, the way head-order.mjs is.
50
47
  */
51
48
  export function rejectServerSourcesInClient(moduleRoots) {
52
49
  let ssrBuild = false;
@@ -1,15 +1,3 @@
1
- // What each frontend module adds to the client entry.
2
- //
3
- // Every page loads the entry, and everything it imports statically, before it
4
- // runs anything: a module's `dms.frontend.ts`, its plugins and whatever they
5
- // import land there, on every page of every application that loads the
6
- // module. The client build records, from the bundle's own metadata, how much
7
- // of the entry each module's sources make up; `ajs dms build` reports it and
8
- // warns about a module above its threshold.
9
- //
10
- // Kept beside vite.config.ts rather than inside it so the plugin can be
11
- // exercised on its own, the way client-chunking.mjs is.
12
-
13
1
  import { mkdirSync, writeFileSync } from "node:fs";
14
2
  import { dirname, join, relative, sep } from "node:path";
15
3
 
@@ -109,6 +97,18 @@ export function attributeEntry(bundle, modules) {
109
97
  * Records, in the client build, what each frontend module adds to the entry
110
98
  * (`ENTRY_WEIGHT_FILE`). Server builds are left alone: their bundle is no
111
99
  * page's entry.
100
+ *
101
+ * What each frontend module adds to the client entry. Every page loads the
102
+ * entry, and everything it imports statically, before it runs anything: a
103
+ * module's `dms.frontend.ts`, its plugins and whatever they import land there,
104
+ * on every page of every application that loads the module. The client build
105
+ * records, from the bundle's own metadata, how much of the entry each module's
106
+ * sources make up; `ajs dms build` reports it and warns about a module above
107
+ * its threshold. Kept beside vite.config.ts rather than inside it so the plugin
108
+ * can be exercised on its own, the way client-chunking.mjs is.
109
+ *
110
+ * The weight is recorded once the bundle is written: plugins that generate the
111
+ * bundle after this one still rewrite the chunks' code.
112
112
  */
113
113
  export function recordEntryWeight(modules) {
114
114
  let ssrBuild = false;
@@ -117,8 +117,6 @@ export function recordEntryWeight(modules) {
117
117
  configResolved(config) {
118
118
  ssrBuild = Boolean(config.build.ssr);
119
119
  },
120
- // Once the bundle is written: plugins that generate the bundle after this
121
- // one still rewrite the chunks' code.
122
120
  writeBundle(options, bundle) {
123
121
  if (ssrBuild || !options.dir) return;
124
122
  const file = join(options.dir, ENTRY_WEIGHT_FILE);