Repository: preactjs/preact-render-to-string
Pull request #175 (2020-11-14 13:34:48)

Fix options.diffed and options._commit never being called
We never called `options.diffed` and `options._commit` during SSR. Looks like we forgot to add those.

Resolves a part of the issue mentioned at https://github.com/preactjs/preact/issues/2574


Patch:
diff --git a/package-lock.json b/package-lock.json
index 191e299..41a794f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6024,9 +6024,9 @@
       }
     },
     "preact": {
-      "version": "10.2.1",
-      "resolved": "https://registry.npmjs.org/preact/-/preact-10.2.1.tgz",
-      "integrity": "sha512-BUNvmQcVtNElku7mYHexIiM5JlGNSW2BY9O2t9xk1NqA43O8wbE0cah6PAlvT7PBHvyDRJ5TAj5Fewdi9DqLoQ==",
+      "version": "10.5.7",
+      "resolved": "https://registry.npmjs.org/preact/-/preact-10.5.7.tgz",
+      "integrity": "sha512-4oEpz75t/0UNcwmcsjk+BIcDdk68oao+7kxcpc1hQPNs2Oo3ZL9xFz8UBf350mxk/VEdD41L5b4l2dE3Ug3RYg==",
       "dev": true
     },
     "prelude-ls": {
diff --git a/package.json b/package.json
index 576228e..cb9f73b 100644
--- a/package.json
+++ b/package.json
@@ -100,7 +100,7 @@
 		"microbundle": "^0.6.0",
 		"mocha": "^5.2.0",
 		"npm-merge-driver-install": "^1.1.1",
-		"preact": "^10.0.4",
+		"preact": "^10.5.7",
 		"prettier": "^2.1.2",
 		"sinon": "^1.17.5",
 		"sinon-chai": "^2.8.0",
diff --git a/src/index.js b/src/index.js
index 27abd16..e2ea66b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -39,8 +39,17 @@ renderToString.render = renderToString;
  */
 let shallowRender = (vnode, context) => renderToString(vnode, context, SHALLOW);
 
+const EMPTY_ARR = [];
+function renderToString(vnode, context, opts) {
+	const res = _renderToString(vnode, context, opts);
+	// options._commit, we don't schedule any effects in this library right now,
+	// so we can pass an empty queue to this hook.
+	if (options.__c) options.__c(vnode, EMPTY_ARR);
+	return res;
+}
+
 /** The default export is an alias of `render()`. */
-function renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
+function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
 	if (vnode == null || typeof vnode === 'boolean') {
 		return '';
 	}
@@ -77,7 +86,7 @@ function renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
 			for (let i = 0; i < children.length; i++) {
 				rendered +=
 					(i > 0 && pretty ? '\n' : '') +
-					renderToString(
+					_renderToString(
 						children[i],
 						context,
 						opts,
@@ -173,7 +182,8 @@ function renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
 				context = assign(assign({}, context), c.getChildContext());
 			}
 
-			return renderToString(
+			if (options.diffed) options.diffed(vnode);
+			return _renderToString(
 				rendered,
 				context,
 				opts,
@@ -313,7 +323,7 @@ function renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
 							: nodeName === 'foreignObject'
 							? false
 							: isSvgMode,
-					ret = renderToString(
+					ret = _renderToString(
 						child,
 						context,
 						opts,
@@ -351,13 +361,13 @@ function renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
 		}
 	}
 
-	if (pieces.length || html) {
+	if (pieces.length) {
 		s += pieces.join('');
 	} else if (opts && opts.xml) {
 		return s.substring(0, s.length - 1) + ' />';
 	}
 
-	if (isVoid && !children && !html) {
+	if (isVoid && !children) {
 		s = s.replace(/>$/, ' />');
 	} else {
 		if (pretty && ~s.indexOf('\n')) s += '\n';


Test patch:
diff --git a/test/render.js b/test/render.js
index e2e61fa..1a64ee9 100644
--- a/test/render.js
+++ b/test/render.js
@@ -228,15 +228,6 @@ describe('render', () => {
 			expect(rendered).to.equal(expected);
 		});
 
-		it('should not self-close void elements if it has dangerouslySetInnerHTML prop', () => {
-			let rendered = render(
-					<link dangerouslySetInnerHTML={{ __html: '<foo>' }} />
-				),
-				expected = `<link><foo></link>`;
-
-			expect(rendered).to.equal(expected);
-		});
-
 		it('should serialize object styles', () => {
 			let rendered = render(<div style={{ color: 'red', border: 'none' }} />),
 				expected = `<div style="color: red; border: none;"></div>`;
@@ -823,15 +814,6 @@ describe('render', () => {
 			expect(renderXml(<a>b</a>)).to.equal(`<a>b</a>`);
 		});
 
-		it('should not self-close if it has dangerouslySetInnerHTML prop', () => {
-			expect(
-				renderXml(<a dangerouslySetInnerHTML={{ __html: 'b' }} />)
-			).to.equal(`<a>b</a>`);
-			expect(
-				renderXml(<a dangerouslySetInnerHTML={{ __html: '<b />' }} />)
-			).to.equal(`<a><b /></a>`);
-		});
-
 		it('should render boolean attributes with named values', () => {
 			expect(renderXml(<div foo bar />)).to.equal(
 				`<div foo="foo" bar="bar" />`
@@ -1067,7 +1049,7 @@ describe('render', () => {
 		});
 	});
 
-	it('should invoke options._render and options._diff', () => {
+	it('should invoke option hooks', () => {
 		const calls = [];
 		// _diff
 		const oldDiff = options.__b;
@@ -1081,6 +1063,17 @@ describe('render', () => {
 			calls.push(['_render', args]);
 			if (oldRender) oldRender(...args);
 		};
+		const oldDiffed = options.diffed;
+		options.diffed = (...args) => {
+			calls.push(['diffed', args]);
+			if (oldDiffed) oldDiffed(...args);
+		};
+		// _commit
+		const oldCommit = options.__c;
+		options.__c = (...args) => {
+			calls.push(['_commit', args]);
+			if (oldCommit) oldCommit(...args);
+		};
 
 		function Component1({ children }) {
 			return children;
@@ -1098,11 +1091,14 @@ describe('render', () => {
 		expect(calls).to.deep.equal([
 			['_diff', [vnode1]],
 			['_render', [vnode1]],
+			['diffed', [vnode1]],
 			['_diff', [vnode2]],
-			['_render', [vnode2]]
+			['_render', [vnode2]],
+			['diffed', [vnode2]],
+			['_commit', [vnode1, []]]
 		]);
 
-		expect(calls.length).to.equal(4);
+		expect(calls.length).to.equal(7);
 
 		options.__b = oldDiff;
 		options.__r = oldRender;