@nuxt/hints 0.0.0 → 1.0.0-alpha.1
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/LICENSE +21 -0
- package/README.md +136 -0
- package/dist/client/200.html +1 -0
- package/dist/client/404.html +1 -0
- package/dist/client/_nuxt/2EtD6e53.js +1 -0
- package/dist/client/_nuxt/3e1v2bzS.js +1 -0
- package/dist/client/_nuxt/5i3qLPDT.js +1 -0
- package/dist/client/_nuxt/B0m2ddpp.js +1 -0
- package/dist/client/_nuxt/B1dDrJ26.js +1 -0
- package/dist/client/_nuxt/B6aJPvgy.js +1 -0
- package/dist/client/_nuxt/B7mTdjB0.js +1 -0
- package/dist/client/_nuxt/BEDo0Tqx.js +1 -0
- package/dist/client/_nuxt/BGJmEYvX.js +1 -0
- package/dist/client/_nuxt/BHrmToEH.js +1 -0
- package/dist/client/_nuxt/BIGW1oBm.js +1 -0
- package/dist/client/_nuxt/BJtxVCQ_.js +1 -0
- package/dist/client/_nuxt/BLmx8bSh.js +1 -0
- package/dist/client/_nuxt/BLtJtn59.js +1 -0
- package/dist/client/_nuxt/BPQ3VLAy.js +1 -0
- package/dist/client/_nuxt/BUGFbzbB.js +6 -0
- package/dist/client/_nuxt/BXkSAIEj.js +1 -0
- package/dist/client/_nuxt/BahsQGtb.js +19 -0
- package/dist/client/_nuxt/BfHTSMKl.js +1 -0
- package/dist/client/_nuxt/BfjtVDDH.js +1 -0
- package/dist/client/_nuxt/BgDCqdQA.js +1 -0
- package/dist/client/_nuxt/Bgk_gK97.js +5 -0
- package/dist/client/_nuxt/Bkuqu6BP.js +1 -0
- package/dist/client/_nuxt/Boah4cwZ.js +1 -0
- package/dist/client/_nuxt/Bp3cYrEr.js +1 -0
- package/dist/client/_nuxt/Br6cN0cg.js +1 -0
- package/dist/client/_nuxt/BthQWCQV.js +1 -0
- package/dist/client/_nuxt/Buea-lGh.js +1 -0
- package/dist/client/_nuxt/BvKcRjC_.js +1 -0
- package/dist/client/_nuxt/Bw305WKR.js +1 -0
- package/dist/client/_nuxt/Bynn4rkp.js +1 -0
- package/dist/client/_nuxt/ByztXxam.js +1 -0
- package/dist/client/_nuxt/BzJJZx-M.js +1 -0
- package/dist/client/_nuxt/C-Jbm3Hp.js +1 -0
- package/dist/client/_nuxt/C39BiMTA.js +1 -0
- package/dist/client/_nuxt/C8M2exoo.js +1 -0
- package/dist/client/_nuxt/C9dUb6Cb.js +1 -0
- package/dist/client/_nuxt/C9oPPf7i.js +1 -0
- package/dist/client/_nuxt/C9tS-k6U.js +1 -0
- package/dist/client/_nuxt/CDVJQ6XC.js +1 -0
- package/dist/client/_nuxt/CEG3Q07z.js +1 -0
- package/dist/client/_nuxt/CFHQjOhq.js +1 -0
- package/dist/client/_nuxt/CG6Dc4jp.js +1 -0
- package/dist/client/_nuxt/CH1njM8p.js +1 -0
- package/dist/client/_nuxt/CHoFPrvn.js +1 -0
- package/dist/client/_nuxt/CKUSRNTx.js +1 -0
- package/dist/client/_nuxt/CMFEf1DS.js +1 -0
- package/dist/client/_nuxt/COt5Ahok.js +1 -0
- package/dist/client/_nuxt/CS3Unz2-.js +1 -0
- package/dist/client/_nuxt/CTRr51gU.js +1 -0
- package/dist/client/_nuxt/CVO1_9PV.js +1 -0
- package/dist/client/_nuxt/CVdnzihN.js +1 -0
- package/dist/client/_nuxt/CXZktZb0.js +1 -0
- package/dist/client/_nuxt/CXtECtnM.js +1 -0
- package/dist/client/_nuxt/CYsAdtH9.js +1 -0
- package/dist/client/_nuxt/C_d8ykrU.js +9 -0
- package/dist/client/_nuxt/CafNBF8u.js +1 -0
- package/dist/client/_nuxt/CbfX1IO0.js +1 -0
- package/dist/client/_nuxt/CfQXZHmo.js +1 -0
- package/dist/client/_nuxt/CgjmDhmW.js +1 -0
- package/dist/client/_nuxt/Cj5Yp3dK.js +1 -0
- package/dist/client/_nuxt/CkXjmgJE.js +1 -0
- package/dist/client/_nuxt/Cl0AqbOI.js +1 -0
- package/dist/client/_nuxt/Cm3UrAx6.js +1 -0
- package/dist/client/_nuxt/CmIQRyeF.js +1 -0
- package/dist/client/_nuxt/Cmh6b_Ma.js +1 -0
- package/dist/client/_nuxt/CnK8MTSM.js +1 -0
- package/dist/client/_nuxt/CnnebwVN.js +1 -0
- package/dist/client/_nuxt/Cp-IABpG.js +1 -0
- package/dist/client/_nuxt/Csfq5Kiy.js +1 -0
- package/dist/client/_nuxt/CufHLc7y.js +1 -0
- package/dist/client/_nuxt/Cuk6v7N8.js +1 -0
- package/dist/client/_nuxt/Cv9koXgw.js +1 -0
- package/dist/client/_nuxt/Cvjx9yec.js +1 -0
- package/dist/client/_nuxt/CxMWoQ9E.js +1 -0
- package/dist/client/_nuxt/CxbxFI8M.js +1 -0
- package/dist/client/_nuxt/CyktbL80.js +1 -0
- package/dist/client/_nuxt/CylS5w8V.js +1 -0
- package/dist/client/_nuxt/D-2ljcwZ.js +1 -0
- package/dist/client/_nuxt/D0r3Knsf.js +1 -0
- package/dist/client/_nuxt/D1fewSvT.js +1 -0
- package/dist/client/_nuxt/D4h5O-jR.js +1 -0
- package/dist/client/_nuxt/D5KoaKCx.js +1 -0
- package/dist/client/_nuxt/D7VJQ_DO.js +1 -0
- package/dist/client/_nuxt/D7oLnXFd.js +1 -0
- package/dist/client/_nuxt/D87Tk5Gz.js +1 -0
- package/dist/client/_nuxt/DAi9KRSo.js +1 -0
- package/dist/client/_nuxt/DDBovgxi.js +1 -0
- package/dist/client/_nuxt/DFWUc33u.js +1 -0
- package/dist/client/_nuxt/DGP4VlC8.js +1 -0
- package/dist/client/_nuxt/DGztddWO.js +1 -0
- package/dist/client/_nuxt/DH5Ifo-i.js +1 -0
- package/dist/client/_nuxt/DHJKELXO.js +1 -0
- package/dist/client/_nuxt/DNMeEaky.js +1 -0
- package/dist/client/_nuxt/DPfMkruS.js +1 -0
- package/dist/client/_nuxt/DQyhUUbL.js +1 -0
- package/dist/client/_nuxt/DRw_LuNl.js +1 -0
- package/dist/client/_nuxt/DU1UobuO.js +1 -0
- package/dist/client/_nuxt/DUrsayuJ.js +1 -0
- package/dist/client/_nuxt/DUszq2jm.js +1 -0
- package/dist/client/_nuxt/DVMEJ2y_.js +1 -0
- package/dist/client/_nuxt/DWedfzmr.js +1 -0
- package/dist/client/_nuxt/DXbdFlpD.js +1 -0
- package/dist/client/_nuxt/DZxFcAj9.js +1 -0
- package/dist/client/_nuxt/DcaNXYhu.js +1 -0
- package/dist/client/_nuxt/Ddv68eIx.js +1 -0
- package/dist/client/_nuxt/Des-eS-w.js +1 -0
- package/dist/client/_nuxt/DiinP2Uv.js +1 -0
- package/dist/client/_nuxt/DnULxvSX.js +1 -0
- package/dist/client/_nuxt/DqwNpetd.js +1 -0
- package/dist/client/_nuxt/Dx-B1_4e.js +1 -0
- package/dist/client/_nuxt/DxNHbxmM.js +1 -0
- package/dist/client/_nuxt/DxSwrfjg.js +1 -0
- package/dist/client/_nuxt/E3gJ1_iC.js +1 -0
- package/dist/client/_nuxt/GsRaNv29.js +1 -0
- package/dist/client/_nuxt/L9t79GZl.js +1 -0
- package/dist/client/_nuxt/MzD3tlZU.js +1 -0
- package/dist/client/_nuxt/NleAzG8P.js +1 -0
- package/dist/client/_nuxt/PoHY5YXO.js +1 -0
- package/dist/client/_nuxt/TsXTqZ29.js +1 -0
- package/dist/client/_nuxt/Xqf5ue2O.js +1 -0
- package/dist/client/_nuxt/Y-IJRkoC.js +1 -0
- package/dist/client/_nuxt/Yzrsuije.js +1 -0
- package/dist/client/_nuxt/bN70gL4F.js +1 -0
- package/dist/client/_nuxt/builds/latest.json +1 -0
- package/dist/client/_nuxt/builds/meta/edb115ef-e91c-4b8b-a8bc-7091fc0b356e.json +1 -0
- package/dist/client/_nuxt/eOWES_5F.js +1 -0
- package/dist/client/_nuxt/entry.BPIf5bpe.css +1 -0
- package/dist/client/_nuxt/error-404.BZAFKftT.css +1 -0
- package/dist/client/_nuxt/error-500.BWF-6BkQ.css +1 -0
- package/dist/client/_nuxt/fuZLfV_i.js +1 -0
- package/dist/client/_nuxt/g9-lgVsj.js +1 -0
- package/dist/client/_nuxt/hJgmCMqR.js +1 -0
- package/dist/client/_nuxt/hegEt444.js +1 -0
- package/dist/client/_nuxt/m17aaUwq.js +1 -0
- package/dist/client/_nuxt/tqtdBwhO.js +1 -0
- package/dist/client/_nuxt/wDzz0qaB.js +1 -0
- package/dist/client/hydration/index.html +1 -0
- package/dist/client/index.html +1 -0
- package/dist/client/third-party-scripts/index.html +1 -0
- package/dist/client/web-vitals/index.html +1 -0
- package/dist/module.d.mts +9 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +105 -0
- package/dist/runtime/components/nuxt-island.d.ts +137 -0
- package/dist/runtime/components/nuxt-island.js +14 -0
- package/dist/runtime/composables/devtoolsData.d.ts +7 -0
- package/dist/runtime/composables/devtoolsData.js +12 -0
- package/dist/runtime/composables/hydration.d.ts +5 -0
- package/dist/runtime/composables/hydration.js +46 -0
- package/dist/runtime/plugins/hydration/plugin.client.d.ts +2 -0
- package/dist/runtime/plugins/hydration/plugin.client.js +11 -0
- package/dist/runtime/plugins/third-party-scripts/nitro.plugin.d.ts +2 -0
- package/dist/runtime/plugins/third-party-scripts/nitro.plugin.js +48 -0
- package/dist/runtime/plugins/third-party-scripts/plugin.client.d.ts +2 -0
- package/dist/runtime/plugins/third-party-scripts/plugin.client.js +71 -0
- package/dist/runtime/plugins/vue-tracer-state.client.d.ts +2 -0
- package/dist/runtime/plugins/vue-tracer-state.client.js +7 -0
- package/dist/runtime/plugins/web-vitals/plugin.client.d.ts +13 -0
- package/dist/runtime/plugins/web-vitals/plugin.client.js +137 -0
- package/dist/runtime/plugins/web-vitals/utils.d.ts +35 -0
- package/dist/runtime/plugins/web-vitals/utils.js +13 -0
- package/dist/runtime/types.d.ts +48 -0
- package/dist/types.d.mts +3 -0
- package/package.json +57 -2
- /package/dist/{.gitkeep → client/_nuxt/third-party-scripts.tn0RQdqM.css} +0 -0
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { addDevServerHandler, defineNuxtModule, createResolver, addPlugin, addBuildPlugin, addComponent, addServerPlugin } from '@nuxt/kit';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { eventHandler, proxyRequest } from 'h3';
|
|
4
|
+
import MagicString from 'magic-string';
|
|
5
|
+
import { createUnplugin } from 'unplugin';
|
|
6
|
+
|
|
7
|
+
const DEVTOOLS_UI_ROUTE = "/__nuxt-hints";
|
|
8
|
+
const DEVTOOLS_UI_LOCAL_PORT = 3300;
|
|
9
|
+
function setupDevToolsUI(nuxt, resolver) {
|
|
10
|
+
const clientPath = resolver.resolve("./client");
|
|
11
|
+
const isProductionBuild = existsSync(clientPath);
|
|
12
|
+
if (isProductionBuild) {
|
|
13
|
+
nuxt.hook("vite:serverCreated", async (server) => {
|
|
14
|
+
const sirv = await import('sirv').then((r) => r.default || r);
|
|
15
|
+
server.middlewares.use(
|
|
16
|
+
DEVTOOLS_UI_ROUTE,
|
|
17
|
+
sirv(clientPath, { dev: true, single: true })
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
} else {
|
|
21
|
+
addDevServerHandler({
|
|
22
|
+
route: DEVTOOLS_UI_ROUTE,
|
|
23
|
+
handler: eventHandler((e) => {
|
|
24
|
+
return proxyRequest(e, "http://localhost:" + DEVTOOLS_UI_LOCAL_PORT + DEVTOOLS_UI_ROUTE + e.path);
|
|
25
|
+
})
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
nuxt.hook("devtools:customTabs", (tabs) => {
|
|
29
|
+
tabs.push({
|
|
30
|
+
name: "hints",
|
|
31
|
+
title: "Hints",
|
|
32
|
+
icon: "carbon:idea",
|
|
33
|
+
category: "analyze",
|
|
34
|
+
view: {
|
|
35
|
+
type: "iframe",
|
|
36
|
+
src: DEVTOOLS_UI_ROUTE
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const InjectHydrationPlugin = createUnplugin(() => {
|
|
43
|
+
return {
|
|
44
|
+
name: "@nuxt/hints:inject-hydration-check",
|
|
45
|
+
enforce: "pre",
|
|
46
|
+
transformInclude(id) {
|
|
47
|
+
return id.endsWith(".vue") && !id.includes("node_modules");
|
|
48
|
+
},
|
|
49
|
+
transform(code) {
|
|
50
|
+
const m = new MagicString(code);
|
|
51
|
+
const re = /<script\s+setup[^>]*>/g;
|
|
52
|
+
const match = re.exec(code);
|
|
53
|
+
if (!match) {
|
|
54
|
+
return code;
|
|
55
|
+
}
|
|
56
|
+
m.appendRight(
|
|
57
|
+
match.index + match[0].length,
|
|
58
|
+
`
|
|
59
|
+
import { useHydrationCheck } from '@nuxt/hints/runtime/composables/hydration'
|
|
60
|
+
useHydrationCheck();`
|
|
61
|
+
);
|
|
62
|
+
return {
|
|
63
|
+
code: m.toString(),
|
|
64
|
+
map: m.generateMap({ hires: true })
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const module = defineNuxtModule({
|
|
71
|
+
meta: {
|
|
72
|
+
name: "@nuxt/hints",
|
|
73
|
+
configKey: "hints"
|
|
74
|
+
},
|
|
75
|
+
defaults: {
|
|
76
|
+
devtools: true
|
|
77
|
+
},
|
|
78
|
+
setup(options, nuxt) {
|
|
79
|
+
if (!nuxt.options.dev) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const resolver = createResolver(import.meta.url);
|
|
83
|
+
addPlugin(resolver.resolve("./runtime/plugins/web-vitals/plugin.client"));
|
|
84
|
+
addPlugin(resolver.resolve("./runtime/plugins/hydration/plugin.client"));
|
|
85
|
+
addBuildPlugin(InjectHydrationPlugin);
|
|
86
|
+
addComponent({
|
|
87
|
+
name: "NuxtIsland",
|
|
88
|
+
filePath: resolver.resolve("./runtime/components/nuxt-island"),
|
|
89
|
+
priority: 1e3
|
|
90
|
+
});
|
|
91
|
+
addPlugin(resolver.resolve("./runtime/plugins/third-party-scripts/plugin.client"));
|
|
92
|
+
addServerPlugin(resolver.resolve("./runtime/plugins/third-party-scripts/nitro.plugin"));
|
|
93
|
+
nuxt.hook("prepare:types", ({ references }) => {
|
|
94
|
+
references.push({
|
|
95
|
+
types: resolver.resolve("./runtime/types.d.ts")
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
if (options.devtools) {
|
|
99
|
+
setupDevToolsUI(nuxt, resolver);
|
|
100
|
+
addPlugin(resolver.resolve("./runtime/plugins/vue-tracer-state.client"));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export { module as default };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
declare const HintsNuxtIsland: {
|
|
2
|
+
new (...args: any[]): import("vue").CreateComponentPublicInstanceWithMixins<Readonly<import("vue").ExtractPropTypes<{
|
|
3
|
+
name: {
|
|
4
|
+
type: StringConstructor;
|
|
5
|
+
required: true;
|
|
6
|
+
};
|
|
7
|
+
lazy: BooleanConstructor;
|
|
8
|
+
props: {
|
|
9
|
+
type: ObjectConstructor;
|
|
10
|
+
default: () => undefined;
|
|
11
|
+
};
|
|
12
|
+
context: {
|
|
13
|
+
type: ObjectConstructor;
|
|
14
|
+
default: () => {};
|
|
15
|
+
};
|
|
16
|
+
scopeId: {
|
|
17
|
+
type: import("vue").PropType<string | undefined | null>;
|
|
18
|
+
default: () => undefined;
|
|
19
|
+
};
|
|
20
|
+
source: {
|
|
21
|
+
type: StringConstructor;
|
|
22
|
+
default: () => undefined;
|
|
23
|
+
};
|
|
24
|
+
dangerouslyLoadClientComponents: {
|
|
25
|
+
type: BooleanConstructor;
|
|
26
|
+
default: boolean;
|
|
27
|
+
};
|
|
28
|
+
}>> & Readonly<{
|
|
29
|
+
onError?: ((...args: any[]) => any) | undefined;
|
|
30
|
+
}>, (_ctx: any, _cache: any) => (import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
31
|
+
[key: string]: any;
|
|
32
|
+
}> | import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
33
|
+
[key: string]: any;
|
|
34
|
+
}>[])[] | import("vue").VNode<any, any, {
|
|
35
|
+
[key: string]: any;
|
|
36
|
+
}>[], {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "error"[], import("vue").PublicProps, {
|
|
37
|
+
props: Record<string, any>;
|
|
38
|
+
source: string;
|
|
39
|
+
scopeId: string | null | undefined;
|
|
40
|
+
lazy: boolean;
|
|
41
|
+
context: Record<string, any>;
|
|
42
|
+
dangerouslyLoadClientComponents: boolean;
|
|
43
|
+
}, true, {}, {}, import("vue").GlobalComponents, import("vue").GlobalDirectives, string, {}, any, import("vue").ComponentProvideOptions, {
|
|
44
|
+
P: {};
|
|
45
|
+
B: {};
|
|
46
|
+
D: {};
|
|
47
|
+
C: {};
|
|
48
|
+
M: {};
|
|
49
|
+
Defaults: {};
|
|
50
|
+
}, Readonly<import("vue").ExtractPropTypes<{
|
|
51
|
+
name: {
|
|
52
|
+
type: StringConstructor;
|
|
53
|
+
required: true;
|
|
54
|
+
};
|
|
55
|
+
lazy: BooleanConstructor;
|
|
56
|
+
props: {
|
|
57
|
+
type: ObjectConstructor;
|
|
58
|
+
default: () => undefined;
|
|
59
|
+
};
|
|
60
|
+
context: {
|
|
61
|
+
type: ObjectConstructor;
|
|
62
|
+
default: () => {};
|
|
63
|
+
};
|
|
64
|
+
scopeId: {
|
|
65
|
+
type: import("vue").PropType<string | undefined | null>;
|
|
66
|
+
default: () => undefined;
|
|
67
|
+
};
|
|
68
|
+
source: {
|
|
69
|
+
type: StringConstructor;
|
|
70
|
+
default: () => undefined;
|
|
71
|
+
};
|
|
72
|
+
dangerouslyLoadClientComponents: {
|
|
73
|
+
type: BooleanConstructor;
|
|
74
|
+
default: boolean;
|
|
75
|
+
};
|
|
76
|
+
}>> & Readonly<{
|
|
77
|
+
onError?: ((...args: any[]) => any) | undefined;
|
|
78
|
+
}>, (_ctx: any, _cache: any) => (import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
79
|
+
[key: string]: any;
|
|
80
|
+
}> | import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
81
|
+
[key: string]: any;
|
|
82
|
+
}>[])[] | import("vue").VNode<any, any, {
|
|
83
|
+
[key: string]: any;
|
|
84
|
+
}>[], {}, {}, {}, {
|
|
85
|
+
props: Record<string, any>;
|
|
86
|
+
source: string;
|
|
87
|
+
scopeId: string | null | undefined;
|
|
88
|
+
lazy: boolean;
|
|
89
|
+
context: Record<string, any>;
|
|
90
|
+
dangerouslyLoadClientComponents: boolean;
|
|
91
|
+
}>;
|
|
92
|
+
__isFragment?: never;
|
|
93
|
+
__isTeleport?: never;
|
|
94
|
+
__isSuspense?: never;
|
|
95
|
+
} & import("vue").ComponentOptionsBase<Readonly<import("vue").ExtractPropTypes<{
|
|
96
|
+
name: {
|
|
97
|
+
type: StringConstructor;
|
|
98
|
+
required: true;
|
|
99
|
+
};
|
|
100
|
+
lazy: BooleanConstructor;
|
|
101
|
+
props: {
|
|
102
|
+
type: ObjectConstructor;
|
|
103
|
+
default: () => undefined;
|
|
104
|
+
};
|
|
105
|
+
context: {
|
|
106
|
+
type: ObjectConstructor;
|
|
107
|
+
default: () => {};
|
|
108
|
+
};
|
|
109
|
+
scopeId: {
|
|
110
|
+
type: import("vue").PropType<string | undefined | null>;
|
|
111
|
+
default: () => undefined;
|
|
112
|
+
};
|
|
113
|
+
source: {
|
|
114
|
+
type: StringConstructor;
|
|
115
|
+
default: () => undefined;
|
|
116
|
+
};
|
|
117
|
+
dangerouslyLoadClientComponents: {
|
|
118
|
+
type: BooleanConstructor;
|
|
119
|
+
default: boolean;
|
|
120
|
+
};
|
|
121
|
+
}>> & Readonly<{
|
|
122
|
+
onError?: ((...args: any[]) => any) | undefined;
|
|
123
|
+
}>, (_ctx: any, _cache: any) => (import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
124
|
+
[key: string]: any;
|
|
125
|
+
}> | import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
126
|
+
[key: string]: any;
|
|
127
|
+
}>[])[] | import("vue").VNode<any, any, {
|
|
128
|
+
[key: string]: any;
|
|
129
|
+
}>[], {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "error"[], "error", {
|
|
130
|
+
props: Record<string, any>;
|
|
131
|
+
source: string;
|
|
132
|
+
scopeId: string | null | undefined;
|
|
133
|
+
lazy: boolean;
|
|
134
|
+
context: Record<string, any>;
|
|
135
|
+
dangerouslyLoadClientComponents: boolean;
|
|
136
|
+
}, {}, string, {}, import("vue").GlobalComponents, import("vue").GlobalDirectives, string, import("vue").ComponentProvideOptions> & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps;
|
|
137
|
+
export default HintsNuxtIsland;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import NuxtIsland from "#app/components/nuxt-island";
|
|
2
|
+
import { useNuxtApp } from "#imports";
|
|
3
|
+
const originalSetup = NuxtIsland.setup;
|
|
4
|
+
const HintsNuxtIsland = Object.assign({}, NuxtIsland, {
|
|
5
|
+
setup(props, ctx) {
|
|
6
|
+
if (useNuxtApp().ssrContext?.islandContext) {
|
|
7
|
+
console.warn(
|
|
8
|
+
`[@nuxt/hints] Nesting islands within islands is not recommanded for performance reasons. This leads to waterfall calls.`
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
return originalSetup(props, ctx);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
export default HintsNuxtIsland;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ImagePerformanceIssueDetails } from '../plugins/web-vitals/utils.js';
|
|
2
|
+
export type ImagePerformanceData = {
|
|
3
|
+
componentLocation: string | undefined;
|
|
4
|
+
issues: ImagePerformanceIssueDetails[];
|
|
5
|
+
element: HTMLImageElement | HTMLElement;
|
|
6
|
+
};
|
|
7
|
+
export declare function useHintIssues(): unknown;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
import { useNuxtApp } from "#app";
|
|
3
|
+
export function useHintIssues() {
|
|
4
|
+
const nuxtApp = useNuxtApp();
|
|
5
|
+
if (nuxtApp.__hintsPerformances) {
|
|
6
|
+
return nuxtApp.__hintsPerformances;
|
|
7
|
+
}
|
|
8
|
+
nuxtApp.__hintsPerformances = {
|
|
9
|
+
imagePerformances: ref([])
|
|
10
|
+
};
|
|
11
|
+
return nuxtApp.__hintsPerformances;
|
|
12
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getCurrentInstance, onMounted } from "vue";
|
|
2
|
+
import { useNuxtApp } from "#imports";
|
|
3
|
+
function formatHTML(html) {
|
|
4
|
+
if (!html) return "";
|
|
5
|
+
let formatted = "";
|
|
6
|
+
let indent = 0;
|
|
7
|
+
const tags = html.split(/(<\/?[^>]+>)/g);
|
|
8
|
+
for (const tag of tags) {
|
|
9
|
+
if (!tag.trim()) continue;
|
|
10
|
+
if (tag.startsWith("</")) {
|
|
11
|
+
indent--;
|
|
12
|
+
formatted += "\n" + " ".repeat(Math.max(0, indent)) + tag;
|
|
13
|
+
} else if (tag.startsWith("<") && !tag.endsWith("/>") && !tag.includes("<!")) {
|
|
14
|
+
formatted += "\n" + " ".repeat(indent) + tag;
|
|
15
|
+
indent++;
|
|
16
|
+
} else if (tag.startsWith("<")) {
|
|
17
|
+
formatted += "\n" + " ".repeat(indent) + tag;
|
|
18
|
+
} else {
|
|
19
|
+
formatted += "\n" + " ".repeat(indent) + tag.trim();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return formatted.trim();
|
|
23
|
+
}
|
|
24
|
+
export function useHydrationCheck() {
|
|
25
|
+
if (import.meta.server) return;
|
|
26
|
+
const nuxtApp = useNuxtApp();
|
|
27
|
+
if (!nuxtApp.isHydrating) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const instance = getCurrentInstance();
|
|
31
|
+
if (!instance) return;
|
|
32
|
+
const htmlPrehydration = formatHTML(instance.vnode.el?.outerHTML);
|
|
33
|
+
const vnodePrehydration = instance.vnode;
|
|
34
|
+
onMounted(() => {
|
|
35
|
+
const htmlPostHydration = formatHTML(instance.vnode.el?.outerHTML);
|
|
36
|
+
if (htmlPrehydration !== htmlPostHydration) {
|
|
37
|
+
nuxtApp.__hints.hydration.push({
|
|
38
|
+
instance,
|
|
39
|
+
vnode: vnodePrehydration,
|
|
40
|
+
htmlPreHydration: htmlPrehydration,
|
|
41
|
+
htmlPostHydration
|
|
42
|
+
});
|
|
43
|
+
console.warn(`[nuxt/hints:hydration] Component ${instance.type.name ?? instance.type.displayName ?? instance.type.__name ?? instance.type.__file} seems to have different html pre and post-hydration. Please make sure you don't have any hydration issue.`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useNuxtApp } from "#imports";
|
|
2
|
+
import { defu } from "defu";
|
|
3
|
+
export default defineNuxtPlugin({
|
|
4
|
+
name: "@nuxt/hints:hydration",
|
|
5
|
+
setup() {
|
|
6
|
+
const nuxtApp = useNuxtApp();
|
|
7
|
+
nuxtApp.__hints = defu(nuxtApp.__hints, {
|
|
8
|
+
hydration: []
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export default function(nitroApp) {
|
|
2
|
+
nitroApp.hooks.hook("render:html", ({ head }) => {
|
|
3
|
+
head.unshift(`
|
|
4
|
+
<script>
|
|
5
|
+
window.__hints_TPC_start_time = Date.now();
|
|
6
|
+
|
|
7
|
+
function __hints_TPC_saveTime(script, startTime) {
|
|
8
|
+
script.__hints_TPC_end_time = Date.now();
|
|
9
|
+
const scriptStartTime = startTime || script.__hints_TPC_start_time || window.__hints_TPC_start_time;
|
|
10
|
+
|
|
11
|
+
const resourceEntries = performance.getEntriesByName(script.src)
|
|
12
|
+
const scriptEntry = resourceEntries.find(entry => entry.name === script.src)
|
|
13
|
+
|
|
14
|
+
if (scriptEntry) {
|
|
15
|
+
// Calculate parse + execute time using modern API
|
|
16
|
+
const navigationEntry = performance.getEntriesByType('navigation')[0]
|
|
17
|
+
const navigationStart = navigationEntry ? performance.timeOrigin : performance.timeOrigin
|
|
18
|
+
|
|
19
|
+
script.requestTime = (scriptEntry.responseStart - scriptEntry.requestStart);
|
|
20
|
+
script.downloadTime = (scriptEntry.responseEnd - scriptEntry.responseStart);
|
|
21
|
+
script.totalNetworkTime = (scriptEntry.responseEnd - scriptEntry.startTime);
|
|
22
|
+
script.parseExecuteTime = script.__hints_TPC_end_time - (navigationStart + scriptEntry.responseEnd);
|
|
23
|
+
script.loaded = true;
|
|
24
|
+
console.log('[@nuxt/hints]: \u{1F4CA} Detailed timing for', script.src, {
|
|
25
|
+
'Request': script.requestTime.toFixed(2) + 'ms',
|
|
26
|
+
'Download': script.downloadTime.toFixed(2) + 'ms',
|
|
27
|
+
'Total Network': script.totalNetworkTime.toFixed(2) + 'ms',
|
|
28
|
+
'Parse + Execute': script.parseExecuteTime.toFixed(2) + 'ms'
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
<\/script>
|
|
33
|
+
`);
|
|
34
|
+
head.push(`
|
|
35
|
+
<script>
|
|
36
|
+
for (const script of document.scripts) {
|
|
37
|
+
if (script.src && !script.src.startsWith(window.location.origin)) {
|
|
38
|
+
script.__hints_TPC_start_time = window.__hints_TPC_start_time || Date.now();
|
|
39
|
+
script.onload = function(_) {
|
|
40
|
+
__hints_TPC_saveTime(script, script.__hints_TPC_start_time);
|
|
41
|
+
}
|
|
42
|
+
__hints_TPC_saveTime(script, script.__hints_TPC_start_time);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
<\/script>
|
|
46
|
+
`);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { defineNuxtPlugin, ref, useNuxtApp } from "#imports";
|
|
2
|
+
export default defineNuxtPlugin({
|
|
3
|
+
name: "nuxt-hints:third-party-scripts",
|
|
4
|
+
setup() {
|
|
5
|
+
const nuxtApp = useNuxtApp();
|
|
6
|
+
const scripts = nuxtApp.__hints_tpc = ref([]);
|
|
7
|
+
const isUsingNuxtScripts = !!nuxtApp.$scripts;
|
|
8
|
+
nuxtApp.hook("hints:scripts:added", (script) => {
|
|
9
|
+
scripts.value.push({ element: script, loaded: false });
|
|
10
|
+
});
|
|
11
|
+
nuxtApp.hook("hints:scripts:loaded", (script) => {
|
|
12
|
+
const existingScript = scripts.value.find((s) => s.element === script);
|
|
13
|
+
if (existingScript) {
|
|
14
|
+
existingScript.loaded = true;
|
|
15
|
+
} else {
|
|
16
|
+
console.warn(`[@nuxt/hints]: Script loaded event received for a script not tracked: ${script.src}. Please open an issue with a minimal reproduction if you think this is a bug.`);
|
|
17
|
+
scripts.value.push({ element: script, loaded: true });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
nuxtApp.hooks.hookOnce("app:mounted", () => {
|
|
21
|
+
let hasThirdPartyScript = false;
|
|
22
|
+
for (const script of document.scripts) {
|
|
23
|
+
if (script.src && !script.src.startsWith(window.location.origin)) {
|
|
24
|
+
hasThirdPartyScript = true;
|
|
25
|
+
onScriptAdded(script);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (hasThirdPartyScript && !isUsingNuxtScripts) {
|
|
29
|
+
console.info("\u2139\uFE0F [@nuxt/hints]: Third-party scripts detected on page load: consider using @nuxt/scripts");
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
const observer = new MutationObserver((mutations) => {
|
|
33
|
+
for (const mutation of mutations) {
|
|
34
|
+
if (mutation.type === "childList") {
|
|
35
|
+
for (const node of mutation.addedNodes) {
|
|
36
|
+
if (isScript(node) && node.src && !node.src.startsWith(window.location.origin)) {
|
|
37
|
+
onScriptAdded(node);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
observer.observe(document.documentElement, {
|
|
44
|
+
childList: true,
|
|
45
|
+
subtree: true
|
|
46
|
+
});
|
|
47
|
+
function onScriptAdded(script) {
|
|
48
|
+
if (script.src.startsWith("chrome-extension")) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!script.crossOrigin) {
|
|
52
|
+
console.warn(`[@nuxt/hints]: Third-party script "${script.src}" is missing crossorigin attribute. Consider adding crossorigin="anonymous" for better security and error reporting.`);
|
|
53
|
+
}
|
|
54
|
+
nuxtApp.callHook("hints:scripts:added", script).then(() => {
|
|
55
|
+
if (!script.loaded) {
|
|
56
|
+
script.onload = () => {
|
|
57
|
+
window.__hints_TPC_saveTime(script, script.__hints_TPC_start_time);
|
|
58
|
+
nuxtApp.callHook("hints:scripts:loaded", script);
|
|
59
|
+
};
|
|
60
|
+
} else {
|
|
61
|
+
window.__hints_TPC_saveTime(script, script.__hints_TPC_start_time);
|
|
62
|
+
nuxtApp.callHook("hints:scripts:loaded", script);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
console.info(`\u2139\uFE0F [@nuxt/hints]: Dynamically added third-party script detected: ${script.src}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
function isScript(node) {
|
|
70
|
+
return node.nodeName === "SCRIPT";
|
|
71
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as tracerOverlay from "vite-plugin-vue-tracer/client/overlay";
|
|
2
|
+
import * as tracerRecord from "vite-plugin-vue-tracer/client/record";
|
|
3
|
+
import { defineNuxtPlugin } from "#imports";
|
|
4
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
5
|
+
nuxtApp.__tracerOverlay = tracerOverlay;
|
|
6
|
+
nuxtApp.__tracerRecord = tracerRecord;
|
|
7
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface PerformanceEntry {
|
|
3
|
+
element?: HTMLElement;
|
|
4
|
+
sources?: {
|
|
5
|
+
node: HTMLElement;
|
|
6
|
+
currentRect: DOMRect;
|
|
7
|
+
previousRect: DOMRect;
|
|
8
|
+
}[];
|
|
9
|
+
value?: number;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
|
|
13
|
+
export default _default;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useNuxtApp } from "#imports";
|
|
2
|
+
import { onINP, onLCP, onCLS } from "web-vitals/attribution";
|
|
3
|
+
import { defu } from "defu";
|
|
4
|
+
import { ref } from "vue";
|
|
5
|
+
function isImgElement(element) {
|
|
6
|
+
return element instanceof Element && element.tagName === "IMG";
|
|
7
|
+
}
|
|
8
|
+
export default defineNuxtPlugin({
|
|
9
|
+
name: "nuxt-hints:performance",
|
|
10
|
+
setup() {
|
|
11
|
+
const nuxtApp = useNuxtApp();
|
|
12
|
+
nuxtApp.__hints = defu(nuxtApp.__hints, {
|
|
13
|
+
webvitals: {
|
|
14
|
+
lcp: ref([]),
|
|
15
|
+
inp: ref([]),
|
|
16
|
+
cls: ref([])
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
nuxtApp.hook("hints:webvitals:sync", (webvitals) => {
|
|
20
|
+
webvitals.lcp.value = [...nuxtApp.__hints.webvitals.lcp.value];
|
|
21
|
+
webvitals.inp.value = [...nuxtApp.__hints.webvitals.inp.value];
|
|
22
|
+
webvitals.cls.value = [...nuxtApp.__hints.webvitals.cls.value];
|
|
23
|
+
});
|
|
24
|
+
nuxtApp.hook("app:mounted", () => {
|
|
25
|
+
onINP((metric) => {
|
|
26
|
+
if (metric.rating === "good") {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
console.info(
|
|
30
|
+
"[@nuxt/hints:web-vitals] INP Metric: ",
|
|
31
|
+
metric
|
|
32
|
+
);
|
|
33
|
+
nuxtApp.__hints.webvitals.inp.value.push(metric);
|
|
34
|
+
nuxtApp.callHook("hints:webvitals:inp", metric);
|
|
35
|
+
}, {
|
|
36
|
+
reportAllChanges: true
|
|
37
|
+
});
|
|
38
|
+
onLCP((metric) => {
|
|
39
|
+
if (metric.rating === "good") {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
console.info(
|
|
43
|
+
`[@nuxt/hints:web-vitals] LCP Metric: `,
|
|
44
|
+
metric
|
|
45
|
+
);
|
|
46
|
+
nuxtApp.__hints.webvitals.lcp.value.push(metric);
|
|
47
|
+
nuxtApp.callHook("hints:webvitals:lcp", metric);
|
|
48
|
+
for (const performanceEntry of metric.entries) {
|
|
49
|
+
if (!performanceEntry.element || !isImgElement(performanceEntry.element)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (performanceEntry.element.attributes?.getNamedItem("loading")?.value === "lazy") {
|
|
53
|
+
console.warn(
|
|
54
|
+
'[@nuxt/hints:performance] LCP Element should not have `loading="lazy"` \n\n Learn more: https://web.dev/optimize-lcp/#optimize-the-priority-the-resource-is-given'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (hasImageFormat(performanceEntry.element.src)) {
|
|
58
|
+
if (!performanceEntry.element.src.includes("webp") || !performanceEntry.element.src.includes("avif")) {
|
|
59
|
+
console.warn(
|
|
60
|
+
"[@nuxt/hints:performance] LCP Element can be served in a next gen format like `webp` or `avif` \n\n Learn more: https://web.dev/choose-the-right-image-format/ \n\n Use: https://image.nuxt.com/usage/nuxt-img#format"
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (performanceEntry.element.fetchPriority !== "high") {
|
|
65
|
+
console.warn(
|
|
66
|
+
'[@nuxt/hints:performance] LCP Element can have `fetchPriority="high"` to load as soon as possible \n\n Learn more: https://web.dev/optimize-lcp/#optimize-the-priority-the-resource-is-given'
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (!performanceEntry.element.attributes.getNamedItem("height") || !performanceEntry.element.attributes.getNamedItem("width")) {
|
|
70
|
+
console.warn(
|
|
71
|
+
"[@nuxt/hints:performance] Images should have `width` and `height` sizes set \n\n Learn more: https://web.dev/optimize-cls/#images-without-dimensions \n\n Use: https://image.nuxt.com/usage/nuxt-img#width-height"
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (performanceEntry.startTime > 2500) {
|
|
75
|
+
console.warn(
|
|
76
|
+
`[@nuxt/hints:performance] LCP Element loaded in ${performanceEntry.startTime} miliseconds. Good result is below 2500 miliseconds
|
|
77
|
+
|
|
78
|
+
Learn more: https://web.dev/lcp/#what-is-a-good-lcp-score`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (!isElementPreloaded(performanceEntry.element.src)) {
|
|
82
|
+
console.warn(
|
|
83
|
+
"[@nuxt/hints:performance] LCP Element can be preloaded in `head` to improve load time \n\n Learn more: https://web.dev/optimize-lcp/#optimize-when-the-resource-is-discovered \n\n Use: https://image.nuxt.com/usage/nuxt-img#preload"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}, {
|
|
88
|
+
reportAllChanges: true
|
|
89
|
+
});
|
|
90
|
+
onCLS((metric) => {
|
|
91
|
+
if (metric.rating === "good") {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
console.info(
|
|
95
|
+
"[@nuxt/hints:web-vitals] CLS Metric: ",
|
|
96
|
+
metric
|
|
97
|
+
);
|
|
98
|
+
nuxtApp.callHook(
|
|
99
|
+
"hints:webvitals:cls",
|
|
100
|
+
metric
|
|
101
|
+
);
|
|
102
|
+
nuxtApp.__hints.webvitals.cls.value.push(metric);
|
|
103
|
+
for (const entry of metric.entries) {
|
|
104
|
+
const performanceEntry = entry;
|
|
105
|
+
if (!performanceEntry.sources?.[0]) return;
|
|
106
|
+
const sourceElement = performanceEntry.sources?.[0].node;
|
|
107
|
+
if (!sourceElement || sourceElement.parentElement?.className.includes("nuxt-devtools")) return;
|
|
108
|
+
console.info(
|
|
109
|
+
"[@nuxt/hints:performance] Potential CLS Element: ",
|
|
110
|
+
sourceElement
|
|
111
|
+
);
|
|
112
|
+
if ((performanceEntry.value ?? 0) > 0.1) {
|
|
113
|
+
console.warn(
|
|
114
|
+
`[@nuxt/hints:performance] CLS was ${performanceEntry.value}. Good result is below 0.1
|
|
115
|
+
|
|
116
|
+
Learn more: https://web.dev/articles/cls#what-is-a-good-cls-score`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (isImgElement(sourceElement) && (!sourceElement.attributes.getNamedItem("height") || !sourceElement.attributes.getNamedItem("width"))) {
|
|
120
|
+
console.warn(
|
|
121
|
+
"[@nuxt/hints:performance] Images should have `width` and `height` sizes set \n\n Learn more: https://web.dev/optimize-cls/#images-without-dimensions \n\n Use: https://image.nuxt.com/usage/nuxt-img#width-height"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
const hasImageFormat = (src) => {
|
|
130
|
+
const imageFormats = ["avif", "jpg", "jpeg", "png", "webp"];
|
|
131
|
+
return imageFormats.some((format) => src.includes(format));
|
|
132
|
+
};
|
|
133
|
+
const isElementPreloaded = (src) => {
|
|
134
|
+
return Array.from(document.head.childNodes).filter(
|
|
135
|
+
(el) => el.nodeName === "LINK" && el.attributes.href.value === src
|
|
136
|
+
).length;
|
|
137
|
+
};
|