@nuxt/hints 1.0.0-alpha.3 → 1.0.0-alpha.4

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 (62) hide show
  1. package/dist/client/200.html +1 -1
  2. package/dist/client/404.html +1 -1
  3. package/dist/client/_nuxt/6YZAjlve.js +1 -0
  4. package/dist/client/_nuxt/BXSELIT7.js +1 -0
  5. package/dist/client/_nuxt/BgVA9H9k.js +1 -0
  6. package/dist/client/_nuxt/BuasPzSQ.js +21 -0
  7. package/dist/client/_nuxt/By0zDdDR.js +6 -0
  8. package/dist/client/_nuxt/{CeyKylGo.js → C4-GUK1K.js} +3 -3
  9. package/dist/client/_nuxt/{DE7yhJ9n.js → CHRGnUwv.js} +1 -1
  10. package/dist/client/_nuxt/{3uZvltmj.js → CIn0yCh1.js} +1 -1
  11. package/dist/client/_nuxt/CeAnYTQ5.js +1 -0
  12. package/dist/client/_nuxt/{DyZ7ilof.js → Chg59sI4.js} +1 -1
  13. package/dist/client/_nuxt/CjPw6jm5.js +1 -0
  14. package/dist/client/_nuxt/GrcSzlUJ.js +1 -0
  15. package/dist/client/_nuxt/{BTIAbEX4.js → P-pLD3_d.js} +1 -1
  16. package/dist/client/_nuxt/{BYW8DJWC.js → Y-og_wyR.js} +1 -1
  17. package/dist/client/_nuxt/{DxmC8vYz.js → YA1k0SOY.js} +1 -1
  18. package/dist/client/_nuxt/builds/latest.json +1 -1
  19. package/dist/client/_nuxt/builds/meta/17377e6a-4112-4616-a57e-0db49b9956bf.json +1 -0
  20. package/dist/client/_nuxt/error-404.Db9gmKof.css +1 -0
  21. package/dist/client/_nuxt/error-500.BSnPHzYt.css +1 -0
  22. package/dist/client/_nuxt/hydration.Dg0tHOnZ.css +1 -0
  23. package/dist/client/_nuxt/sTshF0tC.js +1 -0
  24. package/dist/client/hydration/index.html +1 -1
  25. package/dist/client/index.html +1 -1
  26. package/dist/client/third-party-scripts/index.html +1 -1
  27. package/dist/client/web-vitals/index.html +1 -1
  28. package/dist/module.d.mts +1 -3
  29. package/dist/module.json +1 -1
  30. package/dist/module.mjs +60 -24
  31. package/dist/runtime/core/components/nuxt-island.d.ts +1 -136
  32. package/dist/runtime/core/plugins/vue-tracer-state.client.d.ts +1 -1
  33. package/dist/runtime/hydration/component.d.ts +1 -1
  34. package/dist/runtime/hydration/component.js +1 -2
  35. package/dist/runtime/hydration/composables.js +16 -26
  36. package/dist/runtime/hydration/handler.nitro.d.ts +5 -0
  37. package/dist/runtime/hydration/handler.nitro.js +52 -0
  38. package/dist/runtime/hydration/plugin.client.d.ts +1 -1
  39. package/dist/runtime/hydration/sse.nitro.d.ts +2 -0
  40. package/dist/runtime/hydration/sse.nitro.js +22 -0
  41. package/dist/runtime/hydration/types.d.ts +29 -0
  42. package/dist/runtime/hydration/types.js +0 -0
  43. package/dist/runtime/hydration/utils.d.ts +3 -0
  44. package/dist/runtime/hydration/utils.js +23 -0
  45. package/dist/runtime/third-party-scripts/nitro.plugin.js +2 -2
  46. package/dist/runtime/third-party-scripts/plugin.client.d.ts +1 -1
  47. package/dist/runtime/third-party-scripts/plugin.client.js +2 -2
  48. package/dist/runtime/types.d.ts +11 -2
  49. package/dist/runtime/web-vitals/plugin.client.d.ts +1 -1
  50. package/package.json +4 -4
  51. package/dist/client/_nuxt/BAIAvlAa.js +0 -1
  52. package/dist/client/_nuxt/Ckm7x3qc.js +0 -6
  53. package/dist/client/_nuxt/CsLEpyV7.js +0 -1
  54. package/dist/client/_nuxt/CzzNqrSg.js +0 -1
  55. package/dist/client/_nuxt/DBrXuSAZ.js +0 -1
  56. package/dist/client/_nuxt/DDotc1jX.js +0 -21
  57. package/dist/client/_nuxt/REBTOKr5.js +0 -1
  58. package/dist/client/_nuxt/builds/meta/b0fc6608-9102-4a88-9842-0a2ed6526798.json +0 -1
  59. package/dist/client/_nuxt/error-404.MLlw4bJt.css +0 -1
  60. package/dist/client/_nuxt/error-500.CBQQ1PSP.css +0 -1
  61. package/dist/client/_nuxt/hydration.B5wxUWyr.css +0 -1
  62. package/dist/client/_nuxt/s2_R2YM5.js +0 -1
@@ -1,26 +1,6 @@
1
1
  import { getCurrentInstance, onMounted } from "vue";
2
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(Math.max(0, indent)) + tag;
15
- indent++;
16
- } else if (tag.startsWith("<")) {
17
- formatted += "\n" + " ".repeat(Math.max(0, indent)) + tag;
18
- } else {
19
- formatted += "\n" + " ".repeat(Math.max(0, indent)) + tag.trim();
20
- }
21
- }
22
- return formatted.trim();
23
- }
3
+ import { HYDRATION_ROUTE, formatHTML } from "./utils.js";
24
4
  export function useHydrationCheck() {
25
5
  if (import.meta.server) return;
26
6
  const nuxtApp = useNuxtApp();
@@ -29,16 +9,26 @@ export function useHydrationCheck() {
29
9
  }
30
10
  const instance = getCurrentInstance();
31
11
  if (!instance) return;
32
- const htmlPrehydration = formatHTML(instance.vnode.el?.outerHTML);
12
+ const htmlPreHydration = formatHTML(instance.vnode.el?.outerHTML);
33
13
  const vnodePrehydration = instance.vnode;
34
14
  onMounted(() => {
35
15
  const htmlPostHydration = formatHTML(instance.vnode.el?.outerHTML);
36
- if (htmlPrehydration !== htmlPostHydration) {
16
+ if (htmlPreHydration !== htmlPostHydration) {
17
+ const payload = {
18
+ htmlPreHydration,
19
+ htmlPostHydration,
20
+ id: globalThis.crypto.randomUUID(),
21
+ componentName: instance.type.name ?? instance.type.displayName ?? instance.type.__name,
22
+ fileLocation: instance.type.__file ?? "unknown"
23
+ };
37
24
  nuxtApp.__hints.hydration.push({
25
+ ...payload,
38
26
  instance,
39
- vnode: vnodePrehydration,
40
- htmlPreHydration: htmlPrehydration,
41
- htmlPostHydration
27
+ vnode: vnodePrehydration
28
+ });
29
+ $fetch(new URL(HYDRATION_ROUTE, window.location.origin).href, {
30
+ method: "POST",
31
+ body: payload
42
32
  });
43
33
  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
34
  }
@@ -0,0 +1,5 @@
1
+ import type { HydrationMismatchPayload } from './types.js';
2
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void> | {
3
+ mismatches: HydrationMismatchPayload[];
4
+ }>;
5
+ export default _default;
@@ -0,0 +1,52 @@
1
+ import { createError, defineEventHandler, readBody, setResponseStatus } from "h3";
2
+ import { useNitroApp } from "nitropack/runtime";
3
+ const hydrationMismatches = [];
4
+ export default defineEventHandler((event) => {
5
+ switch (event.method) {
6
+ case "GET":
7
+ return getHandler();
8
+ case "POST":
9
+ return postHandler(event);
10
+ case "DELETE":
11
+ return deleteHandler(event);
12
+ default:
13
+ throw createError({ statusCode: 405, statusMessage: "Method Not Allowed" });
14
+ }
15
+ });
16
+ function getHandler() {
17
+ return {
18
+ mismatches: hydrationMismatches
19
+ };
20
+ }
21
+ async function postHandler(event) {
22
+ const body = await readBody(event);
23
+ assertPayload(body);
24
+ const nitro = useNitroApp();
25
+ const payload = { id: body.id, htmlPreHydration: body.htmlPreHydration, htmlPostHydration: body.htmlPostHydration, componentName: body.componentName, fileLocation: body.fileLocation };
26
+ hydrationMismatches.push(payload);
27
+ if (hydrationMismatches.length > 20) {
28
+ hydrationMismatches.shift();
29
+ }
30
+ nitro.hooks.callHook("hints:hydration:mismatch", payload);
31
+ setResponseStatus(event, 201);
32
+ }
33
+ async function deleteHandler(event) {
34
+ const nitro = useNitroApp();
35
+ const body = await readBody(event);
36
+ if (!body || !Array.isArray(body.id)) {
37
+ throw createError({ statusCode: 400, statusMessage: "Invalid payload" });
38
+ }
39
+ for (const id of body.id) {
40
+ const index = hydrationMismatches.findIndex((m) => m.id === id);
41
+ if (index !== -1) {
42
+ hydrationMismatches.splice(index, 1);
43
+ }
44
+ }
45
+ nitro.hooks.callHook("hints:hydration:cleared", { id: body.id });
46
+ setResponseStatus(event, 204);
47
+ }
48
+ function assertPayload(body) {
49
+ if (typeof body !== "object" || typeof body.id !== "string" || body.htmlPreHydration !== void 0 && typeof body.htmlPreHydration !== "string" || body.htmlPostHydration !== void 0 && typeof body.htmlPostHydration !== "string" || typeof body.componentName !== "string" || typeof body.fileLocation !== "string") {
50
+ throw createError({ statusCode: 400, statusMessage: "Invalid payload" });
51
+ }
52
+ }
@@ -1,2 +1,2 @@
1
- declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
1
+ declare const _default: any;
2
2
  export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
2
+ export default _default;
@@ -0,0 +1,22 @@
1
+ import { createEventStream, defineEventHandler } from "h3";
2
+ import { useNitroApp } from "nitropack/runtime";
3
+ export default defineEventHandler((event) => {
4
+ const nitro = useNitroApp();
5
+ const eventStream = createEventStream(event);
6
+ const unsubs = [nitro.hooks.hook("hints:hydration:mismatch", (mismatch) => {
7
+ eventStream.push({
8
+ data: JSON.stringify(mismatch),
9
+ event: "hints:hydration:mismatch"
10
+ });
11
+ }), nitro.hooks.hook("hints:hydration:cleared", async (payload) => {
12
+ eventStream.push({
13
+ data: JSON.stringify(payload.id),
14
+ event: "hints:hydration:cleared"
15
+ });
16
+ })];
17
+ eventStream.onClosed(async () => {
18
+ unsubs.forEach((unsub) => unsub());
19
+ await eventStream.close();
20
+ });
21
+ return eventStream.send();
22
+ });
@@ -0,0 +1,29 @@
1
+ import type { EventStreamMessage } from 'h3';
2
+ import type { ComponentInternalInstance, VNode } from 'vue';
3
+ export interface HydrationMismatchPayload {
4
+ id: string;
5
+ componentName?: string;
6
+ fileLocation: string;
7
+ htmlPreHydration: string;
8
+ htmlPostHydration: string;
9
+ }
10
+ export interface LocalHydrationMismatch extends HydrationMismatchPayload {
11
+ instance: ComponentInternalInstance;
12
+ vnode: VNode;
13
+ }
14
+ export interface HydrationMismatchResponse {
15
+ mismatches: HydrationMismatchPayload[];
16
+ }
17
+ export interface HydrationDeleteSSE extends EventStreamMessage {
18
+ event: 'hydration:cleared';
19
+ data: string;
20
+ }
21
+ export interface HydrationNewSSE extends EventStreamMessage {
22
+ event: 'hydration:mismatch';
23
+ /**
24
+ * Stringified HydrationMismatchPayload
25
+ * @see HydrationMismatchPayload
26
+ */
27
+ data: string;
28
+ }
29
+ export type HydrationSSEPayload = HydrationDeleteSSE | HydrationNewSSE;
File without changes
@@ -0,0 +1,3 @@
1
+ export declare const HYDRATION_ROUTE = "/__nuxt_hydration";
2
+ export declare const HYDRATION_SSE_ROUTE = "/__nuxt_hydration/sse";
3
+ export declare function formatHTML(html: string | undefined): string;
@@ -0,0 +1,23 @@
1
+ export const HYDRATION_ROUTE = "/__nuxt_hydration";
2
+ export const HYDRATION_SSE_ROUTE = "/__nuxt_hydration/sse";
3
+ export 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(Math.max(0, indent)) + tag;
15
+ indent++;
16
+ } else if (tag.startsWith("<")) {
17
+ formatted += "\n" + " ".repeat(Math.max(0, indent)) + tag;
18
+ } else {
19
+ formatted += "\n" + " ".repeat(Math.max(0, indent)) + tag.trim();
20
+ }
21
+ }
22
+ return formatted.trim();
23
+ }
@@ -36,9 +36,9 @@ function __hints_TPC_saveTime(script, startTime) {
36
36
  for (const script of document.scripts) {
37
37
  if (script.src && !script.src.startsWith(window.location.origin)) {
38
38
  script.__hints_TPC_start_time = window.__hints_TPC_start_time || Date.now();
39
- script.onload = function(_) {
39
+ script.addEventListener('load', () => {
40
40
  __hints_TPC_saveTime(script, script.__hints_TPC_start_time);
41
- }
41
+ })
42
42
  __hints_TPC_saveTime(script, script.__hints_TPC_start_time);
43
43
  }
44
44
  }
@@ -1,2 +1,2 @@
1
- declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
1
+ declare const _default: any;
2
2
  export default _default;
@@ -70,10 +70,10 @@ export default defineNuxtPlugin({
70
70
  }
71
71
  nuxtApp.callHook("hints:scripts:added", script).then(() => {
72
72
  if (!script.loaded) {
73
- script.onload = () => {
73
+ script.addEventListener("load", () => {
74
74
  window.__hints_TPC_saveTime(script, script.__hints_TPC_start_time);
75
75
  nuxtApp.callHook("hints:scripts:loaded", script);
76
- };
76
+ });
77
77
  } else {
78
78
  window.__hints_TPC_saveTime(script, script.__hints_TPC_start_time);
79
79
  nuxtApp.callHook("hints:scripts:loaded", script);
@@ -1,5 +1,6 @@
1
- import type { ComponentInternalInstance, VNode, Ref } from 'vue'
1
+ import type { VNode, Ref } from 'vue'
2
2
  import type { LCPMetricWithAttribution, INPMetricWithAttribution, CLSMetricWithAttribution } from 'web-vitals/attribution'
3
+ import type { HydrationMismatchPayload, LocalHydrationMismatch } from './hydration/types'
3
4
 
4
5
  declare global {
5
6
  interface Window {
@@ -19,6 +20,7 @@ declare global {
19
20
  __vnode?: VNode
20
21
  }
21
22
  }
23
+
22
24
  declare module '#app' {
23
25
  interface RuntimeNuxtHooks {
24
26
  'hints:scripts:added': (script: HTMLScriptElement) => void
@@ -33,7 +35,7 @@ declare module '#app' {
33
35
  interface NuxtApp {
34
36
  __hints_tpc: Ref<{ element: HTMLScriptElement, loaded: boolean }[]>
35
37
  __hints: {
36
- hydration: { instance: ComponentInternalInstance, vnode: VNode, htmlPreHydration: string | undefined, htmlPostHydration: string | undefined }[]
38
+ hydration: LocalHydrationMismatch[]
37
39
  webvitals: {
38
40
  lcp: Ref<LCPMetricWithAttribution[]>
39
41
  inp: Ref<INPMetricWithAttribution[]>
@@ -45,4 +47,11 @@ declare module '#app' {
45
47
  }
46
48
  }
47
49
 
50
+ declare module 'nitropack' {
51
+ interface NitroRuntimeHooks {
52
+ 'hints:hydration:mismatch': (payload: HydrationMismatchPayload) => void
53
+ 'hints:hydration:cleared': (payload: { id: string[] }) => void
54
+ }
55
+ }
56
+
48
57
  export {}
@@ -9,5 +9,5 @@ declare global {
9
9
  value?: number;
10
10
  }
11
11
  }
12
- declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
12
+ declare const _default: any;
13
13
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxt/hints",
3
- "version": "1.0.0-alpha.3",
3
+ "version": "1.0.0-alpha.4",
4
4
  "description": "Nuxt module that shows hints for aspects of your application such as Performance, Security, and more!",
5
5
  "repository": "https://github.com/nuxt/hints",
6
6
  "homepage": "https://github.com/nuxt/hints",
@@ -27,7 +27,7 @@
27
27
  "knitwork": "^1.3.0",
28
28
  "magic-string": "^0.30.19",
29
29
  "nitropack": "^2.12.6",
30
- "oxc-parser": "^0.99.0",
30
+ "oxc-parser": "^0.103.0",
31
31
  "shiki": "^3.13.0",
32
32
  "sirv": "^3.0.2",
33
33
  "unplugin": "^2.3.10",
@@ -48,11 +48,11 @@
48
48
  "diff": "^8.0.2",
49
49
  "eslint": "^9.36.0",
50
50
  "happy-dom": "^20.0.10",
51
- "pkg-pr-new": "0.0.60",
51
+ "pkg-pr-new": "0.0.62",
52
52
  "rimraf": "^6.0.1",
53
53
  "sass-embedded": "^1.93.3",
54
54
  "vitest": "^4.0.0",
55
- "@nuxt/hints": "^1.0.0-alpha.3"
55
+ "@nuxt/hints": "^1.0.0-alpha.4"
56
56
  },
57
57
  "scripts": {
58
58
  "client:build": "nuxi generate client",
@@ -1 +0,0 @@
1
- import g from"./CeyKylGo.js";import{_ as h,c as f,o,r as k,g as w,j as N,b as e,w as s,a as t,k as m,l as u,m as n,d as p,t as x}from"./Ckm7x3qc.js";import{u as C,a as V,_ as $}from"./CzzNqrSg.js";import{_ as B}from"./s2_R2YM5.js";import{_ as S}from"./REBTOKr5.js";const j={},H={class:"n-badge"};function L(d,r){return o(),f("span",H,[k(d.$slots,"default")])}const z=Object.assign(h(j,[["render",L]]),{__name:"NBadge"}),I={class:"grid grid-cols-1 md:grid-cols-3 gap-3 p-4"},P={class:"flex items-center gap-3 min-w-0"},T={class:"flex items-center gap-3 min-w-0"},R=w({__name:"index",setup(d){const{allMetrics:r}=C(),{hydration:b}=V(),_=N(()=>b.length||0);return(W,a)=>{const l=g,v=z,c=$,i=B,y=S;return o(),f("div",I,[e(i,{to:"/web-vitals",class:"block"},{default:s(()=>[e(c,{class:"flex items-center justify-between p-4 hover:border-neutral-400 dark:hover:border-neutral-500"},{default:s(()=>[t("div",P,[e(l,{name:"material-symbols:monitoring",class:"text-xl text-blue-500"}),a[0]||(a[0]=t("div",{class:"min-w-0"},[t("div",{class:"text-sm font-medium truncate"}," Web Vitals "),t("div",{class:"text-xs text-neutral-500"}," LCP / INP / CLS ")],-1))]),n(r).length?(o(),m(v,{key:0},{default:s(()=>[p(x(n(r).length)+" issues ",1)]),_:1})):u("",!0)]),_:1})]),_:1}),e(i,{to:"/hydration",class:"block"},{default:s(()=>[e(c,{class:"flex items-center justify-between p-4 hover:border-neutral-400 dark:hover:border-neutral-500"},{default:s(()=>[t("div",T,[e(l,{name:"material-symbols:water-full",class:"text-xl text-cyan-500"}),a[1]||(a[1]=t("div",{class:"min-w-0"},[t("div",{class:"text-sm font-medium truncate"}," Hydration "),t("div",{class:"text-xs text-neutral-500"}," SSR vs client diffs ")],-1))]),n(_)?(o(),m(y,{key:0,size:"small",type:"error",bordered:!1},{default:s(()=>[p(x(n(_))+" issues ",1)]),_:1})):u("",!0)]),_:1})]),_:1}),e(i,{to:"/third-party-scripts",class:"block"},{default:s(()=>[e(c,{class:"p-4 flex items-center gap-3 hover:border-neutral-400 dark:hover:border-neutral-500"},{default:s(()=>[e(l,{name:"material-symbols:extension",class:"text-xl text-violet-500"}),a[2]||(a[2]=t("div",{class:"min-w-0"},[t("div",{class:"text-sm font-medium truncate"}," Third party scripts "),t("div",{class:"text-xs text-neutral-500"}," Analyze third-party scripts speed. ")],-1))]),_:1})]),_:1})])}}});export{R as default};