@antelopejs/dms-frontend 0.5.6 → 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 +376 -18
- package/dist/app-configs.d.ts +9 -0
- package/dist/app-configs.js +52 -0
- package/dist/asset-roots.d.ts +7 -0
- package/dist/asset-roots.js +34 -0
- package/dist/commands/build-action.js +9 -0
- package/dist/commands/dev-action.js +88 -19
- package/dist/commands/start-action.js +2 -0
- package/dist/common.d.ts +1 -0
- package/dist/common.js +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +8 -3
- package/dist/derived-outputs.d.ts +2 -0
- package/dist/derived-outputs.js +38 -31
- package/dist/help.js +10 -3
- package/dist/layer-watch.js +123 -131
- package/dist/layers.js +2 -0
- package/dist/manifest-secrets.js +5 -5
- package/dist/manifest.js +7 -1
- package/dist/materialize.js +21 -1
- package/dist/module-set.d.ts +16 -0
- package/dist/module-set.js +80 -0
- package/dist/server-process.d.ts +1 -0
- package/dist/server-process.js +28 -2
- package/dist/verify-source-runner.js +12 -0
- package/dist/workspace.d.ts +9 -0
- package/package.json +2 -1
- package/templates/vue/app-runtime.ts +58 -16
- package/templates/vue/client-server-guard.mjs +73 -0
- package/templates/vue/email-renderer.ts +17 -4
- package/templates/vue/email-runtime.ts +1 -1
- package/templates/vue/frontend-module.ts +1817 -259
- package/templates/vue/globals.d.ts +1 -1
- package/templates/vue/icon-hydration.ts +106 -1
- package/templates/vue/index.html +0 -1
- package/templates/vue/layer-app-config.ts +92 -0
- package/templates/vue/main.ts +10 -2
- package/templates/vue/package.json +4 -1
- package/templates/vue/server/auth/backend.mjs +13 -19
- package/templates/vue/server/auth/client-ip.mjs +27 -0
- package/templates/vue/server/auth/establish-endpoints.mjs +70 -0
- package/templates/vue/server/auth/session.mjs +6 -97
- package/templates/vue/server/auth-hooks.mjs +84 -0
- package/templates/vue/server/backend-proxies.mjs +110 -0
- package/templates/vue/server/dev-private-files.mjs +99 -0
- package/templates/vue/server/email.mjs +11 -33
- package/templates/vue/server/favicon.mjs +20 -0
- package/templates/vue/server/inertia.mjs +48 -3
- package/templates/vue/server/listen-host.mjs +19 -0
- package/templates/vue/server/module-event.mjs +265 -0
- package/templates/vue/server/module-routes.mjs +513 -0
- package/templates/vue/server/module-server.mjs +225 -0
- package/templates/vue/server/module-testing.mjs +65 -0
- package/templates/vue/server/page-props.mjs +151 -0
- package/templates/vue/server/path-patterns.mjs +38 -0
- package/templates/vue/server/public-endpoints.mjs +69 -11
- package/templates/vue/server/request-routing.mjs +47 -0
- package/templates/vue/server/route-rules.mjs +129 -0
- package/templates/vue/server-module.ts +294 -0
- package/templates/vue/server.mjs +177 -161
- package/templates/vue/ssr-renderer.ts +60 -19
- package/templates/vue/tsconfig.json +2 -1
- package/templates/vue/ui-app-config.ts +18 -6
- package/templates/vue/ui-inertia-page.mjs +37 -0
- package/templates/vue/vite.config.ts +31 -10
- package/templates/vue/vite.email.config.ts +1 -1
- package/templates/vue/vite.server.config.ts +57 -0
- package/templates/vue/server/auth/oauth.mjs +0 -233
- package/templates/vue/server/auth/routes.mjs +0 -444
- package/templates/vue/server/render-token.mjs +0 -81
- package/templates/vue/server/tester.mjs +0 -227
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/dms-frontend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Frontend-agnostic loader for AntelopeJS DMS, shipping the Vue 3 renderer (Vite, Inertia, SSR)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelope",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"@vitejs/plugin-vue": "^6.0.1",
|
|
86
86
|
"@vue/server-renderer": "^3.5.25",
|
|
87
87
|
"defu": "^6.1.4",
|
|
88
|
+
"devalue": "^5.9.4",
|
|
88
89
|
"knip": "^6.34.0",
|
|
89
90
|
"ofetch": "^1.5.1",
|
|
90
91
|
"oxfmt": "^0.66.0",
|
|
@@ -7,6 +7,7 @@ import ui from "@nuxt/ui/vue-plugin";
|
|
|
7
7
|
import { useHead } from "@unhead/vue";
|
|
8
8
|
import type { VueHeadClient } from "@unhead/vue/types";
|
|
9
9
|
import type { IconifyIcon } from "@iconify/vue";
|
|
10
|
+
import { parse } from "devalue";
|
|
10
11
|
import type { FetchOptions } from "ofetch";
|
|
11
12
|
import {
|
|
12
13
|
type App,
|
|
@@ -22,8 +23,10 @@ import {
|
|
|
22
23
|
import { createI18n, useI18n } from "vue-i18n";
|
|
23
24
|
import DmsDynamicPage from "./DmsDynamicPage.vue";
|
|
24
25
|
import {
|
|
26
|
+
type DmsLayerAppConfig,
|
|
25
27
|
type DmsLocaleLoader,
|
|
26
28
|
type DmsPageProps,
|
|
29
|
+
type DmsPayload,
|
|
27
30
|
type DmsUser,
|
|
28
31
|
createDmsFrontendRuntime,
|
|
29
32
|
dmsPageSuspenseProps,
|
|
@@ -32,18 +35,22 @@ import {
|
|
|
32
35
|
getDmsLayoutProps,
|
|
33
36
|
getDmsPage,
|
|
34
37
|
hydrateDmsPageProps,
|
|
38
|
+
hydrateDmsSharedProps,
|
|
35
39
|
installDmsPlugins,
|
|
40
|
+
keepSharedOverHistory,
|
|
36
41
|
preloadDmsPage,
|
|
42
|
+
preloadPrefetchedDmsPages,
|
|
37
43
|
provideDmsFrontendRuntime,
|
|
38
44
|
readDmsLocaleCookie,
|
|
39
45
|
resolveDmsAsyncComponents,
|
|
40
46
|
resolveDmsComponent,
|
|
41
|
-
|
|
47
|
+
restoreDmsPayload,
|
|
42
48
|
trackDmsAsyncComponents,
|
|
49
|
+
updateDmsAppConfig,
|
|
43
50
|
useDmsAppConfig,
|
|
44
51
|
useDmsState,
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
useDmsError,
|
|
53
|
+
useDmsUserSession,
|
|
47
54
|
} from "./frontend-module";
|
|
48
55
|
import {
|
|
49
56
|
SSR_ICONS_ID,
|
|
@@ -87,7 +94,7 @@ export type DmsServerFetch = (
|
|
|
87
94
|
options?: FetchOptions,
|
|
88
95
|
) => Promise<unknown>;
|
|
89
96
|
|
|
90
|
-
const
|
|
97
|
+
export const DMS_PAYLOAD_ID = "dms-payload";
|
|
91
98
|
export const SSR_ASYNC_COMPONENTS_ID = "dms-ssr-async-components";
|
|
92
99
|
const DEFAULT_LOCALE = "en";
|
|
93
100
|
|
|
@@ -112,17 +119,38 @@ function userLocale(user: DmsUser | null | undefined): string | undefined {
|
|
|
112
119
|
return supportedLocales.includes(locale) ? locale : undefined;
|
|
113
120
|
}
|
|
114
121
|
|
|
115
|
-
function readServerRendered<T>(
|
|
122
|
+
function readServerRendered<T>(
|
|
123
|
+
id: string,
|
|
124
|
+
empty: T,
|
|
125
|
+
parseText: (text: string) => unknown = JSON.parse,
|
|
126
|
+
): T {
|
|
116
127
|
if (typeof document === "undefined") return empty;
|
|
117
128
|
const element = document.getElementById(id);
|
|
118
129
|
if (!element?.textContent) return empty;
|
|
119
|
-
const value =
|
|
130
|
+
const value = parseText(element.textContent) as T;
|
|
120
131
|
element.remove();
|
|
121
132
|
return value;
|
|
122
133
|
}
|
|
123
134
|
|
|
124
135
|
drawLoadedIconsAtOnce();
|
|
125
136
|
|
|
137
|
+
/** A browser runtime, started from what the server rendered, if it did. */
|
|
138
|
+
function createBrowserRuntime(
|
|
139
|
+
serverFetch?: DmsServerFetch,
|
|
140
|
+
): DmsConfiguredApp["runtime"] {
|
|
141
|
+
const runtime = createDmsFrontendRuntime(
|
|
142
|
+
serverFetch as typeof import("ofetch").ofetch,
|
|
143
|
+
);
|
|
144
|
+
// Nothing when the server did not render the page: no hydration then.
|
|
145
|
+
const payload = readServerRendered<DmsPayload | undefined>(
|
|
146
|
+
DMS_PAYLOAD_ID,
|
|
147
|
+
undefined,
|
|
148
|
+
parse,
|
|
149
|
+
);
|
|
150
|
+
if (payload) restoreDmsPayload(runtime, payload);
|
|
151
|
+
return runtime;
|
|
152
|
+
}
|
|
153
|
+
|
|
126
154
|
const DmsInertiaPage = defineComponent({
|
|
127
155
|
name: "DmsInertiaPage",
|
|
128
156
|
inheritAttrs: false,
|
|
@@ -152,7 +180,7 @@ const DmsPersistentLayout = defineComponent({
|
|
|
152
180
|
setup(_, { attrs, slots }) {
|
|
153
181
|
const props = computed(() => attrs as unknown as DmsPageProps);
|
|
154
182
|
const i18n = useI18n();
|
|
155
|
-
const { user } =
|
|
183
|
+
const { user } = useDmsUserSession();
|
|
156
184
|
let localeVersion = 0;
|
|
157
185
|
useHead({
|
|
158
186
|
htmlAttrs: { lang: computed(() => i18n.locale.value) },
|
|
@@ -162,6 +190,13 @@ const DmsPersistentLayout = defineComponent({
|
|
|
162
190
|
() => hydrateDmsPageProps(props.value),
|
|
163
191
|
{ immediate: true, flush: "sync" },
|
|
164
192
|
);
|
|
193
|
+
// Apart from the page: a reload of the site layout alone (`router.reload({
|
|
194
|
+
// only: ["shared"] })`) is not a navigation.
|
|
195
|
+
watch(
|
|
196
|
+
() => attrs.shared,
|
|
197
|
+
() => hydrateDmsSharedProps(props.value.shared),
|
|
198
|
+
{ flush: "sync" },
|
|
199
|
+
);
|
|
165
200
|
// The session, not the page props: a page restored from history or from
|
|
166
201
|
// the prefetch cache carries the user as it was when it was fetched, and
|
|
167
202
|
// would switch the interface back to a language the user has left. A
|
|
@@ -178,7 +213,7 @@ const DmsPersistentLayout = defineComponent({
|
|
|
178
213
|
},
|
|
179
214
|
{ immediate: true, flush: "sync" },
|
|
180
215
|
);
|
|
181
|
-
const currentError =
|
|
216
|
+
const currentError = useDmsError();
|
|
182
217
|
const overlays = useDmsState<string[]>("dms-app-overlays", () => []);
|
|
183
218
|
return () => {
|
|
184
219
|
const error = currentError.value ?? props.value.error;
|
|
@@ -206,7 +241,7 @@ export function resolveDmsInertiaPage(): Component {
|
|
|
206
241
|
|
|
207
242
|
function renderDmsPage(
|
|
208
243
|
props: DmsPageProps,
|
|
209
|
-
capturedError: DmsPageProps["error"] =
|
|
244
|
+
capturedError: DmsPageProps["error"] = useDmsError().value,
|
|
210
245
|
) {
|
|
211
246
|
const error = capturedError ?? props.error;
|
|
212
247
|
const page = error
|
|
@@ -251,16 +286,21 @@ function mergeDmsAppConfig(): void {
|
|
|
251
286
|
mergeUiAppConfig(useAppConfig(), useDmsAppConfig());
|
|
252
287
|
}
|
|
253
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Development only: applies the layers' app configs, edited while the page is
|
|
291
|
+
* open, to the DMS app config and to Nuxt UI's, both in place, so the
|
|
292
|
+
* components reading them and Nuxt UI's colors follow without a reload.
|
|
293
|
+
*/
|
|
294
|
+
export function applyLayerAppConfigs(appConfigs: DmsLayerAppConfig[]): void {
|
|
295
|
+
updateDmsAppConfig(appConfigs);
|
|
296
|
+
mergeUiAppConfig(useAppConfig(), useDmsAppConfig(), { isChanged: true });
|
|
297
|
+
}
|
|
298
|
+
|
|
254
299
|
export async function configureDmsApp(
|
|
255
300
|
options: DmsAppOptions,
|
|
256
301
|
): Promise<DmsConfiguredApp> {
|
|
257
302
|
mergeDmsAppConfig();
|
|
258
|
-
const runtime =
|
|
259
|
-
options.runtime ??
|
|
260
|
-
createDmsFrontendRuntime(
|
|
261
|
-
options.serverFetch as typeof import("ofetch").ofetch,
|
|
262
|
-
readServerRendered<Record<string, unknown>>(SSR_ASYNC_DATA_ID, {}),
|
|
263
|
-
);
|
|
303
|
+
const runtime = options.runtime ?? createBrowserRuntime(options.serverFetch);
|
|
264
304
|
// The signed-in user's language, else the one this browser picked, which is
|
|
265
305
|
// how a visitor's choice on a public page survives a reload.
|
|
266
306
|
const locale =
|
|
@@ -289,6 +329,8 @@ export async function configureDmsApp(
|
|
|
289
329
|
if (typeof window !== "undefined") {
|
|
290
330
|
const toast = options.app.runWithContext(() => useToast());
|
|
291
331
|
showNetworkErrors(toast.add, i18n.global);
|
|
332
|
+
preloadPrefetchedDmsPages();
|
|
333
|
+
options.app.runWithContext(keepSharedOverHistory);
|
|
292
334
|
}
|
|
293
335
|
// Resolve, before the app mounts, every async component the server
|
|
294
336
|
// rendered: the page, its layout, and the components the render reached.
|
|
@@ -309,4 +351,4 @@ export async function configureDmsApp(
|
|
|
309
351
|
return { mounted, runtime };
|
|
310
352
|
}
|
|
311
353
|
|
|
312
|
-
export {
|
|
354
|
+
export { trackDmsAsyncComponents };
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
import { relative, sep } from "node:path";
|
|
15
|
+
|
|
16
|
+
const SERVER_ENTRY = /(?:^|[\\/])dms\.server\.[cm]?[jt]sx?$/;
|
|
17
|
+
|
|
18
|
+
function stripQuery(id) {
|
|
19
|
+
const query = id.indexOf("?");
|
|
20
|
+
return query === -1 ? id : id.slice(0, query);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function insideModuleServerDirectory(file, moduleRoots) {
|
|
24
|
+
return moduleRoots.some((root) => {
|
|
25
|
+
const path = relative(root, file);
|
|
26
|
+
if (!path || path.startsWith("..") || path.startsWith(sep)) return false;
|
|
27
|
+
const segments = path.split(/[\\/]/);
|
|
28
|
+
return segments.slice(0, -1).includes("server");
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Whether `id`, a resolved module id, is server code: a module's server entry
|
|
34
|
+
* (`dms.server.*`) or a file under a `server/` directory of a module. Installed
|
|
35
|
+
* packages never are: `node_modules` has its own `server` directories.
|
|
36
|
+
*/
|
|
37
|
+
export function isServerSource(id, moduleRoots) {
|
|
38
|
+
if (id.startsWith("\0")) return false;
|
|
39
|
+
const file = stripQuery(id);
|
|
40
|
+
if (/[\\/]node_modules[\\/]/.test(file)) return false;
|
|
41
|
+
return (
|
|
42
|
+
SERVER_ENTRY.test(file) || insideModuleServerDirectory(file, moduleRoots)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Fail a client build, and a client request in development, that loads server
|
|
48
|
+
* code. Server-side rendering and the server modules build load it through
|
|
49
|
+
* SSR, which the guard leaves alone.
|
|
50
|
+
*/
|
|
51
|
+
export function rejectServerSourcesInClient(moduleRoots) {
|
|
52
|
+
let ssrBuild = false;
|
|
53
|
+
return {
|
|
54
|
+
name: "dms-reject-server-sources-in-client",
|
|
55
|
+
enforce: "pre",
|
|
56
|
+
configResolved(config) {
|
|
57
|
+
ssrBuild = Boolean(config.build.ssr);
|
|
58
|
+
},
|
|
59
|
+
load(id, options) {
|
|
60
|
+
if (ssrBuild || options?.ssr) return null;
|
|
61
|
+
if (!isServerSource(id, moduleRoots)) return null;
|
|
62
|
+
const importers = this.getModuleInfo?.(id)?.importers ?? [];
|
|
63
|
+
const from = importers.length
|
|
64
|
+
? ` (imported by ${importers.join(", ")})`
|
|
65
|
+
: "";
|
|
66
|
+
this.error(
|
|
67
|
+
`Server code in the client bundle: ${stripQuery(id)}${from}. ` +
|
|
68
|
+
"A dms.server.ts and the files under a module's server/ directory " +
|
|
69
|
+
"run in the server only; import them from a dms.server.ts, never from client code.",
|
|
70
|
+
);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -2,6 +2,7 @@ import { renderToString } from "@vue/server-renderer";
|
|
|
2
2
|
import { defu } from "defu";
|
|
3
3
|
import { type Component, createSSRApp, h } from "vue";
|
|
4
4
|
import { createI18n } from "vue-i18n";
|
|
5
|
+
import { appConfigs } from "./app-configs.generated";
|
|
5
6
|
import moduleRegistry from "./generated-frontend-modules.json";
|
|
6
7
|
import { loadEmailLocaleMessages } from "./email-locales";
|
|
7
8
|
import { stripSsrMarkers } from "./email-markup";
|
|
@@ -28,6 +29,21 @@ const orderedRegistries = moduleRegistry.modules
|
|
|
28
29
|
.map((module) => registries[`/frontend-modules/${module.id}/dms.email.ts`])
|
|
29
30
|
.filter((registry): registry is EmailRegistry => Boolean(registry));
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Every app config an email reads, highest precedence first: module by module,
|
|
34
|
+
* by descending priority, what its `dms.email.ts` declares for emails, then
|
|
35
|
+
* its layers' `app/app.config.ts`. A higher-priority module's layers still
|
|
36
|
+
* override a lower one's email config, and a module's own email config, the
|
|
37
|
+
* more specific of its two, keeps the values it gave emails before the layers'
|
|
38
|
+
* configs applied to them.
|
|
39
|
+
*/
|
|
40
|
+
const emailAppConfigs = moduleRegistry.modules.flatMap((module) => [
|
|
41
|
+
registries[`/frontend-modules/${module.id}/dms.email.ts`]?.appConfig ?? {},
|
|
42
|
+
...appConfigs
|
|
43
|
+
.filter((layer) => layer.module === module.id)
|
|
44
|
+
.map((layer) => layer.config),
|
|
45
|
+
]);
|
|
46
|
+
|
|
31
47
|
function templateEntries(): Array<[string, () => Promise<EmailModule>]> {
|
|
32
48
|
return orderedRegistries.flatMap((registry) =>
|
|
33
49
|
Object.entries(registry.serverEmailTemplates),
|
|
@@ -138,10 +154,7 @@ export async function renderEmail(
|
|
|
138
154
|
}),
|
|
139
155
|
);
|
|
140
156
|
app.provide("dmsEmailLocale", locale);
|
|
141
|
-
app.provide(
|
|
142
|
-
"dmsEmailAppConfig",
|
|
143
|
-
defu({}, ...orderedRegistries.map((registry) => registry.appConfig ?? {})),
|
|
144
|
-
);
|
|
157
|
+
app.provide("dmsEmailAppConfig", defu({}, ...emailAppConfigs));
|
|
145
158
|
const publicConfig = defu(
|
|
146
159
|
{},
|
|
147
160
|
...moduleRegistry.modules.map((module) => module.options),
|
|
@@ -16,7 +16,7 @@ export function useDmsRuntimeConfig(): EmailRuntimeConfig {
|
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export function
|
|
19
|
+
export function defineDmsAppConfig<T extends Record<string, unknown>>(
|
|
20
20
|
config: T,
|
|
21
21
|
): T {
|
|
22
22
|
return config;
|