UNPKG

next

Version:

The React Framework

171 lines (169 loc) • 7.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); 0 && (module.exports = { getHydrationWarningType: null, getReactHydrationDiffSegments: null, hydrationErrorState: null, storeHydrationErrorStateFromConsoleArgs: null }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { getHydrationWarningType: function() { return getHydrationWarningType; }, getReactHydrationDiffSegments: function() { return getReactHydrationDiffSegments; }, hydrationErrorState: function() { return hydrationErrorState; }, storeHydrationErrorStateFromConsoleArgs: function() { return storeHydrationErrorStateFromConsoleArgs; } }); const _ishydrationerror = require("../is-hydration-error"); const hydrationErrorState = {}; // https://github.com/facebook/react/blob/main/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js used as a reference const htmlTagsWarnings = new Set([ 'Warning: In HTML, %s cannot be a child of <%s>.%s\nThis will cause a hydration error.%s', 'Warning: In HTML, %s cannot be a descendant of <%s>.\nThis will cause a hydration error.%s', 'Warning: In HTML, text nodes cannot be a child of <%s>.\nThis will cause a hydration error.', "Warning: In HTML, whitespace text nodes cannot be a child of <%s>. Make sure you don't have any extra whitespace between tags on each line of your source code.\nThis will cause a hydration error.", 'Warning: Expected server HTML to contain a matching <%s> in <%s>.%s', 'Warning: Did not expect server HTML to contain a <%s> in <%s>.%s' ]); const textAndTagsMismatchWarnings = new Set([ 'Warning: Expected server HTML to contain a matching text node for "%s" in <%s>.%s', 'Warning: Did not expect server HTML to contain the text node "%s" in <%s>.%s' ]); const getHydrationWarningType = (message)=>{ if (typeof message !== 'string') { // TODO: Doesn't make sense to treat no message as a hydration error message. // We should bail out somewhere earlier. return 'text'; } const normalizedMessage = message.startsWith('Warning: ') ? message : "Warning: " + message; if (isHtmlTagsWarning(normalizedMessage)) return 'tag'; if (isTextInTagsMismatchWarning(normalizedMessage)) return 'text-in-tag'; return 'text'; }; const isHtmlTagsWarning = (message)=>htmlTagsWarnings.has(message); const isTextInTagsMismatchWarning = (msg)=>textAndTagsMismatchWarnings.has(msg); const getReactHydrationDiffSegments = (msg)=>{ if (msg) { const { message, diff } = (0, _ishydrationerror.getHydrationErrorStackInfo)(msg); if (message) return [ message, diff ]; } return undefined; }; function storeHydrationErrorStateFromConsoleArgs() { for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){ args[_key] = arguments[_key]; } let [msg, firstContent, secondContent, ...rest] = args; if ((0, _ishydrationerror.testReactHydrationWarning)(msg)) { // Some hydration warnings has 4 arguments, some has 3, fallback to the last argument // when the 3rd argument is not the component stack but an empty string const isReact18 = msg.startsWith('Warning: '); // For some warnings, there's only 1 argument for template. // The second argument is the diff or component stack. if (args.length === 3) { secondContent = ''; } const warning = [ // remove the last %s from the message msg, firstContent, secondContent ]; const lastArg = (rest[rest.length - 1] || '').trim(); if (!isReact18) { hydrationErrorState.reactOutputComponentDiff = lastArg; } else { hydrationErrorState.reactOutputComponentDiff = generateHydrationDiffReact18(msg, firstContent, secondContent, lastArg); } hydrationErrorState.warning = warning; hydrationErrorState.serverContent = firstContent; hydrationErrorState.clientContent = secondContent; } } /* * Some hydration errors in React 18 does not have the diff in the error message. * Instead it has the error stack trace which is component stack that we can leverage. * Will parse the diff from the error stack trace * e.g. * Warning: Expected server HTML to contain a matching <div> in <p>. * at div * at p * at div * at div * at Page * output: * <Page> * <div> * <p> * > <div> * */ function generateHydrationDiffReact18(message, firstContent, secondContent, lastArg) { const componentStack = lastArg; let firstIndex = -1; let secondIndex = -1; const hydrationWarningType = getHydrationWarningType(message); // at div\n at Foo\n at Bar (....)\n -> [div, Foo] const components = componentStack.split('\n')// .reverse() .map((line, index)=>{ // `<space>at <component> (<location>)` -> `at <component> (<location>)` line = line.trim(); // extract `<space>at <component>` to `<<component>>` // e.g. ` at Foo` -> `<Foo>` const [, component, location] = /at (\w+)( \((.*)\))?/.exec(line) || []; // If there's no location then it's user-land stack frame if (!location) { if (component === firstContent && firstIndex === -1) { firstIndex = index; } else if (component === secondContent && secondIndex === -1) { secondIndex = index; } } return location ? '' : component; }).filter(Boolean).reverse(); let diff = ''; for(let i = 0; i < components.length; i++){ const component = components[i]; const matchFirstContent = hydrationWarningType === 'tag' && i === components.length - firstIndex - 1; const matchSecondContent = hydrationWarningType === 'tag' && i === components.length - secondIndex - 1; if (matchFirstContent || matchSecondContent) { const spaces = ' '.repeat(Math.max(i * 2 - 2, 0) + 2); diff += "> " + spaces + "<" + component + ">\n"; } else { const spaces = ' '.repeat(i * 2 + 2); diff += spaces + "<" + component + ">\n"; } } if (hydrationWarningType === 'text') { const spaces = ' '.repeat(components.length * 2); diff += "+ " + spaces + '"' + firstContent + '"\n'; diff += "- " + spaces + '"' + secondContent + '"\n'; } else if (hydrationWarningType === 'text-in-tag') { const spaces = ' '.repeat(components.length * 2); diff += "> " + spaces + "<" + secondContent + ">\n"; diff += "> " + spaces + '"' + firstContent + '"\n'; } return diff; } if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') { Object.defineProperty(exports.default, '__esModule', { value: true }); Object.assign(exports.default, exports); module.exports = exports.default; } //# sourceMappingURL=hydration-error-info.js.map