@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.
Files changed (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +136 -0
  3. package/dist/client/200.html +1 -0
  4. package/dist/client/404.html +1 -0
  5. package/dist/client/_nuxt/2EtD6e53.js +1 -0
  6. package/dist/client/_nuxt/3e1v2bzS.js +1 -0
  7. package/dist/client/_nuxt/5i3qLPDT.js +1 -0
  8. package/dist/client/_nuxt/B0m2ddpp.js +1 -0
  9. package/dist/client/_nuxt/B1dDrJ26.js +1 -0
  10. package/dist/client/_nuxt/B6aJPvgy.js +1 -0
  11. package/dist/client/_nuxt/B7mTdjB0.js +1 -0
  12. package/dist/client/_nuxt/BEDo0Tqx.js +1 -0
  13. package/dist/client/_nuxt/BGJmEYvX.js +1 -0
  14. package/dist/client/_nuxt/BHrmToEH.js +1 -0
  15. package/dist/client/_nuxt/BIGW1oBm.js +1 -0
  16. package/dist/client/_nuxt/BJtxVCQ_.js +1 -0
  17. package/dist/client/_nuxt/BLmx8bSh.js +1 -0
  18. package/dist/client/_nuxt/BLtJtn59.js +1 -0
  19. package/dist/client/_nuxt/BPQ3VLAy.js +1 -0
  20. package/dist/client/_nuxt/BUGFbzbB.js +6 -0
  21. package/dist/client/_nuxt/BXkSAIEj.js +1 -0
  22. package/dist/client/_nuxt/BahsQGtb.js +19 -0
  23. package/dist/client/_nuxt/BfHTSMKl.js +1 -0
  24. package/dist/client/_nuxt/BfjtVDDH.js +1 -0
  25. package/dist/client/_nuxt/BgDCqdQA.js +1 -0
  26. package/dist/client/_nuxt/Bgk_gK97.js +5 -0
  27. package/dist/client/_nuxt/Bkuqu6BP.js +1 -0
  28. package/dist/client/_nuxt/Boah4cwZ.js +1 -0
  29. package/dist/client/_nuxt/Bp3cYrEr.js +1 -0
  30. package/dist/client/_nuxt/Br6cN0cg.js +1 -0
  31. package/dist/client/_nuxt/BthQWCQV.js +1 -0
  32. package/dist/client/_nuxt/Buea-lGh.js +1 -0
  33. package/dist/client/_nuxt/BvKcRjC_.js +1 -0
  34. package/dist/client/_nuxt/Bw305WKR.js +1 -0
  35. package/dist/client/_nuxt/Bynn4rkp.js +1 -0
  36. package/dist/client/_nuxt/ByztXxam.js +1 -0
  37. package/dist/client/_nuxt/BzJJZx-M.js +1 -0
  38. package/dist/client/_nuxt/C-Jbm3Hp.js +1 -0
  39. package/dist/client/_nuxt/C39BiMTA.js +1 -0
  40. package/dist/client/_nuxt/C8M2exoo.js +1 -0
  41. package/dist/client/_nuxt/C9dUb6Cb.js +1 -0
  42. package/dist/client/_nuxt/C9oPPf7i.js +1 -0
  43. package/dist/client/_nuxt/C9tS-k6U.js +1 -0
  44. package/dist/client/_nuxt/CDVJQ6XC.js +1 -0
  45. package/dist/client/_nuxt/CEG3Q07z.js +1 -0
  46. package/dist/client/_nuxt/CFHQjOhq.js +1 -0
  47. package/dist/client/_nuxt/CG6Dc4jp.js +1 -0
  48. package/dist/client/_nuxt/CH1njM8p.js +1 -0
  49. package/dist/client/_nuxt/CHoFPrvn.js +1 -0
  50. package/dist/client/_nuxt/CKUSRNTx.js +1 -0
  51. package/dist/client/_nuxt/CMFEf1DS.js +1 -0
  52. package/dist/client/_nuxt/COt5Ahok.js +1 -0
  53. package/dist/client/_nuxt/CS3Unz2-.js +1 -0
  54. package/dist/client/_nuxt/CTRr51gU.js +1 -0
  55. package/dist/client/_nuxt/CVO1_9PV.js +1 -0
  56. package/dist/client/_nuxt/CVdnzihN.js +1 -0
  57. package/dist/client/_nuxt/CXZktZb0.js +1 -0
  58. package/dist/client/_nuxt/CXtECtnM.js +1 -0
  59. package/dist/client/_nuxt/CYsAdtH9.js +1 -0
  60. package/dist/client/_nuxt/C_d8ykrU.js +9 -0
  61. package/dist/client/_nuxt/CafNBF8u.js +1 -0
  62. package/dist/client/_nuxt/CbfX1IO0.js +1 -0
  63. package/dist/client/_nuxt/CfQXZHmo.js +1 -0
  64. package/dist/client/_nuxt/CgjmDhmW.js +1 -0
  65. package/dist/client/_nuxt/Cj5Yp3dK.js +1 -0
  66. package/dist/client/_nuxt/CkXjmgJE.js +1 -0
  67. package/dist/client/_nuxt/Cl0AqbOI.js +1 -0
  68. package/dist/client/_nuxt/Cm3UrAx6.js +1 -0
  69. package/dist/client/_nuxt/CmIQRyeF.js +1 -0
  70. package/dist/client/_nuxt/Cmh6b_Ma.js +1 -0
  71. package/dist/client/_nuxt/CnK8MTSM.js +1 -0
  72. package/dist/client/_nuxt/CnnebwVN.js +1 -0
  73. package/dist/client/_nuxt/Cp-IABpG.js +1 -0
  74. package/dist/client/_nuxt/Csfq5Kiy.js +1 -0
  75. package/dist/client/_nuxt/CufHLc7y.js +1 -0
  76. package/dist/client/_nuxt/Cuk6v7N8.js +1 -0
  77. package/dist/client/_nuxt/Cv9koXgw.js +1 -0
  78. package/dist/client/_nuxt/Cvjx9yec.js +1 -0
  79. package/dist/client/_nuxt/CxMWoQ9E.js +1 -0
  80. package/dist/client/_nuxt/CxbxFI8M.js +1 -0
  81. package/dist/client/_nuxt/CyktbL80.js +1 -0
  82. package/dist/client/_nuxt/CylS5w8V.js +1 -0
  83. package/dist/client/_nuxt/D-2ljcwZ.js +1 -0
  84. package/dist/client/_nuxt/D0r3Knsf.js +1 -0
  85. package/dist/client/_nuxt/D1fewSvT.js +1 -0
  86. package/dist/client/_nuxt/D4h5O-jR.js +1 -0
  87. package/dist/client/_nuxt/D5KoaKCx.js +1 -0
  88. package/dist/client/_nuxt/D7VJQ_DO.js +1 -0
  89. package/dist/client/_nuxt/D7oLnXFd.js +1 -0
  90. package/dist/client/_nuxt/D87Tk5Gz.js +1 -0
  91. package/dist/client/_nuxt/DAi9KRSo.js +1 -0
  92. package/dist/client/_nuxt/DDBovgxi.js +1 -0
  93. package/dist/client/_nuxt/DFWUc33u.js +1 -0
  94. package/dist/client/_nuxt/DGP4VlC8.js +1 -0
  95. package/dist/client/_nuxt/DGztddWO.js +1 -0
  96. package/dist/client/_nuxt/DH5Ifo-i.js +1 -0
  97. package/dist/client/_nuxt/DHJKELXO.js +1 -0
  98. package/dist/client/_nuxt/DNMeEaky.js +1 -0
  99. package/dist/client/_nuxt/DPfMkruS.js +1 -0
  100. package/dist/client/_nuxt/DQyhUUbL.js +1 -0
  101. package/dist/client/_nuxt/DRw_LuNl.js +1 -0
  102. package/dist/client/_nuxt/DU1UobuO.js +1 -0
  103. package/dist/client/_nuxt/DUrsayuJ.js +1 -0
  104. package/dist/client/_nuxt/DUszq2jm.js +1 -0
  105. package/dist/client/_nuxt/DVMEJ2y_.js +1 -0
  106. package/dist/client/_nuxt/DWedfzmr.js +1 -0
  107. package/dist/client/_nuxt/DXbdFlpD.js +1 -0
  108. package/dist/client/_nuxt/DZxFcAj9.js +1 -0
  109. package/dist/client/_nuxt/DcaNXYhu.js +1 -0
  110. package/dist/client/_nuxt/Ddv68eIx.js +1 -0
  111. package/dist/client/_nuxt/Des-eS-w.js +1 -0
  112. package/dist/client/_nuxt/DiinP2Uv.js +1 -0
  113. package/dist/client/_nuxt/DnULxvSX.js +1 -0
  114. package/dist/client/_nuxt/DqwNpetd.js +1 -0
  115. package/dist/client/_nuxt/Dx-B1_4e.js +1 -0
  116. package/dist/client/_nuxt/DxNHbxmM.js +1 -0
  117. package/dist/client/_nuxt/DxSwrfjg.js +1 -0
  118. package/dist/client/_nuxt/E3gJ1_iC.js +1 -0
  119. package/dist/client/_nuxt/GsRaNv29.js +1 -0
  120. package/dist/client/_nuxt/L9t79GZl.js +1 -0
  121. package/dist/client/_nuxt/MzD3tlZU.js +1 -0
  122. package/dist/client/_nuxt/NleAzG8P.js +1 -0
  123. package/dist/client/_nuxt/PoHY5YXO.js +1 -0
  124. package/dist/client/_nuxt/TsXTqZ29.js +1 -0
  125. package/dist/client/_nuxt/Xqf5ue2O.js +1 -0
  126. package/dist/client/_nuxt/Y-IJRkoC.js +1 -0
  127. package/dist/client/_nuxt/Yzrsuije.js +1 -0
  128. package/dist/client/_nuxt/bN70gL4F.js +1 -0
  129. package/dist/client/_nuxt/builds/latest.json +1 -0
  130. package/dist/client/_nuxt/builds/meta/edb115ef-e91c-4b8b-a8bc-7091fc0b356e.json +1 -0
  131. package/dist/client/_nuxt/eOWES_5F.js +1 -0
  132. package/dist/client/_nuxt/entry.BPIf5bpe.css +1 -0
  133. package/dist/client/_nuxt/error-404.BZAFKftT.css +1 -0
  134. package/dist/client/_nuxt/error-500.BWF-6BkQ.css +1 -0
  135. package/dist/client/_nuxt/fuZLfV_i.js +1 -0
  136. package/dist/client/_nuxt/g9-lgVsj.js +1 -0
  137. package/dist/client/_nuxt/hJgmCMqR.js +1 -0
  138. package/dist/client/_nuxt/hegEt444.js +1 -0
  139. package/dist/client/_nuxt/m17aaUwq.js +1 -0
  140. package/dist/client/_nuxt/tqtdBwhO.js +1 -0
  141. package/dist/client/_nuxt/wDzz0qaB.js +1 -0
  142. package/dist/client/hydration/index.html +1 -0
  143. package/dist/client/index.html +1 -0
  144. package/dist/client/third-party-scripts/index.html +1 -0
  145. package/dist/client/web-vitals/index.html +1 -0
  146. package/dist/module.d.mts +9 -0
  147. package/dist/module.json +9 -0
  148. package/dist/module.mjs +105 -0
  149. package/dist/runtime/components/nuxt-island.d.ts +137 -0
  150. package/dist/runtime/components/nuxt-island.js +14 -0
  151. package/dist/runtime/composables/devtoolsData.d.ts +7 -0
  152. package/dist/runtime/composables/devtoolsData.js +12 -0
  153. package/dist/runtime/composables/hydration.d.ts +5 -0
  154. package/dist/runtime/composables/hydration.js +46 -0
  155. package/dist/runtime/plugins/hydration/plugin.client.d.ts +2 -0
  156. package/dist/runtime/plugins/hydration/plugin.client.js +11 -0
  157. package/dist/runtime/plugins/third-party-scripts/nitro.plugin.d.ts +2 -0
  158. package/dist/runtime/plugins/third-party-scripts/nitro.plugin.js +48 -0
  159. package/dist/runtime/plugins/third-party-scripts/plugin.client.d.ts +2 -0
  160. package/dist/runtime/plugins/third-party-scripts/plugin.client.js +71 -0
  161. package/dist/runtime/plugins/vue-tracer-state.client.d.ts +2 -0
  162. package/dist/runtime/plugins/vue-tracer-state.client.js +7 -0
  163. package/dist/runtime/plugins/web-vitals/plugin.client.d.ts +13 -0
  164. package/dist/runtime/plugins/web-vitals/plugin.client.js +137 -0
  165. package/dist/runtime/plugins/web-vitals/utils.d.ts +35 -0
  166. package/dist/runtime/plugins/web-vitals/utils.js +13 -0
  167. package/dist/runtime/types.d.ts +48 -0
  168. package/dist/types.d.mts +3 -0
  169. package/package.json +57 -2
  170. /package/dist/{.gitkeep → client/_nuxt/third-party-scripts.tn0RQdqM.css} +0 -0
@@ -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,5 @@
1
+ /**
2
+ * prefer implementing onMismatch hook after vue 3.6
3
+ * compare element
4
+ */
5
+ export declare function useHydrationCheck(): void;
@@ -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,2 @@
1
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
2
+ export default _default;
@@ -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,2 @@
1
+ import type { NitroApp } from 'nitropack/types';
2
+ export default function (nitroApp: NitroApp): void;
@@ -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,2 @@
1
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
2
+ export default _default;
@@ -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,2 @@
1
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
2
+ export default _default;
@@ -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
+ };