diff --git a/fixtures/ssr/src/components/Page.js b/fixtures/ssr/src/components/Page.js
index 0284b6b39d811..806d1115c8207 100644
--- a/fixtures/ssr/src/components/Page.js
+++ b/fixtures/ssr/src/components/Page.js
@@ -15,6 +15,9 @@ export default class Page extends Component {
);
return (
+
+ A random number: {Math.random()}
+
{!this.state.active ? link : 'Thanks!'}
diff --git a/fixtures/ssr/src/index.js b/fixtures/ssr/src/index.js
index ca551c8dadc9a..e4364cfbea28d 100644
--- a/fixtures/ssr/src/index.js
+++ b/fixtures/ssr/src/index.js
@@ -1,6 +1,6 @@
import React from 'react';
-import {render} from 'react-dom';
+import {hydrate} from 'react-dom';
import App from './components/App';
-render(
, document);
+hydrate(
, document);
diff --git a/src/renderers/dom/fiber/ReactDOMFiberComponent.js b/src/renderers/dom/fiber/ReactDOMFiberComponent.js
index 8cb1f608ba574..07a84c242ad28 100644
--- a/src/renderers/dom/fiber/ReactDOMFiberComponent.js
+++ b/src/renderers/dom/fiber/ReactDOMFiberComponent.js
@@ -53,6 +53,7 @@ var registrationNameModules = EventPluginRegistry.registrationNameModules;
var DANGEROUSLY_SET_INNER_HTML = 'dangerouslySetInnerHTML';
var SUPPRESS_CONTENT_EDITABLE_WARNING = 'suppressContentEditableWarning';
+var SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
var CHILDREN = 'children';
var STYLE = 'style';
var HTML = '__html';
@@ -273,7 +274,10 @@ function setInitialDOMProperties(
} else if (typeof nextProp === 'number') {
setTextContent(domElement, '' + nextProp);
}
- } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING) {
+ } else if (
+ propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
+ propKey === SUPPRESS_HYDRATION_WARNING
+ ) {
// Noop
} else if (registrationNameModules.hasOwnProperty(propKey)) {
if (nextProp != null) {
@@ -664,7 +668,10 @@ var ReactDOMFiberComponent = {
propKey === CHILDREN
) {
// Noop. This is handled by the clear text mechanism.
- } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING) {
+ } else if (
+ propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
+ propKey === SUPPRESS_HYDRATION_WARNING
+ ) {
// Noop
} else if (registrationNameModules.hasOwnProperty(propKey)) {
// This is a special case. If any listener updates we need to ensure
@@ -750,7 +757,10 @@ var ReactDOMFiberComponent = {
) {
(updatePayload = updatePayload || []).push(propKey, '' + nextProp);
}
- } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING) {
+ } else if (
+ propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
+ propKey === SUPPRESS_HYDRATION_WARNING
+ ) {
// Noop
} else if (registrationNameModules.hasOwnProperty(propKey)) {
if (nextProp != null) {
@@ -828,6 +838,8 @@ var ReactDOMFiberComponent = {
rootContainerElement: Element | Document,
): null | Array
{
if (__DEV__) {
+ var suppressHydrationWarning =
+ rawProps[SUPPRESS_HYDRATION_WARNING] === true;
var isCustomComponentTag = isCustomComponent(tag, rawProps);
validatePropertiesInDevelopment(tag, rawProps);
if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
@@ -986,14 +998,14 @@ var ReactDOMFiberComponent = {
// TODO: Should we use domElement.firstChild.nodeValue to compare?
if (typeof nextProp === 'string') {
if (domElement.textContent !== nextProp) {
- if (__DEV__) {
+ if (__DEV__ && !suppressHydrationWarning) {
warnForTextDifference(domElement.textContent, nextProp);
}
updatePayload = [CHILDREN, nextProp];
}
} else if (typeof nextProp === 'number') {
if (domElement.textContent !== '' + nextProp) {
- if (__DEV__) {
+ if (__DEV__ && !suppressHydrationWarning) {
warnForTextDifference(domElement.textContent, nextProp);
}
updatePayload = [CHILDREN, '' + nextProp];
@@ -1010,8 +1022,11 @@ var ReactDOMFiberComponent = {
// Validate that the properties correspond to their expected values.
var serverValue;
var propertyInfo;
- if (
+ if (suppressHydrationWarning) {
+ // Don't bother comparing. We're ignoring all these warnings.
+ } else if (
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
+ propKey === SUPPRESS_HYDRATION_WARNING ||
// Controlled attributes are not validated
// TODO: Only ignore them on controlled tags.
propKey === 'value' ||
@@ -1085,7 +1100,7 @@ var ReactDOMFiberComponent = {
if (__DEV__) {
// $FlowFixMe - Should be inferred as not undefined.
- if (extraAttributeNames.size > 0) {
+ if (extraAttributeNames.size > 0 && !suppressHydrationWarning) {
// $FlowFixMe - Should be inferred as not undefined.
warnForExtraAttributes(extraAttributeNames);
}
@@ -1125,12 +1140,13 @@ var ReactDOMFiberComponent = {
diffHydratedText(textNode: Text, text: string): boolean {
const isDifferent = textNode.nodeValue !== text;
+ return isDifferent;
+ },
+
+ warnForUnmatchedText(textNode: Text, text: string) {
if (__DEV__) {
- if (isDifferent) {
- warnForTextDifference(textNode.nodeValue, text);
- }
+ warnForTextDifference(textNode.nodeValue, text);
}
- return isDifferent;
},
warnForDeletedHydratableElement(
diff --git a/src/renderers/dom/fiber/ReactDOMFiberEntry.js b/src/renderers/dom/fiber/ReactDOMFiberEntry.js
index 0e0559d47a1d7..d0dd466bfb4fb 100644
--- a/src/renderers/dom/fiber/ReactDOMFiberEntry.js
+++ b/src/renderers/dom/fiber/ReactDOMFiberEntry.js
@@ -50,6 +50,7 @@ var {
updateProperties,
diffHydratedProperties,
diffHydratedText,
+ warnForUnmatchedText,
warnForDeletedHydratableElement,
warnForDeletedHydratableText,
warnForInsertedHydratedElement,
@@ -58,6 +59,7 @@ var {
var {precacheFiberNode, updateFiberProps} = ReactDOMComponentTree;
if (__DEV__) {
+ var SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
var lowPriorityWarning = require('lowPriorityWarning');
var warning = require('fbjs/lib/warning');
var validateDOMNesting = require('validateDOMNesting');
@@ -99,6 +101,7 @@ type Props = {
autoFocus?: boolean,
children?: mixed,
hidden?: boolean,
+ suppressHydrationWarning?: boolean,
};
type Instance = Element;
type TextInstance = Text;
@@ -523,30 +526,96 @@ var DOMRenderer = ReactFiberReconciler({
return diffHydratedText(textInstance, text);
},
+ didNotMatchHydratedContainerTextInstance(
+ parentContainer: Container,
+ textInstance: TextInstance,
+ text: string,
+ ) {
+ if (__DEV__) {
+ warnForUnmatchedText(textInstance, text);
+ }
+ },
+
+ didNotMatchHydratedTextInstance(
+ parentType: string,
+ parentProps: Props,
+ parentInstance: Instance,
+ textInstance: TextInstance,
+ text: string,
+ ) {
+ if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
+ warnForUnmatchedText(textInstance, text);
+ }
+ },
+
+ didNotHydrateContainerInstance(
+ parentContainer: Container,
+ instance: Instance | TextInstance,
+ ) {
+ if (__DEV__) {
+ if (instance.nodeType === 1) {
+ warnForDeletedHydratableElement(parentContainer, (instance: any));
+ } else {
+ warnForDeletedHydratableText(parentContainer, (instance: any));
+ }
+ }
+ },
+
didNotHydrateInstance(
- parentInstance: Instance | Container,
+ parentType: string,
+ parentProps: Props,
+ parentInstance: Instance,
instance: Instance | TextInstance,
) {
- if (instance.nodeType === 1) {
- warnForDeletedHydratableElement(parentInstance, (instance: any));
- } else {
- warnForDeletedHydratableText(parentInstance, (instance: any));
+ if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
+ if (instance.nodeType === 1) {
+ warnForDeletedHydratableElement(parentInstance, (instance: any));
+ } else {
+ warnForDeletedHydratableText(parentInstance, (instance: any));
+ }
+ }
+ },
+
+ didNotFindHydratableContainerInstance(
+ parentContainer: Container,
+ type: string,
+ props: Props,
+ ) {
+ if (__DEV__) {
+ warnForInsertedHydratedElement(parentContainer, type, props);
+ }
+ },
+
+ didNotFindHydratableContainerTextInstance(
+ parentContainer: Container,
+ text: string,
+ ) {
+ if (__DEV__) {
+ warnForInsertedHydratedText(parentContainer, text);
}
},
didNotFindHydratableInstance(
- parentInstance: Instance | Container,
+ parentType: string,
+ parentProps: Props,
+ parentInstance: Instance,
type: string,
props: Props,
) {
- warnForInsertedHydratedElement(parentInstance, type, props);
+ if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
+ warnForInsertedHydratedElement(parentInstance, type, props);
+ }
},
didNotFindHydratableTextInstance(
- parentInstance: Instance | Container,
+ parentType: string,
+ parentProps: Props,
+ parentInstance: Instance,
text: string,
) {
- warnForInsertedHydratedText(parentInstance, text);
+ if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
+ warnForInsertedHydratedText(parentInstance, text);
+ }
},
scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,
diff --git a/src/renderers/dom/shared/DOMProperty.js b/src/renderers/dom/shared/DOMProperty.js
index 6ca4c3cbc4914..197b57971baad 100644
--- a/src/renderers/dom/shared/DOMProperty.js
+++ b/src/renderers/dom/shared/DOMProperty.js
@@ -21,6 +21,7 @@ var RESERVED_PROPS = {
defaultChecked: true,
innerHTML: true,
suppressContentEditableWarning: true,
+ suppressHydrationWarning: true,
style: true,
};
diff --git a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
index 8c8c9ea1e41ef..b140a31301993 100644
--- a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
+++ b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
@@ -318,6 +318,7 @@ describe('ReactDOMComponent', () => {
,
container,
);
@@ -325,11 +326,15 @@ describe('ReactDOMComponent', () => {
expect(
container.firstChild.hasAttribute('suppressContentEditableWarning'),
).toBe(false);
+ expect(
+ container.firstChild.hasAttribute('suppressHydrationWarning'),
+ ).toBe(false);
ReactDOM.render(
,
container,
);
@@ -337,6 +342,9 @@ describe('ReactDOMComponent', () => {
expect(
container.firstChild.hasAttribute('suppressContentEditableWarning'),
).toBe(false);
+ expect(
+ container.firstChild.hasAttribute('suppressHydrationWarning'),
+ ).toBe(false);
});
it('should skip dangerouslySetInnerHTML on web components', () => {
diff --git a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
index faa22ff76ccfd..f4949a77ce596 100644
--- a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
+++ b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
@@ -725,6 +725,16 @@ describe('ReactDOMServerIntegration', () => {
);
expect(e.getAttribute('dangerouslySetInnerHTML')).toBe(null);
});
+
+ itRenders('no suppressContentEditableWarning attribute', async render => {
+ const e = await render();
+ expect(e.getAttribute('suppressContentEditableWarning')).toBe(null);
+ });
+
+ itRenders('no suppressHydrationWarning attribute', async render => {
+ const e = await render();
+ expect(e.getAttribute('suppressHydrationWarning')).toBe(null);
+ });
});
describe('inline styles', function() {
@@ -2623,6 +2633,36 @@ describe('ReactDOMServerIntegration', () => {
it('should error reconnecting different attribute values', () =>
expectMarkupMismatch(, ));
+
+ it('can explicitly ignore errors reconnecting different element types of children', () =>
+ expectMarkupMatch(
+ ,
+
,
+ ));
+
+ it('can explicitly ignore errors reconnecting missing attributes', () =>
+ expectMarkupMatch(
+ ,
+ ,
+ ));
+
+ it('can explicitly ignore errors reconnecting added attributes', () =>
+ expectMarkupMatch(
+ ,
+ ,
+ ));
+
+ it('can explicitly ignore errors reconnecting different attribute values', () =>
+ expectMarkupMatch(
+ ,
+ ,
+ ));
+
+ it('can not deeply ignore errors reconnecting different attribute values', () =>
+ expectMarkupMismatch(
+ ,
+ ,
+ ));
});
describe('inline styles', function() {
@@ -2661,6 +2701,18 @@ describe('ReactDOMServerIntegration', () => {
,
,
));
+
+ it('can explicitly ignore errors reconnecting added style values', () =>
+ expectMarkupMatch(
+ ,
+ ,
+ ));
+
+ it('can explicitly ignore reconnecting different style values', () =>
+ expectMarkupMatch(
+ ,
+ ,
+ ));
});
describe('text nodes', function() {
@@ -2681,6 +2733,18 @@ describe('ReactDOMServerIntegration', () => {
{'Text1'}{'Text2'}
,
{'Text1'}{'Text3'}
,
));
+
+ it('can explicitly ignore reconnecting different text', () =>
+ expectMarkupMatch(
+ Text
,
+ Other Text
,
+ ));
+
+ it('can explicitly ignore reconnecting different text in two code blocks', () =>
+ expectMarkupMatch(
+ {'Text1'}{'Text2'}
,
+ {'Text1'}{'Text3'}
,
+ ));
});
describe('element trees and children', function() {
@@ -2731,6 +2795,30 @@ describe('ReactDOMServerIntegration', () => {
it('can distinguish an empty component from an empty text component', () =>
expectMarkupMatch(
, {''}
));
+
+ it('can explicitly ignore reconnecting more children', () =>
+ expectMarkupMatch(
+ ,
+ ,
+ ));
+
+ it('can explicitly ignore reconnecting fewer children', () =>
+ expectMarkupMatch(
+ ,
+ ,
+ ));
+
+ it('can explicitly ignore reconnecting reordered children', () =>
+ expectMarkupMatch(
+ ,
+ ,
+ ));
+
+ it('can not deeply ignore reconnecting reordered children', () =>
+ expectMarkupMismatch(
+ ,
+ ,
+ ));
});
// Markup Mismatches: misc
@@ -2739,5 +2827,14 @@ describe('ReactDOMServerIntegration', () => {
"}} />,
"}} />,
));
+
+ it('can explicitly ignore reconnecting a div with different dangerouslySetInnerHTML', () =>
+ expectMarkupMatch(
+ "}} />,
+ "}}
+ suppressHydrationWarning={true}
+ />,
+ ));
});
});
diff --git a/src/renderers/dom/shared/hooks/possibleStandardNames.js b/src/renderers/dom/shared/hooks/possibleStandardNames.js
index 236de2e0e9aef..0349d4ce4e0b7 100644
--- a/src/renderers/dom/shared/hooks/possibleStandardNames.js
+++ b/src/renderers/dom/shared/hooks/possibleStandardNames.js
@@ -401,6 +401,7 @@ var possibleStandardNames = {
strokeopacity: 'strokeOpacity',
'stroke-opacity': 'strokeOpacity',
suppresscontenteditablewarning: 'suppressContentEditableWarning',
+ suppresshydrationwarning: 'suppressHydrationWarning',
surfacescale: 'surfaceScale',
systemlanguage: 'systemLanguage',
tablevalues: 'tableValues',
diff --git a/src/renderers/shared/fiber/ReactFiberHydrationContext.js b/src/renderers/shared/fiber/ReactFiberHydrationContext.js
index dee6f885200b1..879bee732eb53 100644
--- a/src/renderers/shared/fiber/ReactFiberHydrationContext.js
+++ b/src/renderers/shared/fiber/ReactFiberHydrationContext.js
@@ -44,7 +44,13 @@ module.exports = function(
getFirstHydratableChild,
hydrateInstance,
hydrateTextInstance,
+ didNotMatchHydratedContainerTextInstance,
+ didNotMatchHydratedTextInstance,
+ didNotHydrateContainerInstance,
didNotHydrateInstance,
+ // TODO: These are currently unused, see below.
+ // didNotFindHydratableContainerInstance,
+ // didNotFindHydratableContainerTextInstance,
didNotFindHydratableInstance,
didNotFindHydratableTextInstance,
} = config;
@@ -57,7 +63,12 @@ module.exports = function(
getFirstHydratableChild &&
hydrateInstance &&
hydrateTextInstance &&
+ didNotMatchHydratedContainerTextInstance &&
+ didNotMatchHydratedTextInstance &&
+ didNotHydrateContainerInstance &&
didNotHydrateInstance &&
+ // didNotFindHydratableContainerInstance &&
+ // didNotFindHydratableContainerTextInstance &&
didNotFindHydratableInstance &&
didNotFindHydratableTextInstance)
) {
@@ -105,10 +116,18 @@ module.exports = function(
if (__DEV__) {
switch (returnFiber.tag) {
case HostRoot:
- didNotHydrateInstance(returnFiber.stateNode.containerInfo, instance);
+ didNotHydrateContainerInstance(
+ returnFiber.stateNode.containerInfo,
+ instance,
+ );
break;
case HostComponent:
- didNotHydrateInstance(returnFiber.stateNode, instance);
+ didNotHydrateInstance(
+ returnFiber.type,
+ returnFiber.memoizedProps,
+ returnFiber.stateNode,
+ instance,
+ );
break;
}
}
@@ -134,32 +153,57 @@ module.exports = function(
function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
fiber.effectTag |= Placement;
if (__DEV__) {
- var parentInstance;
switch (returnFiber.tag) {
// TODO: Currently we don't warn for insertions into the root because
// we always insert into the root in the non-hydrating case. We just
// delete the existing content. Reenable this once we have a better
// strategy for determining if we're hydrating or not.
- // case HostRoot:
- // parentInstance = returnFiber.stateNode.containerInfo;
+ // case HostRoot: {
+ // const parentContainer = returnFiber.stateNode.containerInfo;
+ // switch (fiber.tag) {
+ // case HostComponent:
+ // const type = fiber.type;
+ // const props = fiber.pendingProps;
+ // didNotFindHydratableContainerInstance(parentContainer, type, props);
+ // break;
+ // case HostText:
+ // const text = fiber.pendingProps;
+ // didNotFindHydratableContainerTextInstance(parentContainer, text);
+ // break;
+ // }
// break;
- case HostComponent:
- parentInstance = returnFiber.stateNode;
+ // }
+ case HostComponent: {
+ const parentType = returnFiber.type;
+ const parentProps = returnFiber.memoizedProps;
+ const parentInstance = returnFiber.stateNode;
+ switch (fiber.tag) {
+ case HostComponent:
+ const type = fiber.type;
+ const props = fiber.pendingProps;
+ didNotFindHydratableInstance(
+ parentType,
+ parentProps,
+ parentInstance,
+ type,
+ props,
+ );
+ break;
+ case HostText:
+ const text = fiber.pendingProps;
+ didNotFindHydratableTextInstance(
+ parentType,
+ parentProps,
+ parentInstance,
+ text,
+ );
+ break;
+ }
break;
+ }
default:
return;
}
- switch (fiber.tag) {
- case HostComponent:
- const type = fiber.type;
- const props = fiber.pendingProps;
- didNotFindHydratableInstance(parentInstance, type, props);
- break;
- case HostText:
- const text = fiber.pendingProps;
- didNotFindHydratableTextInstance(parentInstance, text);
- break;
- }
}
}
@@ -243,11 +287,41 @@ module.exports = function(
function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
const textInstance: TI = fiber.stateNode;
- const shouldUpdate = hydrateTextInstance(
- textInstance,
- fiber.memoizedProps,
- fiber,
- );
+ const textContent: string = fiber.memoizedProps;
+ const shouldUpdate = hydrateTextInstance(textInstance, textContent, fiber);
+ if (__DEV__) {
+ if (shouldUpdate) {
+ // We assume that prepareToHydrateHostTextInstance is called in a context where the
+ // hydration parent is the parent host component of this host text.
+ const returnFiber = hydrationParentFiber;
+ if (returnFiber !== null) {
+ switch (returnFiber.tag) {
+ case HostRoot: {
+ const parentContainer = returnFiber.stateNode.containerInfo;
+ didNotMatchHydratedContainerTextInstance(
+ parentContainer,
+ textInstance,
+ textContent,
+ );
+ break;
+ }
+ case HostComponent: {
+ const parentType = returnFiber.type;
+ const parentProps = returnFiber.memoizedProps;
+ const parentInstance = returnFiber.stateNode;
+ didNotMatchHydratedTextInstance(
+ parentType,
+ parentProps,
+ parentInstance,
+ textInstance,
+ textContent,
+ );
+ break;
+ }
+ }
+ }
+ }
+ }
return shouldUpdate;
}
diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js
index eafa87add0c78..0d7a76021a982 100644
--- a/src/renderers/shared/fiber/ReactFiberReconciler.js
+++ b/src/renderers/shared/fiber/ReactFiberReconciler.js
@@ -139,14 +139,48 @@ export type HostConfig = {
text: string,
internalInstanceHandle: OpaqueHandle,
) => boolean,
- didNotHydrateInstance?: (parentInstance: I | C, instance: I | TI) => void,
+ didNotMatchHydratedContainerTextInstance?: (
+ parentContainer: C,
+ textInstance: TI,
+ text: string,
+ ) => void,
+ didNotMatchHydratedTextInstance?: (
+ parentType: T,
+ parentProps: P,
+ parentInstance: I,
+ textInstance: TI,
+ text: string,
+ ) => void,
+ didNotHydrateContainerInstance?: (
+ parentContainer: C,
+ instance: I | TI,
+ ) => void,
+ didNotHydrateInstance?: (
+ parentType: T,
+ parentProps: P,
+ parentInstance: I,
+ instance: I | TI,
+ ) => void,
+ didNotFindHydratableContainerInstance?: (
+ parentContainer: C,
+ type: T,
+ props: P,
+ ) => void,
+ didNotFindHydratableContainerTextInstance?: (
+ parentContainer: C,
+ text: string,
+ ) => void,
didNotFindHydratableInstance?: (
- parentInstance: I | C,
+ parentType: T,
+ parentProps: P,
+ parentInstance: I,
type: T,
props: P,
) => void,
didNotFindHydratableTextInstance?: (
- parentInstance: I | C,
+ parentType: T,
+ parentProps: P,
+ parentInstance: I,
text: string,
) => void,
diff --git a/src/renderers/shared/server/ReactPartialRenderer.js b/src/renderers/shared/server/ReactPartialRenderer.js
index facf1c2bc88b9..9f7e41ffd1f45 100644
--- a/src/renderers/shared/server/ReactPartialRenderer.js
+++ b/src/renderers/shared/server/ReactPartialRenderer.js
@@ -251,6 +251,7 @@ var RESERVED_PROPS = {
children: null,
dangerouslySetInnerHTML: null,
suppressContentEditableWarning: null,
+ suppressHydrationWarning: null,
};
function createOpenTagMarkup(