@antelopejs/dms-frontend 0.2.1 → 0.2.2
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/config.js +1 -0
- package/package.json +1 -1
- package/templates/vue/head-order.mjs +39 -0
- package/templates/vue/main.ts +37 -0
- package/templates/vue/server/dev-styles.mjs +98 -0
- package/templates/vue/server/homepage.mjs +32 -0
- package/templates/vue/server/inertia.mjs +21 -0
- package/templates/vue/server.mjs +7 -33
- package/templates/vue/vite.config.ts +36 -3
package/dist/config.js
CHANGED
|
@@ -39,6 +39,7 @@ exports.TEMPLATE_FILES = [
|
|
|
39
39
|
["frontend-module.ts", "frontend-module.ts"],
|
|
40
40
|
["globals.d.ts", "globals.d.ts"],
|
|
41
41
|
["compress-assets.mjs", "compress-assets.mjs"],
|
|
42
|
+
["head-order.mjs", "head-order.mjs"],
|
|
42
43
|
["email-renderer.ts", "email-renderer.ts"],
|
|
43
44
|
["email-runtime.ts", "email-runtime.ts"],
|
|
44
45
|
["email-locales.ts", "email-locales.ts"],
|
package/package.json
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Restoring "paint before hydrate" in the built document.
|
|
2
|
+
//
|
|
3
|
+
// The source index.html marks the entry script `fetchpriority="low"` so the
|
|
4
|
+
// browser spends its bandwidth on the render-blocking stylesheet first. Vite
|
|
5
|
+
// rewrites that script tag when it builds and drops the attribute, and it emits
|
|
6
|
+
// the script *before* the stylesheet — so the production document did the
|
|
7
|
+
// opposite of what the template asked: it discovered a 250 kB hydration bundle
|
|
8
|
+
// first and let it compete with the 27 kB the first paint actually waits on,
|
|
9
|
+
// over the plain HTTP/1.1 the generated server speaks.
|
|
10
|
+
//
|
|
11
|
+
// Kept beside vite.config.ts rather than inside it so the transform can be
|
|
12
|
+
// exercised on its own, the way client-manifest.mjs is.
|
|
13
|
+
|
|
14
|
+
const SCRIPT = /<script[^>]*\stype="module"[^>]*><\/script>/;
|
|
15
|
+
const STYLESHEET = /[ \t]*<link rel="stylesheet"[^>]*>\n?/;
|
|
16
|
+
|
|
17
|
+
function deprioritize(html) {
|
|
18
|
+
return html.replace(SCRIPT, (tag) =>
|
|
19
|
+
tag.includes("fetchpriority")
|
|
20
|
+
? tag
|
|
21
|
+
: tag.replace("<script ", '<script fetchpriority="low" '),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Give the entry script back its low fetch priority and move the stylesheet
|
|
27
|
+
* ahead of it. A document that already has them in that order is returned
|
|
28
|
+
* unchanged, so the transform is safe to run twice.
|
|
29
|
+
*/
|
|
30
|
+
export function orderHeadForFirstPaint(html) {
|
|
31
|
+
const prioritized = deprioritize(html);
|
|
32
|
+
const stylesheet = prioritized.match(STYLESHEET);
|
|
33
|
+
const script = prioritized.match(SCRIPT);
|
|
34
|
+
if (!stylesheet || !script || stylesheet.index < script.index)
|
|
35
|
+
return prioritized;
|
|
36
|
+
return prioritized
|
|
37
|
+
.replace(STYLESHEET, "")
|
|
38
|
+
.replace(SCRIPT, (tag) => `${stylesheet[0].trim()}\n ${tag}`);
|
|
39
|
+
}
|
package/templates/vue/main.ts
CHANGED
|
@@ -6,10 +6,47 @@ import { configureDmsApp, resolveDmsInertiaPage } from "./app-runtime";
|
|
|
6
6
|
import { setupFrontendModules } from "./frontend-module";
|
|
7
7
|
import { frontendModules } from "./frontend-modules.generated";
|
|
8
8
|
|
|
9
|
+
const DEV_STYLE_ATTRIBUTE = "data-dms-dev-style";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Hand each development stylesheet over to Vite.
|
|
13
|
+
*
|
|
14
|
+
* The development server puts the page's stylesheets in the document so it
|
|
15
|
+
* never paints unstyled (see `server/dev-styles.mjs`). Vite then injects its
|
|
16
|
+
* own copy of each one as a `<style data-vite-dev-id>`, and keeping both would
|
|
17
|
+
* make a later CSS edit look like it removed nothing: Vite rewrites its copy,
|
|
18
|
+
* ours keeps the deleted rules alive.
|
|
19
|
+
*
|
|
20
|
+
* We drop ours the moment Vite's appears, keyed by module id, rather than at a
|
|
21
|
+
* fixed point in the boot. A component reached through a dynamic import — most
|
|
22
|
+
* of them — has not been loaded when the app mounts, so removing its scoped
|
|
23
|
+
* rules then would flash exactly what this whole change exists to prevent.
|
|
24
|
+
*/
|
|
25
|
+
function adoptViteDevStyles(): void {
|
|
26
|
+
const pending = new Map<string, Element>();
|
|
27
|
+
for (const node of document.querySelectorAll(`[${DEV_STYLE_ATTRIBUTE}]`)) {
|
|
28
|
+
pending.set(node.getAttribute(DEV_STYLE_ATTRIBUTE) ?? "", node);
|
|
29
|
+
}
|
|
30
|
+
if (pending.size === 0) return;
|
|
31
|
+
const adopt = (): void => {
|
|
32
|
+
for (const style of document.querySelectorAll("style[data-vite-dev-id]")) {
|
|
33
|
+
const id = style.getAttribute("data-vite-dev-id") ?? "";
|
|
34
|
+
pending.get(id)?.remove();
|
|
35
|
+
pending.delete(id);
|
|
36
|
+
}
|
|
37
|
+
if (pending.size === 0) observer.disconnect();
|
|
38
|
+
};
|
|
39
|
+
const observer = new MutationObserver(adopt);
|
|
40
|
+
observer.observe(document.head, { childList: true });
|
|
41
|
+
adopt();
|
|
42
|
+
}
|
|
43
|
+
|
|
9
44
|
function markDmsReady(): void {
|
|
10
45
|
document.documentElement.dataset.dmsReady = "true";
|
|
11
46
|
}
|
|
12
47
|
|
|
48
|
+
if (import.meta.env.DEV) adoptViteDevStyles();
|
|
49
|
+
|
|
13
50
|
await setupFrontendModules(frontendModules);
|
|
14
51
|
|
|
15
52
|
createInertiaApp({
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Putting the page's stylesheets in the document the development server
|
|
2
|
+
// returns, instead of letting the client entry inject them from JavaScript
|
|
3
|
+
// once it has executed.
|
|
4
|
+
//
|
|
5
|
+
// Vite serves every stylesheet as a JavaScript module in development, so a
|
|
6
|
+
// plain SSR document carries no styles at all and the browser paints raw HTML
|
|
7
|
+
// until `/main.ts` and its module graph have run. Production has no such gap:
|
|
8
|
+
// the built `index.html` already links the entry stylesheet, which is why this
|
|
9
|
+
// module is development-only and lives beside client-manifest.mjs rather than
|
|
10
|
+
// inside server.mjs, whose size the linter caps.
|
|
11
|
+
//
|
|
12
|
+
// Each tag carries the module id Vite will use for its own copy
|
|
13
|
+
// (`data-vite-dev-id`), under our own attribute name. `main.ts` hands the
|
|
14
|
+
// stylesheet over as soon as Vite injects that copy — see `adoptViteDevStyles`.
|
|
15
|
+
// We deliberately do NOT reuse `data-vite-dev-id` itself: Vite's client adopts
|
|
16
|
+
// any pre-existing link with that attribute and then skips `updateStyle` for
|
|
17
|
+
// it, which would leave CSS edits stranded.
|
|
18
|
+
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
import { pageModulePreloads } from "./client-manifest.mjs";
|
|
22
|
+
|
|
23
|
+
const PROJECT_ROOT = fileURLToPath(new URL("../", import.meta.url));
|
|
24
|
+
const STYLE_ATTRIBUTE = "data-dms-dev-style";
|
|
25
|
+
const SFC_STYLE = /[?&]vue&type=style/;
|
|
26
|
+
const STYLE_EXTENSION =
|
|
27
|
+
/\.(?:css|less|pcss|postcss|sass|scss|styl|stylus)(?:$|\?)/;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The workspace's generated entry stylesheet. `main.ts` is its only importer,
|
|
31
|
+
* so it never enters the SSR module graph, yet it carries Tailwind, Nuxt UI and
|
|
32
|
+
* one `@source` per materialized module — nearly every rule a page needs.
|
|
33
|
+
* Naming it is the one enumeration this module accepts, and the comment is the
|
|
34
|
+
* reason why.
|
|
35
|
+
*/
|
|
36
|
+
const ENTRY_STYLESHEET = "/dms-main.css";
|
|
37
|
+
|
|
38
|
+
function ssrModuleGraph(devServer) {
|
|
39
|
+
return devServer.environments?.ssr?.moduleGraph ?? devServer.moduleGraph;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function directUrl(url) {
|
|
43
|
+
return `${url}${url.includes("?") ? "&" : "?"}direct`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function styleModules(devServer) {
|
|
47
|
+
const graph = ssrModuleGraph(devServer);
|
|
48
|
+
if (!graph) return [];
|
|
49
|
+
return [...graph.urlToModuleMap.entries()]
|
|
50
|
+
.filter(([url]) => SFC_STYLE.test(url) || STYLE_EXTENSION.test(url))
|
|
51
|
+
.map(([url, module]) => ({ url, id: module?.id ?? url }));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function linkTag({ url, id }) {
|
|
55
|
+
return `<link rel="stylesheet" ${STYLE_ATTRIBUTE}="${id}" href="${directUrl(url)}">`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A single-file component's `<style>` block is served with a JavaScript content
|
|
60
|
+
* type even when requested directly, so a browser would refuse it as a
|
|
61
|
+
* stylesheet. They are small; inline them instead. A block that could close its
|
|
62
|
+
* own tag is dropped rather than escaped — CSS has no escape that is valid in
|
|
63
|
+
* every position, and a scoped component rule is not worth the risk.
|
|
64
|
+
*/
|
|
65
|
+
async function inlineTag(devServer, { url, id }) {
|
|
66
|
+
const result = await devServer
|
|
67
|
+
.transformRequest(directUrl(url))
|
|
68
|
+
.catch(() => undefined);
|
|
69
|
+
const css = result?.code;
|
|
70
|
+
if (!css || /<\/style/i.test(css)) return "";
|
|
71
|
+
return `<style ${STYLE_ATTRIBUTE}="${id}">${css}</style>`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Style tags for everything the development render touched, entry sheet first. */
|
|
75
|
+
export async function developmentStyleTags(devServer) {
|
|
76
|
+
const modules = styleModules(devServer);
|
|
77
|
+
const links = [
|
|
78
|
+
{ url: ENTRY_STYLESHEET, id: join(PROJECT_ROOT, "dms-main.css") },
|
|
79
|
+
...modules.filter(({ url }) => !SFC_STYLE.test(url)),
|
|
80
|
+
].map(linkTag);
|
|
81
|
+
const inlined = await Promise.all(
|
|
82
|
+
modules
|
|
83
|
+
.filter(({ url }) => SFC_STYLE.test(url))
|
|
84
|
+
.map((module) => inlineTag(devServer, module)),
|
|
85
|
+
);
|
|
86
|
+
return [...links, ...inlined].join("");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The style tags a document needs, whichever server is answering it: collected
|
|
91
|
+
* from the live module graph in development, read from the build manifest in
|
|
92
|
+
* production.
|
|
93
|
+
*/
|
|
94
|
+
export async function documentStyleTags(devServer, page, template) {
|
|
95
|
+
return devServer
|
|
96
|
+
? developmentStyleTags(devServer)
|
|
97
|
+
: pageModulePreloads(page, template);
|
|
98
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Working out which page a visit to `/` means.
|
|
2
|
+
//
|
|
3
|
+
// The answer lives in the module registry the workspace was materialized with:
|
|
4
|
+
// a module declares `homepage` somewhere in its options, at whatever depth its
|
|
5
|
+
// own configuration shape puts it, and the highest-priority declaration wins —
|
|
6
|
+
// the registry is already sorted. Read once at boot, because the registry is a
|
|
7
|
+
// build artifact and cannot change while the server is up.
|
|
8
|
+
//
|
|
9
|
+
// Split out of server.mjs, whose size the linter caps.
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
15
|
+
const PROJECT_ROOT = fileURLToPath(new URL("../", import.meta.url));
|
|
16
|
+
const REGISTRY_PATH = join(PROJECT_ROOT, "generated-frontend-modules.json");
|
|
17
|
+
const REGISTRY = existsSync(REGISTRY_PATH)
|
|
18
|
+
? JSON.parse(readFileSync(REGISTRY_PATH, "utf8"))
|
|
19
|
+
: { modules: [] };
|
|
20
|
+
|
|
21
|
+
function findHomepage(options) {
|
|
22
|
+
if (!options || typeof options !== "object") return undefined;
|
|
23
|
+
if (typeof options.homepage === "string") return options.homepage;
|
|
24
|
+
return Object.values(options)
|
|
25
|
+
.map(findHomepage)
|
|
26
|
+
.find((homepage) => homepage !== undefined);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const HOMEPAGE =
|
|
30
|
+
REGISTRY.modules
|
|
31
|
+
.map((module) => findHomepage(module.options))
|
|
32
|
+
.find(Boolean) ?? "/";
|
|
@@ -7,6 +7,17 @@ const CLIENT_MANIFEST_PATH = fileURLToPath(
|
|
|
7
7
|
);
|
|
8
8
|
let productionAssetVersion;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Every document and Inertia payload this server writes carries the visitor's
|
|
12
|
+
* own session: the user, the session, and the page tree their permissions
|
|
13
|
+
* allow. `private` is what keeps a shared cache from ever holding it;
|
|
14
|
+
* `no-cache` keeps the browser's own copy from being reused without asking us
|
|
15
|
+
* first, so a revoked session cannot resurface from disk. We stop short of
|
|
16
|
+
* `no-store`, which would additionally forfeit the back-forward cache on every
|
|
17
|
+
* page of a dashboard people navigate constantly.
|
|
18
|
+
*/
|
|
19
|
+
export const PRIVATE_CACHE_CONTROL = "private, no-cache";
|
|
20
|
+
|
|
10
21
|
export function assetVersion() {
|
|
11
22
|
if (process.env.DMS_DEV === "true" || !existsSync(CLIENT_MANIFEST_PATH))
|
|
12
23
|
return "development";
|
|
@@ -32,6 +43,7 @@ export function createInertiaPage(url, props, version = assetVersion()) {
|
|
|
32
43
|
|
|
33
44
|
export function inertiaHeaders(version = assetVersion()) {
|
|
34
45
|
return {
|
|
46
|
+
"cache-control": PRIVATE_CACHE_CONTROL,
|
|
35
47
|
"content-type": "application/json",
|
|
36
48
|
"x-inertia": "true",
|
|
37
49
|
vary: "X-Inertia",
|
|
@@ -39,6 +51,15 @@ export function inertiaHeaders(version = assetVersion()) {
|
|
|
39
51
|
};
|
|
40
52
|
}
|
|
41
53
|
|
|
54
|
+
/** Headers for a rendered document: same cache stance as an Inertia payload. */
|
|
55
|
+
export function htmlHeaders() {
|
|
56
|
+
return {
|
|
57
|
+
"cache-control": PRIVATE_CACHE_CONTROL,
|
|
58
|
+
"content-type": "text/html",
|
|
59
|
+
vary: "X-Inertia",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
42
63
|
export function handleAssetVersionMismatch(request, response) {
|
|
43
64
|
if (
|
|
44
65
|
!request.headers["x-inertia"] ||
|
package/templates/vue/server.mjs
CHANGED
|
@@ -18,14 +18,14 @@ import {
|
|
|
18
18
|
refreshSession,
|
|
19
19
|
} from "./server/auth/routes.mjs";
|
|
20
20
|
import { readSession } from "./server/auth/session.mjs";
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
productionHtmlTemplate,
|
|
24
|
-
} from "./server/client-manifest.mjs";
|
|
21
|
+
import { productionHtmlTemplate } from "./server/client-manifest.mjs";
|
|
22
|
+
import { documentStyleTags } from "./server/dev-styles.mjs";
|
|
25
23
|
import { handleEmailRender } from "./server/email.mjs";
|
|
24
|
+
import { HOMEPAGE } from "./server/homepage.mjs";
|
|
26
25
|
import { handleTester } from "./server/tester.mjs";
|
|
27
26
|
import {
|
|
28
27
|
createInertiaPage,
|
|
28
|
+
htmlHeaders,
|
|
29
29
|
inertiaAppHtml,
|
|
30
30
|
inertiaHeaders,
|
|
31
31
|
redirectFrontendVisit,
|
|
@@ -36,13 +36,6 @@ export * from "./server/inertia.mjs";
|
|
|
36
36
|
const INERTIA_HEADER = "x-inertia";
|
|
37
37
|
const JSON_TYPE = "application/json";
|
|
38
38
|
const PROJECT_ROOT = fileURLToPath(new URL(".", import.meta.url));
|
|
39
|
-
const MODULE_REGISTRY_PATH = join(
|
|
40
|
-
PROJECT_ROOT,
|
|
41
|
-
"generated-frontend-modules.json",
|
|
42
|
-
);
|
|
43
|
-
const MODULE_REGISTRY = existsSync(MODULE_REGISTRY_PATH)
|
|
44
|
-
? JSON.parse(readFileSync(MODULE_REGISTRY_PATH, "utf8"))
|
|
45
|
-
: { modules: [] };
|
|
46
39
|
const MIME_TYPES = {
|
|
47
40
|
".css": "text/css",
|
|
48
41
|
".js": "text/javascript",
|
|
@@ -74,19 +67,6 @@ let vite;
|
|
|
74
67
|
let vitePromise;
|
|
75
68
|
let frontendHttpServer;
|
|
76
69
|
|
|
77
|
-
function findHomepage(options) {
|
|
78
|
-
if (!options || typeof options !== "object") return undefined;
|
|
79
|
-
if (typeof options.homepage === "string") return options.homepage;
|
|
80
|
-
return Object.values(options)
|
|
81
|
-
.map(findHomepage)
|
|
82
|
-
.find((homepage) => homepage !== undefined);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const HOMEPAGE =
|
|
86
|
-
MODULE_REGISTRY.modules
|
|
87
|
-
.map((module) => findHomepage(module.options))
|
|
88
|
-
.find(Boolean) ?? "/";
|
|
89
|
-
|
|
90
70
|
class BackendResponseError extends Error {
|
|
91
71
|
constructor(status) {
|
|
92
72
|
super(`DMS backend returned ${status}`);
|
|
@@ -358,7 +338,7 @@ export async function renderHtml(page, requestUrl, serverFetch) {
|
|
|
358
338
|
}
|
|
359
339
|
if (rendered.redirect)
|
|
360
340
|
return { html: "", status: 200, redirect: rendered.redirect };
|
|
361
|
-
const preloads = devServer
|
|
341
|
+
const preloads = await documentStyleTags(devServer, page, template);
|
|
362
342
|
const html = template
|
|
363
343
|
.replace("<title>Antelope DMS</title>", rendered.head.headTags)
|
|
364
344
|
.replace("<html", `<html ${rendered.head.htmlAttrs}`)
|
|
@@ -410,13 +390,7 @@ async function writeBackendError(error, request, response) {
|
|
|
410
390
|
request.url,
|
|
411
391
|
serverComponentFetch(request),
|
|
412
392
|
);
|
|
413
|
-
return writeContent(
|
|
414
|
-
request,
|
|
415
|
-
response,
|
|
416
|
-
status,
|
|
417
|
-
{ "content-type": "text/html", vary: "X-Inertia" },
|
|
418
|
-
html,
|
|
419
|
-
);
|
|
393
|
+
return writeContent(request, response, status, htmlHeaders(), html);
|
|
420
394
|
}
|
|
421
395
|
const payload =
|
|
422
396
|
error instanceof UpstreamError || error instanceof RequestBodyError
|
|
@@ -481,7 +455,7 @@ export async function handleRequest(request, response) {
|
|
|
481
455
|
request,
|
|
482
456
|
response,
|
|
483
457
|
rendered.status,
|
|
484
|
-
|
|
458
|
+
htmlHeaders(),
|
|
485
459
|
rendered.html,
|
|
486
460
|
);
|
|
487
461
|
}
|
|
@@ -2,7 +2,8 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { basename, resolve } from "node:path";
|
|
3
3
|
import ui from "@nuxt/ui/vite";
|
|
4
4
|
import vue from "@vitejs/plugin-vue";
|
|
5
|
-
import { defineConfig } from "vite";
|
|
5
|
+
import { defineConfig, type Plugin } from "vite";
|
|
6
|
+
import { orderHeadForFirstPaint } from "./head-order.mjs";
|
|
6
7
|
|
|
7
8
|
interface FrontendModuleRegistryEntry {
|
|
8
9
|
id: string;
|
|
@@ -66,6 +67,38 @@ const optimizedDependencies = [
|
|
|
66
67
|
"json-schema-to-zod",
|
|
67
68
|
"striptags",
|
|
68
69
|
];
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Entry points the dependency optimizer crawls at startup.
|
|
73
|
+
*
|
|
74
|
+
* Every materialized module brings its own dependency set — subpath exports and
|
|
75
|
+
* transitive CommonJS included — and none of it can be listed in
|
|
76
|
+
* `optimizedDependencies` by hand, because the modules are only known at
|
|
77
|
+
* materialization time. Leaving the crawl off and letting Vite discover them as
|
|
78
|
+
* pages load meant the first visit to a cold page re-ran the optimizer
|
|
79
|
+
* mid-request: the chunks already in flight answered 504 and the client was
|
|
80
|
+
* told to reload the whole document. Crawling the module sources once, at
|
|
81
|
+
* startup, pays that cost a single time and in a place where it reads as
|
|
82
|
+
* startup rather than as a crash.
|
|
83
|
+
*/
|
|
84
|
+
const optimizerEntries = [
|
|
85
|
+
resolve(__dirname, "main.ts"),
|
|
86
|
+
...moduleRoots.map((root) => resolve(root, "dms.frontend.ts")),
|
|
87
|
+
...frontendSourceRoots.flatMap((root) => [
|
|
88
|
+
resolve(root, "app/**/*.vue"),
|
|
89
|
+
resolve(root, "app/**/*.ts"),
|
|
90
|
+
]),
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
function paintBeforeHydrate(): Plugin {
|
|
94
|
+
return {
|
|
95
|
+
name: "dms-paint-before-hydrate",
|
|
96
|
+
apply: "build",
|
|
97
|
+
enforce: "post",
|
|
98
|
+
transformIndexHtml: { order: "post", handler: orderHeadForFirstPaint },
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
69
102
|
const uiLinkImport = "@nuxt/ui/components/Link.vue";
|
|
70
103
|
const uiInertiaLinkImport = resolve(
|
|
71
104
|
__dirname,
|
|
@@ -131,6 +164,7 @@ export default defineConfig({
|
|
|
131
164
|
vueTemplate: true,
|
|
132
165
|
},
|
|
133
166
|
}),
|
|
167
|
+
paintBeforeHydrate(),
|
|
134
168
|
],
|
|
135
169
|
resolve: {
|
|
136
170
|
dedupe: ["vue", "reka-ui", "@nuxt/ui"],
|
|
@@ -152,8 +186,7 @@ export default defineConfig({
|
|
|
152
186
|
},
|
|
153
187
|
optimizeDeps: {
|
|
154
188
|
include: optimizedDependencies,
|
|
155
|
-
|
|
156
|
-
entries: [],
|
|
189
|
+
entries: optimizerEntries,
|
|
157
190
|
},
|
|
158
191
|
ssr: { noExternal: ["@nuxt/icon", "@nuxt/ui"] },
|
|
159
192
|
server: { strictPort: true, allowedHosts: [".onamp.dev"] },
|