@ifds/hydration-lens-react 0.1.0
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/README.md +33 -0
- package/dist/index.cjs +159 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @ifds/hydration-lens-react
|
|
2
|
+
|
|
3
|
+
React and Next.js adapter for hydration-lens. It recognizes React hydration warnings and runtime errors, locates the affected DOM element when possible, and displays the shared hydration-lens overlay.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install --save-dev @ifds/hydration-lens-react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Call `init()` from a client component rendered near the beginning of your root layout. Do not put it in `useEffect`: React 19 can report hydration errors during the initial synchronous hydrate pass.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
// app/hydration-lens-init.tsx
|
|
17
|
+
"use client";
|
|
18
|
+
|
|
19
|
+
import { init } from "@ifds/hydration-lens-react";
|
|
20
|
+
|
|
21
|
+
export function HydrationLensInit() {
|
|
22
|
+
if (process.env.NODE_ENV !== "production") init();
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Render `<HydrationLensInit />` as the first child in your root layout. `init(options?)` is safe to call more than once and is a no-op outside the browser. Pass `{ suppressConsole: true }` to suppress recognized framework logging.
|
|
28
|
+
|
|
29
|
+
Supports React 18 and 19 and Next.js applications. This is a diagnostic tool; it does not fix or change application output.
|
|
30
|
+
|
|
31
|
+
## License
|
|
32
|
+
|
|
33
|
+
MIT — see the repository [LICENSE](../../LICENSE).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
init: () => init,
|
|
24
|
+
parseReactRuntimeError: () => parseReactRuntimeError,
|
|
25
|
+
parseReactWarning: () => parseReactWarning,
|
|
26
|
+
reactAdapter: () => reactAdapter
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
var import_hydration_lens_core = require("@ifds-oss/hydration-lens-core");
|
|
30
|
+
|
|
31
|
+
// src/adapter.ts
|
|
32
|
+
var TEXT_MISMATCH = /Text content did not match\. Server: "?%s"? Client: "?%s"?/;
|
|
33
|
+
var TAG_MISMATCH = /Expected server HTML to contain a matching <%s> in <%s>/;
|
|
34
|
+
var HYDRATION_FAILED = /Hydration failed because/i;
|
|
35
|
+
var SUSPENSE_HYDRATION = /error occurred during hydration/i;
|
|
36
|
+
var HYDRATION_KEYWORD = /hydrat/i;
|
|
37
|
+
var STACK_LINE = /^\s*(?:in|at)\s+([A-Za-z0-9_.$]+)/;
|
|
38
|
+
var DIFF_LINE = /^([+-])\s+(.*)$/gm;
|
|
39
|
+
var TREE_TAG_LINE = /^\s*<([A-Za-z][\w.$]*)/gm;
|
|
40
|
+
var issueCounter = 0;
|
|
41
|
+
function formatMessage(format, values) {
|
|
42
|
+
let i = 0;
|
|
43
|
+
return format.replace(/%s/g, () => i < values.length ? values[i++] ?? "%s" : "%s");
|
|
44
|
+
}
|
|
45
|
+
function parseComponentTrail(stack) {
|
|
46
|
+
const trail = [];
|
|
47
|
+
for (const rawLine of stack.split("\n")) {
|
|
48
|
+
const line = rawLine.trim();
|
|
49
|
+
if (!line) continue;
|
|
50
|
+
const match = STACK_LINE.exec(line);
|
|
51
|
+
if (match?.[1]) trail.push({ name: match[1], raw: line });
|
|
52
|
+
}
|
|
53
|
+
return trail;
|
|
54
|
+
}
|
|
55
|
+
function findComponentStack(args) {
|
|
56
|
+
for (let i = args.length - 1; i >= 0; i--) {
|
|
57
|
+
const arg = args[i];
|
|
58
|
+
if (arg !== void 0 && /\n\s*(?:in|at)\s+\S/.test(arg)) return arg;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
function parseReactWarning(rawArgs) {
|
|
63
|
+
const format = typeof rawArgs[0] === "string" ? rawArgs[0] : "";
|
|
64
|
+
if (!format) return null;
|
|
65
|
+
let kind;
|
|
66
|
+
let expected = null;
|
|
67
|
+
let actual = null;
|
|
68
|
+
const stringArgs = rawArgs.map((arg) => typeof arg === "string" ? arg : String(arg));
|
|
69
|
+
const values = stringArgs.slice(1);
|
|
70
|
+
if (TEXT_MISMATCH.test(format)) {
|
|
71
|
+
kind = "text";
|
|
72
|
+
expected = values[0] ?? null;
|
|
73
|
+
actual = values[1] ?? null;
|
|
74
|
+
} else if (TAG_MISMATCH.test(format)) {
|
|
75
|
+
kind = "node";
|
|
76
|
+
actual = values[0] ?? null;
|
|
77
|
+
} else if (HYDRATION_FAILED.test(format) || SUSPENSE_HYDRATION.test(format)) {
|
|
78
|
+
kind = "unknown";
|
|
79
|
+
} else if (HYDRATION_KEYWORD.test(format)) {
|
|
80
|
+
kind = "unknown";
|
|
81
|
+
} else {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const stack = findComponentStack(stringArgs);
|
|
85
|
+
const componentTrail = stack ? parseComponentTrail(stack) : [];
|
|
86
|
+
const rawMessage = formatMessage(format, values);
|
|
87
|
+
return { kind, rawMessage, expected, actual, componentTrail, targetSelectorGuess: null };
|
|
88
|
+
}
|
|
89
|
+
function parseReactRuntimeError(error) {
|
|
90
|
+
const message = error.message;
|
|
91
|
+
if (!HYDRATION_KEYWORD.test(message)) return null;
|
|
92
|
+
let expected = null;
|
|
93
|
+
let actual = null;
|
|
94
|
+
for (const match of message.matchAll(DIFF_LINE)) {
|
|
95
|
+
const [, sign, value] = match;
|
|
96
|
+
if (sign === "+") actual = (value ?? "").trim();
|
|
97
|
+
else if (sign === "-") expected = (value ?? "").trim();
|
|
98
|
+
}
|
|
99
|
+
const kind = /text/i.test(message) ? "text" : "unknown";
|
|
100
|
+
const componentTrail = [];
|
|
101
|
+
for (const match of message.matchAll(TREE_TAG_LINE)) {
|
|
102
|
+
const name = match[1];
|
|
103
|
+
if (name) componentTrail.push({ name, raw: match[0].trim() });
|
|
104
|
+
}
|
|
105
|
+
return { kind, rawMessage: message, expected, actual, componentTrail, targetSelectorGuess: null };
|
|
106
|
+
}
|
|
107
|
+
var reactAdapter = {
|
|
108
|
+
name: "react",
|
|
109
|
+
install(bus, options) {
|
|
110
|
+
const original = console.error.bind(console);
|
|
111
|
+
console.error = (...args) => {
|
|
112
|
+
const parsed = parseReactWarning(args);
|
|
113
|
+
if (parsed) {
|
|
114
|
+
bus.emit({
|
|
115
|
+
id: `react-${++issueCounter}`,
|
|
116
|
+
timestamp: Date.now(),
|
|
117
|
+
framework: "react",
|
|
118
|
+
...parsed
|
|
119
|
+
});
|
|
120
|
+
if (options?.suppressConsole) return;
|
|
121
|
+
}
|
|
122
|
+
original(...args);
|
|
123
|
+
};
|
|
124
|
+
const errorListener = (event) => {
|
|
125
|
+
if (!(event.error instanceof Error)) return;
|
|
126
|
+
const parsed = parseReactRuntimeError(event.error);
|
|
127
|
+
if (!parsed) return;
|
|
128
|
+
bus.emit({
|
|
129
|
+
id: `react-${++issueCounter}`,
|
|
130
|
+
timestamp: Date.now(),
|
|
131
|
+
framework: "react",
|
|
132
|
+
...parsed
|
|
133
|
+
});
|
|
134
|
+
if (options?.suppressConsole) event.preventDefault();
|
|
135
|
+
};
|
|
136
|
+
if (typeof window !== "undefined") window.addEventListener("error", errorListener);
|
|
137
|
+
return () => {
|
|
138
|
+
console.error = original;
|
|
139
|
+
if (typeof window !== "undefined") window.removeEventListener("error", errorListener);
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// src/index.ts
|
|
145
|
+
var installed = false;
|
|
146
|
+
function init(options) {
|
|
147
|
+
if (typeof window === "undefined" || installed) return;
|
|
148
|
+
installed = true;
|
|
149
|
+
reactAdapter.install(import_hydration_lens_core.defaultBus, options);
|
|
150
|
+
(0, import_hydration_lens_core.mountOverlay)(import_hydration_lens_core.defaultBus);
|
|
151
|
+
}
|
|
152
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
153
|
+
0 && (module.exports = {
|
|
154
|
+
init,
|
|
155
|
+
parseReactRuntimeError,
|
|
156
|
+
parseReactWarning,
|
|
157
|
+
reactAdapter
|
|
158
|
+
});
|
|
159
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/adapter.ts"],"sourcesContent":["import type { AdapterOptions } from \"@ifds-oss/hydration-lens-core\";\nimport { defaultBus, mountOverlay } from \"@ifds-oss/hydration-lens-core\";\nimport { reactAdapter } from \"./adapter.js\";\n\nlet installed = false;\n\n/** Call once in a root layout / _app. Dev-only, no-ops outside the browser and on double-init. */\nexport function init(options?: AdapterOptions): void {\n if (typeof window === \"undefined\" || installed) return;\n installed = true;\n reactAdapter.install(defaultBus, options);\n mountOverlay(defaultBus);\n}\n\nexport { reactAdapter };\nexport { parseReactWarning, parseReactRuntimeError } from \"./adapter.js\";\n","import type { Adapter, AdapterOptions, ComponentTrailEntry, HydrationIssue, IssueBus, IssueKind } from \"@ifds-oss/hydration-lens-core\";\n\n/**\n * React's console.error hydration warnings are called with an un-interpolated format\n * string plus positional args (the browser substitutes %s only for display), so these\n * regexes match against the raw format string, not an already-substituted message.\n *\n * Exact wording has shifted across React 18/19 point releases (e.g. optional \"Warning: \"\n * prefix, quoted vs unquoted %s). These are intentionally tolerant of that drift; validate\n * against a live repro before adding new shapes.\n */\nconst TEXT_MISMATCH = /Text content did not match\\. Server: \"?%s\"? Client: \"?%s\"?/;\nconst TAG_MISMATCH = /Expected server HTML to contain a matching <%s> in <%s>/;\nconst HYDRATION_FAILED = /Hydration failed because/i;\nconst SUSPENSE_HYDRATION = /error occurred during hydration/i;\nconst HYDRATION_KEYWORD = /hydrat/i;\n\nconst STACK_LINE = /^\\s*(?:in|at)\\s+([A-Za-z0-9_.$]+)/;\n\n/**\n * React 19 reports most hydration mismatches as a genuinely *thrown* Error (surfaced to\n * the browser as an uncaught exception), not a console.error(...) call — so the regexes\n * above never see it at all. The message body is a JSX-tree-shaped diff instead of the\n * old \"Server: X Client: Y\" sentence, e.g.:\n *\n * Hydration failed because the server rendered text didn't match the client. ...\n * <Home>\n * <Mismatch>\n * <p id=\"mismatch\">\n * + client-value\n * - server-value\n *\n * `+` is the client-rendered value, `-` is the server-rendered one (standard diff convention).\n */\nconst DIFF_LINE = /^([+-])\\s+(.*)$/gm;\nconst TREE_TAG_LINE = /^\\s*<([A-Za-z][\\w.$]*)/gm;\n\nlet issueCounter = 0;\n\nfunction formatMessage(format: string, values: string[]): string {\n let i = 0;\n return format.replace(/%s/g, () => (i < values.length ? (values[i++] ?? \"%s\") : \"%s\"));\n}\n\nfunction parseComponentTrail(stack: string): ComponentTrailEntry[] {\n const trail: ComponentTrailEntry[] = [];\n for (const rawLine of stack.split(\"\\n\")) {\n const line = rawLine.trim();\n if (!line) continue;\n const match = STACK_LINE.exec(line);\n if (match?.[1]) trail.push({ name: match[1], raw: line });\n }\n return trail;\n}\n\nfunction findComponentStack(args: string[]): string | null {\n for (let i = args.length - 1; i >= 0; i--) {\n const arg = args[i];\n if (arg !== undefined && /\\n\\s*(?:in|at)\\s+\\S/.test(arg)) return arg;\n }\n return null;\n}\n\nexport type ParsedReactIssue = Omit<HydrationIssue, \"id\" | \"timestamp\" | \"framework\">;\n\n/** Returns null when the console.error call isn't a hydration warning at all. */\nexport function parseReactWarning(rawArgs: unknown[]): ParsedReactIssue | null {\n const format = typeof rawArgs[0] === \"string\" ? rawArgs[0] : \"\";\n if (!format) return null;\n\n let kind: IssueKind;\n let expected: string | null = null;\n let actual: string | null = null;\n const stringArgs = rawArgs.map((arg) => (typeof arg === \"string\" ? arg : String(arg)));\n const values = stringArgs.slice(1);\n\n if (TEXT_MISMATCH.test(format)) {\n kind = \"text\";\n expected = values[0] ?? null;\n actual = values[1] ?? null;\n } else if (TAG_MISMATCH.test(format)) {\n kind = \"node\";\n actual = values[0] ?? null;\n } else if (HYDRATION_FAILED.test(format) || SUSPENSE_HYDRATION.test(format)) {\n kind = \"unknown\";\n } else if (HYDRATION_KEYWORD.test(format)) {\n kind = \"unknown\";\n } else {\n return null;\n }\n\n const stack = findComponentStack(stringArgs);\n const componentTrail = stack ? parseComponentTrail(stack) : [];\n const rawMessage = formatMessage(format, values);\n\n return { kind, rawMessage, expected, actual, componentTrail, targetSelectorGuess: null };\n}\n\n/** Parses the thrown-Error shape described above. Returns null if it isn't hydration-related. */\nexport function parseReactRuntimeError(error: Error): ParsedReactIssue | null {\n const message = error.message;\n if (!HYDRATION_KEYWORD.test(message)) return null;\n\n let expected: string | null = null;\n let actual: string | null = null;\n for (const match of message.matchAll(DIFF_LINE)) {\n const [, sign, value] = match;\n if (sign === \"+\") actual = (value ?? \"\").trim();\n else if (sign === \"-\") expected = (value ?? \"\").trim();\n }\n\n const kind: IssueKind = /text/i.test(message) ? \"text\" : \"unknown\";\n\n const componentTrail: ComponentTrailEntry[] = [];\n for (const match of message.matchAll(TREE_TAG_LINE)) {\n const name = match[1];\n if (name) componentTrail.push({ name, raw: match[0].trim() });\n }\n\n return { kind, rawMessage: message, expected, actual, componentTrail, targetSelectorGuess: null };\n}\n\nexport const reactAdapter: Adapter = {\n name: \"react\",\n install(bus: IssueBus, options?: AdapterOptions) {\n const original = console.error.bind(console);\n\n console.error = (...args: unknown[]) => {\n const parsed = parseReactWarning(args);\n if (parsed) {\n bus.emit({\n id: `react-${++issueCounter}`,\n timestamp: Date.now(),\n framework: \"react\",\n ...parsed,\n });\n if (options?.suppressConsole) return; // swallow only warnings we recognized as hydration issues\n }\n original(...args);\n };\n\n const errorListener = (event: ErrorEvent) => {\n if (!(event.error instanceof Error)) return;\n const parsed = parseReactRuntimeError(event.error);\n if (!parsed) return;\n bus.emit({\n id: `react-${++issueCounter}`,\n timestamp: Date.now(),\n framework: \"react\",\n ...parsed,\n });\n if (options?.suppressConsole) event.preventDefault(); // suppress only the default uncaught-error logging\n };\n if (typeof window !== \"undefined\") window.addEventListener(\"error\", errorListener);\n\n return () => {\n console.error = original;\n if (typeof window !== \"undefined\") window.removeEventListener(\"error\", errorListener);\n };\n },\n};\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { HydrationIssue, Adapter, AdapterOptions } from '@ifds-oss/hydration-lens-core';
|
|
2
|
+
|
|
3
|
+
type ParsedReactIssue = Omit<HydrationIssue, "id" | "timestamp" | "framework">;
|
|
4
|
+
/** Returns null when the console.error call isn't a hydration warning at all. */
|
|
5
|
+
declare function parseReactWarning(rawArgs: unknown[]): ParsedReactIssue | null;
|
|
6
|
+
/** Parses the thrown-Error shape described above. Returns null if it isn't hydration-related. */
|
|
7
|
+
declare function parseReactRuntimeError(error: Error): ParsedReactIssue | null;
|
|
8
|
+
declare const reactAdapter: Adapter;
|
|
9
|
+
|
|
10
|
+
/** Call once in a root layout / _app. Dev-only, no-ops outside the browser and on double-init. */
|
|
11
|
+
declare function init(options?: AdapterOptions): void;
|
|
12
|
+
|
|
13
|
+
export { init, parseReactRuntimeError, parseReactWarning, reactAdapter };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { HydrationIssue, Adapter, AdapterOptions } from '@ifds-oss/hydration-lens-core';
|
|
2
|
+
|
|
3
|
+
type ParsedReactIssue = Omit<HydrationIssue, "id" | "timestamp" | "framework">;
|
|
4
|
+
/** Returns null when the console.error call isn't a hydration warning at all. */
|
|
5
|
+
declare function parseReactWarning(rawArgs: unknown[]): ParsedReactIssue | null;
|
|
6
|
+
/** Parses the thrown-Error shape described above. Returns null if it isn't hydration-related. */
|
|
7
|
+
declare function parseReactRuntimeError(error: Error): ParsedReactIssue | null;
|
|
8
|
+
declare const reactAdapter: Adapter;
|
|
9
|
+
|
|
10
|
+
/** Call once in a root layout / _app. Dev-only, no-ops outside the browser and on double-init. */
|
|
11
|
+
declare function init(options?: AdapterOptions): void;
|
|
12
|
+
|
|
13
|
+
export { init, parseReactRuntimeError, parseReactWarning, reactAdapter };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { defaultBus, mountOverlay } from "@ifds-oss/hydration-lens-core";
|
|
3
|
+
|
|
4
|
+
// src/adapter.ts
|
|
5
|
+
var TEXT_MISMATCH = /Text content did not match\. Server: "?%s"? Client: "?%s"?/;
|
|
6
|
+
var TAG_MISMATCH = /Expected server HTML to contain a matching <%s> in <%s>/;
|
|
7
|
+
var HYDRATION_FAILED = /Hydration failed because/i;
|
|
8
|
+
var SUSPENSE_HYDRATION = /error occurred during hydration/i;
|
|
9
|
+
var HYDRATION_KEYWORD = /hydrat/i;
|
|
10
|
+
var STACK_LINE = /^\s*(?:in|at)\s+([A-Za-z0-9_.$]+)/;
|
|
11
|
+
var DIFF_LINE = /^([+-])\s+(.*)$/gm;
|
|
12
|
+
var TREE_TAG_LINE = /^\s*<([A-Za-z][\w.$]*)/gm;
|
|
13
|
+
var issueCounter = 0;
|
|
14
|
+
function formatMessage(format, values) {
|
|
15
|
+
let i = 0;
|
|
16
|
+
return format.replace(/%s/g, () => i < values.length ? values[i++] ?? "%s" : "%s");
|
|
17
|
+
}
|
|
18
|
+
function parseComponentTrail(stack) {
|
|
19
|
+
const trail = [];
|
|
20
|
+
for (const rawLine of stack.split("\n")) {
|
|
21
|
+
const line = rawLine.trim();
|
|
22
|
+
if (!line) continue;
|
|
23
|
+
const match = STACK_LINE.exec(line);
|
|
24
|
+
if (match?.[1]) trail.push({ name: match[1], raw: line });
|
|
25
|
+
}
|
|
26
|
+
return trail;
|
|
27
|
+
}
|
|
28
|
+
function findComponentStack(args) {
|
|
29
|
+
for (let i = args.length - 1; i >= 0; i--) {
|
|
30
|
+
const arg = args[i];
|
|
31
|
+
if (arg !== void 0 && /\n\s*(?:in|at)\s+\S/.test(arg)) return arg;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function parseReactWarning(rawArgs) {
|
|
36
|
+
const format = typeof rawArgs[0] === "string" ? rawArgs[0] : "";
|
|
37
|
+
if (!format) return null;
|
|
38
|
+
let kind;
|
|
39
|
+
let expected = null;
|
|
40
|
+
let actual = null;
|
|
41
|
+
const stringArgs = rawArgs.map((arg) => typeof arg === "string" ? arg : String(arg));
|
|
42
|
+
const values = stringArgs.slice(1);
|
|
43
|
+
if (TEXT_MISMATCH.test(format)) {
|
|
44
|
+
kind = "text";
|
|
45
|
+
expected = values[0] ?? null;
|
|
46
|
+
actual = values[1] ?? null;
|
|
47
|
+
} else if (TAG_MISMATCH.test(format)) {
|
|
48
|
+
kind = "node";
|
|
49
|
+
actual = values[0] ?? null;
|
|
50
|
+
} else if (HYDRATION_FAILED.test(format) || SUSPENSE_HYDRATION.test(format)) {
|
|
51
|
+
kind = "unknown";
|
|
52
|
+
} else if (HYDRATION_KEYWORD.test(format)) {
|
|
53
|
+
kind = "unknown";
|
|
54
|
+
} else {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const stack = findComponentStack(stringArgs);
|
|
58
|
+
const componentTrail = stack ? parseComponentTrail(stack) : [];
|
|
59
|
+
const rawMessage = formatMessage(format, values);
|
|
60
|
+
return { kind, rawMessage, expected, actual, componentTrail, targetSelectorGuess: null };
|
|
61
|
+
}
|
|
62
|
+
function parseReactRuntimeError(error) {
|
|
63
|
+
const message = error.message;
|
|
64
|
+
if (!HYDRATION_KEYWORD.test(message)) return null;
|
|
65
|
+
let expected = null;
|
|
66
|
+
let actual = null;
|
|
67
|
+
for (const match of message.matchAll(DIFF_LINE)) {
|
|
68
|
+
const [, sign, value] = match;
|
|
69
|
+
if (sign === "+") actual = (value ?? "").trim();
|
|
70
|
+
else if (sign === "-") expected = (value ?? "").trim();
|
|
71
|
+
}
|
|
72
|
+
const kind = /text/i.test(message) ? "text" : "unknown";
|
|
73
|
+
const componentTrail = [];
|
|
74
|
+
for (const match of message.matchAll(TREE_TAG_LINE)) {
|
|
75
|
+
const name = match[1];
|
|
76
|
+
if (name) componentTrail.push({ name, raw: match[0].trim() });
|
|
77
|
+
}
|
|
78
|
+
return { kind, rawMessage: message, expected, actual, componentTrail, targetSelectorGuess: null };
|
|
79
|
+
}
|
|
80
|
+
var reactAdapter = {
|
|
81
|
+
name: "react",
|
|
82
|
+
install(bus, options) {
|
|
83
|
+
const original = console.error.bind(console);
|
|
84
|
+
console.error = (...args) => {
|
|
85
|
+
const parsed = parseReactWarning(args);
|
|
86
|
+
if (parsed) {
|
|
87
|
+
bus.emit({
|
|
88
|
+
id: `react-${++issueCounter}`,
|
|
89
|
+
timestamp: Date.now(),
|
|
90
|
+
framework: "react",
|
|
91
|
+
...parsed
|
|
92
|
+
});
|
|
93
|
+
if (options?.suppressConsole) return;
|
|
94
|
+
}
|
|
95
|
+
original(...args);
|
|
96
|
+
};
|
|
97
|
+
const errorListener = (event) => {
|
|
98
|
+
if (!(event.error instanceof Error)) return;
|
|
99
|
+
const parsed = parseReactRuntimeError(event.error);
|
|
100
|
+
if (!parsed) return;
|
|
101
|
+
bus.emit({
|
|
102
|
+
id: `react-${++issueCounter}`,
|
|
103
|
+
timestamp: Date.now(),
|
|
104
|
+
framework: "react",
|
|
105
|
+
...parsed
|
|
106
|
+
});
|
|
107
|
+
if (options?.suppressConsole) event.preventDefault();
|
|
108
|
+
};
|
|
109
|
+
if (typeof window !== "undefined") window.addEventListener("error", errorListener);
|
|
110
|
+
return () => {
|
|
111
|
+
console.error = original;
|
|
112
|
+
if (typeof window !== "undefined") window.removeEventListener("error", errorListener);
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/index.ts
|
|
118
|
+
var installed = false;
|
|
119
|
+
function init(options) {
|
|
120
|
+
if (typeof window === "undefined" || installed) return;
|
|
121
|
+
installed = true;
|
|
122
|
+
reactAdapter.install(defaultBus, options);
|
|
123
|
+
mountOverlay(defaultBus);
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
init,
|
|
127
|
+
parseReactRuntimeError,
|
|
128
|
+
parseReactWarning,
|
|
129
|
+
reactAdapter
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/adapter.ts"],"sourcesContent":["import type { AdapterOptions } from \"@ifds-oss/hydration-lens-core\";\nimport { defaultBus, mountOverlay } from \"@ifds-oss/hydration-lens-core\";\nimport { reactAdapter } from \"./adapter.js\";\n\nlet installed = false;\n\n/** Call once in a root layout / _app. Dev-only, no-ops outside the browser and on double-init. */\nexport function init(options?: AdapterOptions): void {\n if (typeof window === \"undefined\" || installed) return;\n installed = true;\n reactAdapter.install(defaultBus, options);\n mountOverlay(defaultBus);\n}\n\nexport { reactAdapter };\nexport { parseReactWarning, parseReactRuntimeError } from \"./adapter.js\";\n","import type { Adapter, AdapterOptions, ComponentTrailEntry, HydrationIssue, IssueBus, IssueKind } from \"@ifds-oss/hydration-lens-core\";\n\n/**\n * React's console.error hydration warnings are called with an un-interpolated format\n * string plus positional args (the browser substitutes %s only for display), so these\n * regexes match against the raw format string, not an already-substituted message.\n *\n * Exact wording has shifted across React 18/19 point releases (e.g. optional \"Warning: \"\n * prefix, quoted vs unquoted %s). These are intentionally tolerant of that drift; validate\n * against a live repro before adding new shapes.\n */\nconst TEXT_MISMATCH = /Text content did not match\\. Server: \"?%s\"? Client: \"?%s\"?/;\nconst TAG_MISMATCH = /Expected server HTML to contain a matching <%s> in <%s>/;\nconst HYDRATION_FAILED = /Hydration failed because/i;\nconst SUSPENSE_HYDRATION = /error occurred during hydration/i;\nconst HYDRATION_KEYWORD = /hydrat/i;\n\nconst STACK_LINE = /^\\s*(?:in|at)\\s+([A-Za-z0-9_.$]+)/;\n\n/**\n * React 19 reports most hydration mismatches as a genuinely *thrown* Error (surfaced to\n * the browser as an uncaught exception), not a console.error(...) call — so the regexes\n * above never see it at all. The message body is a JSX-tree-shaped diff instead of the\n * old \"Server: X Client: Y\" sentence, e.g.:\n *\n * Hydration failed because the server rendered text didn't match the client. ...\n * <Home>\n * <Mismatch>\n * <p id=\"mismatch\">\n * + client-value\n * - server-value\n *\n * `+` is the client-rendered value, `-` is the server-rendered one (standard diff convention).\n */\nconst DIFF_LINE = /^([+-])\\s+(.*)$/gm;\nconst TREE_TAG_LINE = /^\\s*<([A-Za-z][\\w.$]*)/gm;\n\nlet issueCounter = 0;\n\nfunction formatMessage(format: string, values: string[]): string {\n let i = 0;\n return format.replace(/%s/g, () => (i < values.length ? (values[i++] ?? \"%s\") : \"%s\"));\n}\n\nfunction parseComponentTrail(stack: string): ComponentTrailEntry[] {\n const trail: ComponentTrailEntry[] = [];\n for (const rawLine of stack.split(\"\\n\")) {\n const line = rawLine.trim();\n if (!line) continue;\n const match = STACK_LINE.exec(line);\n if (match?.[1]) trail.push({ name: match[1], raw: line });\n }\n return trail;\n}\n\nfunction findComponentStack(args: string[]): string | null {\n for (let i = args.length - 1; i >= 0; i--) {\n const arg = args[i];\n if (arg !== undefined && /\\n\\s*(?:in|at)\\s+\\S/.test(arg)) return arg;\n }\n return null;\n}\n\nexport type ParsedReactIssue = Omit<HydrationIssue, \"id\" | \"timestamp\" | \"framework\">;\n\n/** Returns null when the console.error call isn't a hydration warning at all. */\nexport function parseReactWarning(rawArgs: unknown[]): ParsedReactIssue | null {\n const format = typeof rawArgs[0] === \"string\" ? rawArgs[0] : \"\";\n if (!format) return null;\n\n let kind: IssueKind;\n let expected: string | null = null;\n let actual: string | null = null;\n const stringArgs = rawArgs.map((arg) => (typeof arg === \"string\" ? arg : String(arg)));\n const values = stringArgs.slice(1);\n\n if (TEXT_MISMATCH.test(format)) {\n kind = \"text\";\n expected = values[0] ?? null;\n actual = values[1] ?? null;\n } else if (TAG_MISMATCH.test(format)) {\n kind = \"node\";\n actual = values[0] ?? null;\n } else if (HYDRATION_FAILED.test(format) || SUSPENSE_HYDRATION.test(format)) {\n kind = \"unknown\";\n } else if (HYDRATION_KEYWORD.test(format)) {\n kind = \"unknown\";\n } else {\n return null;\n }\n\n const stack = findComponentStack(stringArgs);\n const componentTrail = stack ? parseComponentTrail(stack) : [];\n const rawMessage = formatMessage(format, values);\n\n return { kind, rawMessage, expected, actual, componentTrail, targetSelectorGuess: null };\n}\n\n/** Parses the thrown-Error shape described above. Returns null if it isn't hydration-related. */\nexport function parseReactRuntimeError(error: Error): ParsedReactIssue | null {\n const message = error.message;\n if (!HYDRATION_KEYWORD.test(message)) return null;\n\n let expected: string | null = null;\n let actual: string | null = null;\n for (const match of message.matchAll(DIFF_LINE)) {\n const [, sign, value] = match;\n if (sign === \"+\") actual = (value ?? \"\").trim();\n else if (sign === \"-\") expected = (value ?? \"\").trim();\n }\n\n const kind: IssueKind = /text/i.test(message) ? \"text\" : \"unknown\";\n\n const componentTrail: ComponentTrailEntry[] = [];\n for (const match of message.matchAll(TREE_TAG_LINE)) {\n const name = match[1];\n if (name) componentTrail.push({ name, raw: match[0].trim() });\n }\n\n return { kind, rawMessage: message, expected, actual, componentTrail, targetSelectorGuess: null };\n}\n\nexport const reactAdapter: Adapter = {\n name: \"react\",\n install(bus: IssueBus, options?: AdapterOptions) {\n const original = console.error.bind(console);\n\n console.error = (...args: unknown[]) => {\n const parsed = parseReactWarning(args);\n if (parsed) {\n bus.emit({\n id: `react-${++issueCounter}`,\n timestamp: Date.now(),\n framework: \"react\",\n ...parsed,\n });\n if (options?.suppressConsole) return; // swallow only warnings we recognized as hydration issues\n }\n original(...args);\n };\n\n const errorListener = (event: ErrorEvent) => {\n if (!(event.error instanceof Error)) return;\n const parsed = parseReactRuntimeError(event.error);\n if (!parsed) return;\n bus.emit({\n id: `react-${++issueCounter}`,\n timestamp: Date.now(),\n framework: \"react\",\n ...parsed,\n });\n if (options?.suppressConsole) event.preventDefault(); // suppress only the default uncaught-error logging\n };\n if (typeof window !== \"undefined\") window.addEventListener(\"error\", errorListener);\n\n return () => {\n console.error = original;\n if (typeof window !== \"undefined\") window.removeEventListener(\"error\", errorListener);\n };\n },\n};\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ifds/hydration-lens-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React/Next.js adapter for hydration-lens: patches console.error and parses React hydration warnings.",
|
|
5
|
+
"keywords": ["react", "nextjs", "hydration", "hydration-mismatch", "ssr", "developer-tools"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": { "type": "git", "url": "git+https://github.com/IFDS-OSS/hydration-lens.git", "directory": "packages/react" },
|
|
8
|
+
"homepage": "https://github.com/IFDS-OSS/hydration-lens#readme",
|
|
9
|
+
"bugs": { "url": "https://github.com/IFDS-OSS/hydration-lens/issues" },
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/index.cjs",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
|
|
17
|
+
"require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": ["dist", "README.md"],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"typecheck": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": ">=18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"peerDependenciesMeta": {
|
|
29
|
+
"react": { "optional": true }
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@ifds/hydration-lens-core": "workspace:*"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"tsup": "^8.3.5",
|
|
36
|
+
"typescript": "^5.7.2"
|
|
37
|
+
}
|
|
38
|
+
}
|