@antelopejs/dms-frontend 0.8.0 → 0.9.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/dist/commands/build-action.js +3 -0
- package/dist/commands/dev-action.d.ts +2 -0
- package/dist/commands/dev-action.js +12 -5
- package/dist/commands/dev.js +3 -1
- package/dist/commands/start-action.js +36 -5
- package/dist/common.d.ts +1 -0
- package/dist/common.js +1 -0
- package/dist/config.js +3 -0
- package/dist/derived-outputs.d.ts +1 -5
- package/dist/derived-outputs.js +46 -33
- package/dist/dev-modules.d.ts +5 -0
- package/dist/dev-modules.js +88 -0
- package/dist/entry-weight.d.ts +24 -0
- package/dist/entry-weight.js +86 -0
- package/dist/help.js +5 -1
- package/dist/layer-watch.js +3 -2
- package/dist/layers.d.ts +1 -0
- package/dist/locale-files.d.ts +6 -0
- package/dist/locale-files.js +75 -0
- package/dist/manifest.js +2 -0
- package/dist/private-values.d.ts +1 -0
- package/dist/private-values.js +16 -0
- package/dist/project-modules-child.d.ts +13 -0
- package/dist/project-modules-child.js +26 -0
- package/dist/project-modules.d.ts +1 -0
- package/dist/project-modules.js +73 -38
- package/dist/workspace-setup.js +41 -3
- package/dist/workspace.d.ts +6 -0
- package/package.json +9 -2
- package/templates/vue/app-runtime.ts +8 -1
- package/templates/vue/entry-weight.mjs +132 -0
- package/templates/vue/frontend-module.ts +144 -1
- package/templates/vue/gitignore +1 -0
- package/templates/vue/main.ts +21 -0
- package/templates/vue/network-error.ts +3 -1
- package/templates/vue/package.json +1 -1
- package/templates/vue/patches/@vueuse__core@14.4.0.patch +24 -0
- package/templates/vue/pnpm-workspace.yaml +5 -0
- package/templates/vue/server/client-manifest.mjs +12 -0
- package/templates/vue/server/in-place-render.mjs +110 -0
- package/templates/vue/server/page-props.mjs +10 -0
- package/templates/vue/server/proxy-transfers.mjs +35 -9
- package/templates/vue/server.mjs +39 -29
- package/templates/vue/sfc-global-types.mjs +119 -0
- package/templates/vue/ssr-renderer.ts +8 -0
- package/templates/vue/ui-kbd.mjs +45 -0
- package/templates/vue/vite.config.ts +32 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Vue's SFC compiler turns `defineProps<Props>()` into runtime prop types, and
|
|
2
|
+
// it reads a type from the component's own imports only. A type a module
|
|
3
|
+
// auto-imports (its declared `app/types`, `app/composables`…, and Vue's own
|
|
4
|
+
// `Ref`, `Component`…) is visible to TypeScript through auto-imports.d.ts but
|
|
5
|
+
// not to the compiler, which drops it from the prop without a word:
|
|
6
|
+
// `boolean | RowActionConfig` became `{ type: Boolean }` in a production build.
|
|
7
|
+
//
|
|
8
|
+
// The compiler does read ambient declarations from `script.globalTypeFiles`,
|
|
9
|
+
// but not the `export type { … } from` re-exports auto-imports.d.ts is made
|
|
10
|
+
// of. This writes those types as ambient aliases it can follow, from the same
|
|
11
|
+
// directories and through the same scanner as the auto-imports, before Vite
|
|
12
|
+
// compiles anything, and rewrites them when a dev server sees a module's
|
|
13
|
+
// declared directories change.
|
|
14
|
+
|
|
15
|
+
import { readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { createRequire } from "node:module";
|
|
17
|
+
import { pathToFileURL } from "node:url";
|
|
18
|
+
|
|
19
|
+
// The very unimport the auto-import plugin scans with, so both read the
|
|
20
|
+
// directories the same way. Imported by URL, not by path: on Windows,
|
|
21
|
+
// `import()` reads `C:\...` as a URL whose scheme is `c:` and refuses it.
|
|
22
|
+
const unimportUrl = pathToFileURL(
|
|
23
|
+
createRequire(
|
|
24
|
+
createRequire(import.meta.url).resolve("unplugin-auto-import/package.json"),
|
|
25
|
+
).resolve("unimport"),
|
|
26
|
+
);
|
|
27
|
+
const { builtinPresets, resolvePreset, scanDirExports } = await import(
|
|
28
|
+
unimportUrl.href
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const HEADER =
|
|
32
|
+
"// Generated by @antelopejs/dms-frontend from the auto-imported types, for\n" +
|
|
33
|
+
"// Vue's SFC compiler (script.globalTypeFiles). Not read by TypeScript.\n";
|
|
34
|
+
|
|
35
|
+
const withoutExtension = (path) => path.replace(/\.[cm]?[jt]sx?$/, "");
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The ambient aliases of the auto-imported types: one per name, in the order
|
|
39
|
+
* the auto-import plugin reads them (Vue's preset, then the scanned
|
|
40
|
+
* directories), the last one winning, as unimport's own deduplication keeps
|
|
41
|
+
* the last of two imports of the same name. A module that exports its own
|
|
42
|
+
* `Component` thus shadows Vue's, for the compiler as for TypeScript.
|
|
43
|
+
*
|
|
44
|
+
* @param {(string | { glob: string, types?: boolean })[]} dirs the auto-import
|
|
45
|
+
* directories, as handed to the auto-import plugin
|
|
46
|
+
* @param {string} cwd the workspace
|
|
47
|
+
*/
|
|
48
|
+
export async function sfcGlobalTypes(dirs, cwd) {
|
|
49
|
+
const vue = await resolvePreset(builtinPresets.vue);
|
|
50
|
+
const scanned = await scanDirExports(dirs, { cwd, types: true });
|
|
51
|
+
const aliases = new Map();
|
|
52
|
+
for (const entry of [...vue, ...scanned]) {
|
|
53
|
+
if (!entry.type) continue;
|
|
54
|
+
const name = entry.as ?? entry.name;
|
|
55
|
+
const source = JSON.stringify(withoutExtension(entry.from));
|
|
56
|
+
aliases.set(
|
|
57
|
+
name,
|
|
58
|
+
`declare type ${name} = import(${source}).${entry.name};`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return `${HEADER}${[...aliases.values()].join("\n")}\n`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function writeIfChanged(file, content) {
|
|
65
|
+
let current;
|
|
66
|
+
try {
|
|
67
|
+
current = readFileSync(file, "utf8");
|
|
68
|
+
} catch {
|
|
69
|
+
current = undefined;
|
|
70
|
+
}
|
|
71
|
+
if (current === content) return false;
|
|
72
|
+
// Through a temporary file: a build starting meanwhile reads the previous
|
|
73
|
+
// declarations or the new ones, never a half-written file the compiler
|
|
74
|
+
// would then keep in its cache.
|
|
75
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
76
|
+
writeFileSync(temporary, content);
|
|
77
|
+
renameSync(temporary, file);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Writes the declarations once, before Vite's config is returned. */
|
|
82
|
+
export async function writeSfcGlobalTypes(dirs, cwd, file) {
|
|
83
|
+
writeIfChanged(file, await sfcGlobalTypes(dirs, cwd));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Keeps the declarations in step on a dev server: a type file added, renamed
|
|
88
|
+
* or removed rewrites them, and the Vue plugin reloads the components whose
|
|
89
|
+
* props read the file.
|
|
90
|
+
*/
|
|
91
|
+
export function sfcGlobalTypesWatcher(dirs, cwd, file) {
|
|
92
|
+
return {
|
|
93
|
+
name: "dms-sfc-global-types",
|
|
94
|
+
apply: "serve",
|
|
95
|
+
configureServer(server) {
|
|
96
|
+
let pending;
|
|
97
|
+
const refresh = (path) => {
|
|
98
|
+
if (!/\.[cm]?[jt]sx?$/.test(path) || path === file) return;
|
|
99
|
+
clearTimeout(pending);
|
|
100
|
+
pending = setTimeout(() => {
|
|
101
|
+
// The file is under the workspace, which Vite watches: rewriting it
|
|
102
|
+
// is the change the Vue plugin reloads the dependent components on.
|
|
103
|
+
// A failed scan or write keeps the previous declarations: it must
|
|
104
|
+
// not end the dev server.
|
|
105
|
+
sfcGlobalTypes(dirs, cwd)
|
|
106
|
+
.then((content) => writeIfChanged(file, content))
|
|
107
|
+
.catch((error) =>
|
|
108
|
+
server.config.logger.warn(
|
|
109
|
+
`Could not update ${file}, the types Vue's SFC compiler ` +
|
|
110
|
+
`reads: ${error instanceof Error ? error.message : error}`,
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
}, 100);
|
|
114
|
+
};
|
|
115
|
+
for (const event of ["add", "change", "unlink"])
|
|
116
|
+
server.watcher.on(event, refresh);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -63,6 +63,11 @@ export interface DmsSsrResult {
|
|
|
63
63
|
modules: string[];
|
|
64
64
|
error?: DmsErrorData;
|
|
65
65
|
redirect?: string;
|
|
66
|
+
/**
|
|
67
|
+
* The locale of the catalog the page was rendered with: the server preloads
|
|
68
|
+
* that catalog's file, which the browser loads before it hydrates.
|
|
69
|
+
*/
|
|
70
|
+
catalogLocale?: string;
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
export interface DmsSsrHead {
|
|
@@ -189,6 +194,7 @@ async function renderDmsPageWithRuntime(
|
|
|
189
194
|
let icons = new Set<string>();
|
|
190
195
|
let iconSymbols = new Map<string, string>();
|
|
191
196
|
let payload: DmsPayload = { data: {}, errors: {}, state: {} };
|
|
197
|
+
let catalogLocale: (() => string | undefined) | undefined;
|
|
192
198
|
const inertiaResult = await createInertiaApp({
|
|
193
199
|
page,
|
|
194
200
|
// Read as the HTML is complete: data a run settles afterwards was never
|
|
@@ -214,6 +220,7 @@ async function renderDmsPageWithRuntime(
|
|
|
214
220
|
runtime,
|
|
215
221
|
serverFetch,
|
|
216
222
|
});
|
|
223
|
+
catalogLocale = configured.catalogLocale;
|
|
217
224
|
return app;
|
|
218
225
|
},
|
|
219
226
|
});
|
|
@@ -256,6 +263,7 @@ async function renderDmsPageWithRuntime(
|
|
|
256
263
|
},
|
|
257
264
|
overlays: ssrContext.teleports?.["#dms-overlays"] ?? "",
|
|
258
265
|
modules: [...(ssrContext.modules ?? [])],
|
|
266
|
+
catalogLocale: catalogLocale?.(),
|
|
259
267
|
};
|
|
260
268
|
} finally {
|
|
261
269
|
// Only once the head and the async data are serialized: both still read
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Nuxt UI prints the `meta`, `ctrl` and `alt` of a key hint (`UKbd`, the
|
|
2
|
+
// `kbds` of its menus) from a map every hint shares, blank until the first
|
|
3
|
+
// hint mounts: a hint hydrating after that, in an async component, printed
|
|
4
|
+
// "⌘" or "Ctrl" over the server's blank, a hydration mismatch on every
|
|
5
|
+
// platform. Its `useKbd` is routed to the DMS runtime's `useDmsKbd`, which
|
|
6
|
+
// keeps each hint on the server's value until it mounts and lets the
|
|
7
|
+
// application give the platform to the server render
|
|
8
|
+
// (`provideDmsKbdPlatform`). The build fails if Nuxt UI changes the form
|
|
9
|
+
// rewritten here, rather than letting the mismatch come back unnoticed.
|
|
10
|
+
|
|
11
|
+
/** Nuxt UI's key hint composable. */
|
|
12
|
+
const USE_KBD = /\/@nuxt\/ui\/dist\/runtime\/composables\/useKbd\.js$/;
|
|
13
|
+
/** Its export as Nuxt UI 4 writes it: one composable shared by every hint. */
|
|
14
|
+
const SHARED_EXPORT =
|
|
15
|
+
/export const useKbd = \/\* @__PURE__ \*\/ createSharedComposable\(_useKbd\);/;
|
|
16
|
+
const ROUTED_EXPORT =
|
|
17
|
+
'import { useDmsKbd } from "#dms/frontend-module";\n' +
|
|
18
|
+
"const useSharedKbd = /* @__PURE__ */ createSharedComposable(_useKbd);\n" +
|
|
19
|
+
"export const useKbd = () => useDmsKbd(useSharedKbd, kbdKeysMap);";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The rewrite, as a Vite plugin.
|
|
23
|
+
*
|
|
24
|
+
* @param isNuxtUiFile `(id) => boolean`, whether a module is Nuxt UI's
|
|
25
|
+
*/
|
|
26
|
+
export function uiKbd(isNuxtUiFile) {
|
|
27
|
+
return {
|
|
28
|
+
name: "dms-ui-kbd",
|
|
29
|
+
enforce: "pre",
|
|
30
|
+
transform(code, id) {
|
|
31
|
+
const path = id.replaceAll("\\", "/").split("?")[0];
|
|
32
|
+
if (!isNuxtUiFile(id) || !USE_KBD.test(path)) return null;
|
|
33
|
+
if (
|
|
34
|
+
!SHARED_EXPORT.test(code) ||
|
|
35
|
+
!code.includes("export const kbdKeysMap")
|
|
36
|
+
)
|
|
37
|
+
this.error(
|
|
38
|
+
`${id} defines useKbd in a form the DMS does not rewrite: its key ` +
|
|
39
|
+
"hints would not hydrate cleanly. Update ui-kbd.mjs for this " +
|
|
40
|
+
"Nuxt UI release.",
|
|
41
|
+
);
|
|
42
|
+
return { code: code.replace(SHARED_EXPORT, ROUTED_EXPORT), map: null };
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -10,16 +10,24 @@ import {
|
|
|
10
10
|
type Plugin,
|
|
11
11
|
} from "vite";
|
|
12
12
|
import { clientCodeSplitting, onBuildLog } from "./client-chunking.mjs";
|
|
13
|
+
import { recordEntryWeight } from "./entry-weight.mjs";
|
|
13
14
|
import { rejectServerSourcesInClient } from "./client-server-guard.mjs";
|
|
14
15
|
import { uiInertiaPage } from "./ui-inertia-page.mjs";
|
|
16
|
+
import { uiKbd } from "./ui-kbd.mjs";
|
|
15
17
|
import { uiRequestState } from "./ui-request-state.mjs";
|
|
16
18
|
import { autoImportDirectories } from "./frontend-build-loader.ts";
|
|
17
19
|
import { frontendBuilds } from "./frontend-builds.generated.ts";
|
|
18
20
|
import { orderHeadForFirstPaint } from "./head-order.mjs";
|
|
19
21
|
import { iconScanGlobs } from "./icon-scan.mjs";
|
|
22
|
+
import {
|
|
23
|
+
sfcGlobalTypesWatcher,
|
|
24
|
+
writeSfcGlobalTypes,
|
|
25
|
+
} from "./sfc-global-types.mjs";
|
|
20
26
|
|
|
21
27
|
interface FrontendModuleRegistryEntry {
|
|
22
28
|
id: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
packageName?: string;
|
|
23
31
|
root: string;
|
|
24
32
|
priority: number;
|
|
25
33
|
}
|
|
@@ -69,6 +77,19 @@ const importDirectories = autoImportDirectories(
|
|
|
69
77
|
frontendBuilds,
|
|
70
78
|
(message) => logger.warn(message),
|
|
71
79
|
);
|
|
80
|
+
/**
|
|
81
|
+
* The auto-imported types, as declarations Vue's SFC compiler reads: without
|
|
82
|
+
* them a prop typed with one compiles to the wrong runtime type.
|
|
83
|
+
*/
|
|
84
|
+
const sfcGlobalTypesFile = resolve(
|
|
85
|
+
import.meta.dirname,
|
|
86
|
+
"sfc-global-types.generated.d.ts",
|
|
87
|
+
);
|
|
88
|
+
await writeSfcGlobalTypes(
|
|
89
|
+
importDirectories,
|
|
90
|
+
import.meta.dirname,
|
|
91
|
+
sfcGlobalTypesFile,
|
|
92
|
+
);
|
|
72
93
|
/**
|
|
73
94
|
* Dependencies pre-bundled by name. A module's own dependencies need no entry:
|
|
74
95
|
* the startup crawl below (`optimizerEntries`) finds them in its sources. A
|
|
@@ -153,6 +174,8 @@ export default defineConfig({
|
|
|
153
174
|
// Fails a client build that imports a dms.server.ts or a file under a
|
|
154
175
|
// module's server/ directory, however it is imported.
|
|
155
176
|
rejectServerSourcesInClient(moduleRoots),
|
|
177
|
+
// What each module adds to the client entry, which `ajs dms build` reports.
|
|
178
|
+
recordEntryWeight(registry.modules),
|
|
156
179
|
{
|
|
157
180
|
name: "dms-ui-inertia-link",
|
|
158
181
|
enforce: "pre",
|
|
@@ -160,9 +183,15 @@ export default defineConfig({
|
|
|
160
183
|
return id === uiLinkImport ? uiInertiaLinkImport : null;
|
|
161
184
|
},
|
|
162
185
|
},
|
|
186
|
+
uiKbd(isNuxtUiFile),
|
|
163
187
|
uiInertiaPage(isNuxtUiFile),
|
|
164
188
|
uiRequestState(isNuxtUiFile),
|
|
165
|
-
vue(),
|
|
189
|
+
vue({ script: { globalTypeFiles: [sfcGlobalTypesFile] } }),
|
|
190
|
+
sfcGlobalTypesWatcher(
|
|
191
|
+
importDirectories,
|
|
192
|
+
import.meta.dirname,
|
|
193
|
+
sfcGlobalTypesFile,
|
|
194
|
+
),
|
|
166
195
|
ui({
|
|
167
196
|
router: "inertia",
|
|
168
197
|
icon: {
|
|
@@ -214,6 +243,7 @@ export default defineConfig({
|
|
|
214
243
|
"useDmsError",
|
|
215
244
|
"useDmsPagedData",
|
|
216
245
|
"useDmsPageLoading",
|
|
246
|
+
"isDmsHydrating",
|
|
217
247
|
"useDmsApp",
|
|
218
248
|
"useDmsRoute",
|
|
219
249
|
"useDmsRouter",
|
|
@@ -224,6 +254,7 @@ export default defineConfig({
|
|
|
224
254
|
"DmsClientOnly",
|
|
225
255
|
"DmsLink",
|
|
226
256
|
"useDmsUserSession",
|
|
257
|
+
"provideDmsKbdPlatform",
|
|
227
258
|
],
|
|
228
259
|
},
|
|
229
260
|
],
|