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

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 (63) hide show
  1. package/README.md +14 -9
  2. package/dist/client/200.html +1 -1
  3. package/dist/client/404.html +1 -1
  4. package/dist/client/_nuxt/BSOx1wTR.js +1 -0
  5. package/dist/client/_nuxt/BZNZ3uhE.js +1 -0
  6. package/dist/client/_nuxt/{DE7yhJ9n.js → BbfnXyuh.js} +1 -1
  7. package/dist/client/_nuxt/{CeyKylGo.js → Begz4AOL.js} +3 -3
  8. package/dist/client/_nuxt/{BTIAbEX4.js → BhEtv9w2.js} +1 -1
  9. package/dist/client/_nuxt/{BYW8DJWC.js → CFPKd07t.js} +1 -1
  10. package/dist/client/_nuxt/Cnhn90sM.js +1 -0
  11. package/dist/client/_nuxt/{DxmC8vYz.js → CpX0l1y3.js} +1 -1
  12. package/dist/client/_nuxt/CzKOi7Jv.js +1 -0
  13. package/dist/client/_nuxt/D6ZzJpIK.js +1 -0
  14. package/dist/client/_nuxt/DAb3G4RE.js +1 -0
  15. package/dist/client/_nuxt/{3uZvltmj.js → DI96rpYV.js} +1 -1
  16. package/dist/client/_nuxt/{s2_R2YM5.js → DdCWK8Sp.js} +1 -1
  17. package/dist/client/_nuxt/DtIwcMt9.js +6 -0
  18. package/dist/client/_nuxt/KEVM2KzW.js +36 -0
  19. package/dist/client/_nuxt/builds/latest.json +1 -1
  20. package/dist/client/_nuxt/builds/meta/a5258e2b-a12d-475a-8f36-441ee2727d81.json +1 -0
  21. package/dist/client/_nuxt/{entry.CqH3PPiL.css → entry.BHEgHyMn.css} +1 -1
  22. package/dist/client/_nuxt/error-404.CAnB1sNT.css +1 -0
  23. package/dist/client/_nuxt/error-500.CHqHY76U.css +1 -0
  24. package/dist/client/_nuxt/hydration.BDJR-Z6Z.css +1 -0
  25. package/dist/client/_nuxt/{DyZ7ilof.js → otccQYWM.js} +1 -1
  26. package/dist/client/hydration/index.html +1 -1
  27. package/dist/client/index.html +1 -1
  28. package/dist/client/third-party-scripts/index.html +1 -1
  29. package/dist/client/web-vitals/index.html +1 -1
  30. package/dist/module.d.mts +1 -3
  31. package/dist/module.json +1 -1
  32. package/dist/module.mjs +60 -24
  33. package/dist/runtime/core/components/nuxt-island.d.ts +1 -136
  34. package/dist/runtime/core/plugins/vue-tracer-state.client.d.ts +1 -1
  35. package/dist/runtime/hydration/component.d.ts +1 -1
  36. package/dist/runtime/hydration/component.js +1 -2
  37. package/dist/runtime/hydration/composables.js +16 -26
  38. package/dist/runtime/hydration/handler.nitro.d.ts +5 -0
  39. package/dist/runtime/hydration/handler.nitro.js +52 -0
  40. package/dist/runtime/hydration/plugin.client.d.ts +1 -1
  41. package/dist/runtime/hydration/sse.nitro.d.ts +2 -0
  42. package/dist/runtime/hydration/sse.nitro.js +22 -0
  43. package/dist/runtime/hydration/types.d.ts +29 -0
  44. package/dist/runtime/hydration/types.js +0 -0
  45. package/dist/runtime/hydration/utils.d.ts +3 -0
  46. package/dist/runtime/hydration/utils.js +23 -0
  47. package/dist/runtime/third-party-scripts/nitro.plugin.js +2 -2
  48. package/dist/runtime/third-party-scripts/plugin.client.d.ts +1 -1
  49. package/dist/runtime/third-party-scripts/plugin.client.js +2 -2
  50. package/dist/runtime/types.d.ts +11 -2
  51. package/dist/runtime/web-vitals/plugin.client.d.ts +1 -1
  52. package/package.json +7 -6
  53. package/dist/client/_nuxt/BAIAvlAa.js +0 -1
  54. package/dist/client/_nuxt/Ckm7x3qc.js +0 -6
  55. package/dist/client/_nuxt/CsLEpyV7.js +0 -1
  56. package/dist/client/_nuxt/CzzNqrSg.js +0 -1
  57. package/dist/client/_nuxt/DBrXuSAZ.js +0 -1
  58. package/dist/client/_nuxt/DDotc1jX.js +0 -21
  59. package/dist/client/_nuxt/REBTOKr5.js +0 -1
  60. package/dist/client/_nuxt/builds/meta/b0fc6608-9102-4a88-9842-0a2ed6526798.json +0 -1
  61. package/dist/client/_nuxt/error-404.MLlw4bJt.css +0 -1
  62. package/dist/client/_nuxt/error-500.CBQQ1PSP.css +0 -1
  63. package/dist/client/_nuxt/hydration.B5wxUWyr.css +0 -1
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/__nuxt-hints/_nuxt/entry.CqH3PPiL.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-hints/_nuxt/Ckm7x3qc.js"><script type="module" src="/__nuxt-hints/_nuxt/Ckm7x3qc.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script type="application/json" data-nuxt-data="nuxt-hints-iframe" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1764624703489,false]</script><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-hints",buildId:"b0fc6608-9102-4a88-9842-0a2ed6526798",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/__nuxt-hints/_nuxt/entry.BHEgHyMn.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-hints/_nuxt/DtIwcMt9.js"><script type="module" src="/__nuxt-hints/_nuxt/DtIwcMt9.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-hints",buildId:"a5258e2b-a12d-475a-8f36-441ee2727d81",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-hints-iframe" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1766698975596,false]</script></body></html>
package/dist/module.d.mts CHANGED
@@ -1,9 +1,7 @@
1
- import * as _nuxt_schema from '@nuxt/schema';
2
-
3
1
  interface ModuleOptions {
4
2
  devtools: boolean;
5
3
  }
6
- declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
4
+ declare const _default: any;
7
5
 
8
6
  export { _default as default };
9
7
  export type { ModuleOptions };
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nuxt/hints",
3
3
  "configKey": "hints",
4
- "version": "1.0.0-alpha.3",
4
+ "version": "1.0.0-alpha.5",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,10 +1,13 @@
1
- import { addDevServerHandler, defineNuxtModule, createResolver, addComponent, addPlugin, addBuildPlugin, addServerPlugin } from '@nuxt/kit';
1
+ import { addDevServerHandler, defineNuxtModule, createResolver, addComponent, addPlugin, addBuildPlugin, addServerHandler, addServerPlugin } from '@nuxt/kit';
2
+ import { HYDRATION_ROUTE, HYDRATION_SSE_ROUTE } from '../dist/runtime/hydration/utils.js';
2
3
  import { existsSync } from 'node:fs';
3
4
  import { eventHandler, proxyRequest } from 'h3';
4
5
  import { genImport } from 'knitwork';
5
6
  import MagicString from 'magic-string';
7
+ import { dirname, resolve } from 'node:path';
6
8
  import { parseSync } from 'oxc-parser';
7
9
  import { createUnplugin } from 'unplugin';
10
+ import { fileURLToPath } from 'node:url';
8
11
 
9
12
  const DEVTOOLS_UI_ROUTE = "/__nuxt-hints";
10
13
  const DEVTOOLS_UI_LOCAL_PORT = 3300;
@@ -41,10 +44,13 @@ function setupDevToolsUI(nuxt, resolver) {
41
44
  });
42
45
  }
43
46
 
47
+ const distDir = dirname(fileURLToPath(import.meta.url));
48
+
44
49
  const INCLUDE_VUE_RE = /\.vue$/;
45
50
  const EXCLUDE_NODE_MODULES = /node_modules/;
46
51
  const DEFINE_COMPONENT_RE = /defineComponent/;
47
52
  const DEFINE_NUXT_COMPONENT_RE = /defineNuxtComponent/;
53
+ const skipPath = normalizePath(resolve(distDir, "runtime/hydration/component.ts"));
48
54
  const InjectHydrationPlugin = createUnplugin(() => {
49
55
  return [
50
56
  {
@@ -54,32 +60,42 @@ const InjectHydrationPlugin = createUnplugin(() => {
54
60
  filter: {
55
61
  id: {
56
62
  include: /.(vue|ts|js|tsx|jsx)$/,
57
- exclude: EXCLUDE_NODE_MODULES
63
+ exclude: [skipPath, EXCLUDE_NODE_MODULES]
58
64
  },
59
- code: /defineNuxtComponent|defineComponent/
65
+ code: {
66
+ include: [DEFINE_COMPONENT_RE, DEFINE_NUXT_COMPONENT_RE]
67
+ }
60
68
  },
61
- handler(code, id) {
69
+ async handler(code, id) {
62
70
  const m = new MagicString(code);
63
71
  const { program } = parseSync(id, code);
64
72
  const imports = program.body.filter((node) => node.type === "ImportDeclaration");
65
73
  const hasDefineComponent = DEFINE_COMPONENT_RE.test(code);
66
74
  const hasDefineNuxtComponent = DEFINE_NUXT_COMPONENT_RE.test(code);
67
- const defineComponentImport = findImportSpecifier(imports, "defineComponent", ["vue", "#imports"]);
75
+ const defineComponentImport = findImportSpecifier(
76
+ imports,
77
+ "defineComponent",
78
+ ["vue", "#imports"],
79
+ (specifier, nextSpecifier) => {
80
+ m.remove(
81
+ specifier.start,
82
+ nextSpecifier?.start ?? specifier.end
83
+ );
84
+ }
85
+ );
68
86
  const defineComponentAlias = defineComponentImport?.local.name || "defineComponent";
69
- if (defineComponentImport) {
70
- m.remove(
71
- defineComponentImport.start,
72
- defineComponentImport.end
73
- );
74
- }
75
- const defineNuxtComponentImport = findImportSpecifier(imports, "defineNuxtComponent", ["#app/composables/component", "#imports", "#app", "nuxt/app"]);
87
+ const defineNuxtComponentImport = findImportSpecifier(
88
+ imports,
89
+ "defineNuxtComponent",
90
+ ["#app/composables/component", "#imports", "#app", "nuxt/app"],
91
+ (specifier, next) => {
92
+ m.remove(
93
+ specifier.start,
94
+ next?.start ?? specifier.end
95
+ );
96
+ }
97
+ );
76
98
  const defineNuxtComponentAlias = defineNuxtComponentImport?.local.name || "defineNuxtComponent";
77
- if (defineNuxtComponentImport) {
78
- m.remove(
79
- defineNuxtComponentImport.start,
80
- defineNuxtComponentImport.end
81
- );
82
- }
83
99
  const importsToAdd = new Set([
84
100
  hasDefineComponent && genImport(
85
101
  "@nuxt/hints/runtime/hydration/component",
@@ -107,9 +123,11 @@ const InjectHydrationPlugin = createUnplugin(() => {
107
123
  filter: {
108
124
  id: {
109
125
  include: INCLUDE_VUE_RE,
110
- exclude: EXCLUDE_NODE_MODULES
126
+ exclude: [skipPath, EXCLUDE_NODE_MODULES]
111
127
  },
112
- code: /(?!defineComponent|defineNuxtComponent)/
128
+ code: {
129
+ exclude: [DEFINE_COMPONENT_RE, DEFINE_NUXT_COMPONENT_RE]
130
+ }
113
131
  },
114
132
  handler(code, id) {
115
133
  const m = new MagicString(code);
@@ -139,11 +157,19 @@ const InjectHydrationPlugin = createUnplugin(() => {
139
157
  }
140
158
  ];
141
159
  });
142
- function findImportSpecifier(importDecl, importedName, pkgNames) {
160
+ function findImportSpecifier(importDecl, importedName, pkgNames, callback) {
143
161
  const names = Array.isArray(pkgNames) ? pkgNames : [pkgNames];
144
- return importDecl.find((imp) => names.includes(imp.source.value))?.specifiers.find((specifier) => {
145
- return specifier.type === "ImportSpecifier" && specifier.imported.type === "Identifier" && specifier.imported.name === importedName;
146
- });
162
+ const allSpecifiers = importDecl.filter((imp) => names.includes(imp.source.value)).flatMap((decl) => decl.specifiers.map((spec, i) => ({ spec, next: decl.specifiers[i + 1] })));
163
+ const match = allSpecifiers.find(
164
+ ({ spec }) => spec.type === "ImportSpecifier" && spec.imported.type === "Identifier" && spec.imported.name === importedName
165
+ );
166
+ if (match) {
167
+ callback?.(match.spec, match.next);
168
+ return match.spec;
169
+ }
170
+ }
171
+ function normalizePath(path) {
172
+ return path.replace(/\\/g, "/");
147
173
  }
148
174
 
149
175
  const moduleName = "@nuxt/hints";
@@ -159,6 +185,8 @@ const module$1 = defineNuxtModule({
159
185
  if (!nuxt.options.dev) {
160
186
  return;
161
187
  }
188
+ nuxt.options.nitro.experimental = nuxt.options.nitro.experimental || {};
189
+ nuxt.options.nitro.experimental.websocket = true;
162
190
  const resolver = createResolver(import.meta.url);
163
191
  addComponent({
164
192
  name: "NuxtIsland",
@@ -168,6 +196,14 @@ const module$1 = defineNuxtModule({
168
196
  addPlugin(resolver.resolve("./runtime/web-vitals/plugin.client"));
169
197
  addPlugin(resolver.resolve("./runtime/hydration/plugin.client"));
170
198
  addBuildPlugin(InjectHydrationPlugin);
199
+ addServerHandler({
200
+ route: HYDRATION_ROUTE,
201
+ handler: resolver.resolve("./runtime/hydration/handler.nitro")
202
+ });
203
+ addServerHandler({
204
+ route: HYDRATION_SSE_ROUTE,
205
+ handler: resolver.resolve("./runtime/hydration/sse.nitro")
206
+ });
171
207
  addPlugin(resolver.resolve("./runtime/third-party-scripts/plugin.client"));
172
208
  addServerPlugin(resolver.resolve("./runtime/third-party-scripts/nitro.plugin"));
173
209
  nuxt.hook("prepare:types", ({ references }) => {
@@ -1,137 +1,2 @@
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;
1
+ declare const HintsNuxtIsland: any;
137
2
  export default HintsNuxtIsland;
@@ -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;
@@ -1,3 +1,3 @@
1
- import { defineComponent as _defineComponent } from 'vue';
1
+ import { defineComponent as _defineComponent } from '#imports';
2
2
  export declare const defineNuxtComponent: typeof _defineComponent;
3
3
  export declare const defineComponent: typeof _defineComponent;
@@ -1,5 +1,4 @@
1
- import { defineComponent as _defineComponent } from "vue";
2
- import { defineNuxtComponent as _defineNuxtComponent } from "nuxt/app";
1
+ import { defineNuxtComponent as _defineNuxtComponent, defineComponent as _defineComponent } from "#imports";
3
2
  import { useHydrationCheck } from "./composables.js";
4
3
  export const defineNuxtComponent = function defineNuxtComponent2(...args) {
5
4
  const [options, key] = args;
@@ -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, {
3
+ mismatches: HydrationMismatchPayload[];
4
+ } | Promise<void>>;
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;