diff --git a/packages/rich-text/CHANGELOG.md b/packages/rich-text/CHANGELOG.md index 0e9ef7674dc481..aadcae6d607998 100644 --- a/packages/rich-text/CHANGELOG.md +++ b/packages/rich-text/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.0.3 (Unreleased) + +### Internal + +- Internal performance optimizations to avoid excessive expensive creation of DOM documents. + ## 3.0.2 (2018-11-21) ## 3.0.1 (2018-11-20) diff --git a/packages/rich-text/src/create-element.js b/packages/rich-text/src/create-element.js index 0e2f6f3572fdf8..eebbe450075e80 100644 --- a/packages/rich-text/src/create-element.js +++ b/packages/rich-text/src/create-element.js @@ -1,13 +1,25 @@ /** * Parse the given HTML into a body element. * + * Note: The current implementation will return a shared reference, reset on + * each call to `createElement`. Therefore, you should not hold a reference to + * the value to operate upon asynchronously, as it may have unexpected results. + * * @param {HTMLDocument} document The HTML document to use to parse. * @param {string} html The HTML to parse. * * @return {HTMLBodyElement} Body element with parsed HTML. */ export function createElement( { implementation }, html ) { - const { body } = implementation.createHTMLDocument( '' ); - body.innerHTML = html; - return body; + // Because `createHTMLDocument` is an expensive operation, and with this + // function being internal to `rich-text` (full control in avoiding a risk + // of asynchronous operations on the shared reference), a single document + // is reused and reset for each call to the function. + if ( ! createElement.body ) { + createElement.body = implementation.createHTMLDocument( '' ).body; + } + + createElement.body.innerHTML = html; + + return createElement.body; } diff --git a/packages/rich-text/src/test/to-dom.js b/packages/rich-text/src/test/to-dom.js index 92cc47aa28846e..16fed3f66d0a26 100644 --- a/packages/rich-text/src/test/to-dom.js +++ b/packages/rich-text/src/test/to-dom.js @@ -66,8 +66,8 @@ describe( 'applyValue', () => { cases.forEach( ( { current, future, description, movedCount } ) => { it( description, () => { - const body = createElement( document, current ); - const futureBody = createElement( document, future ); + const body = createElement( document, current ).cloneNode( true ); + const futureBody = createElement( document, future ).cloneNode( true ); const childNodes = Array.from( futureBody.childNodes ); applyValue( futureBody, body ); const count = childNodes.reduce( ( acc, { parentNode } ) => { diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index b582c77083d9a0..1c34d8680dd2b3 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -3,6 +3,7 @@ */ import { toTree } from './to-tree'; +import { createElement } from './create-element'; /** * Browser dependencies @@ -58,10 +59,17 @@ function getNodeByPath( node, path ) { }; } -function createEmpty() { - const { body } = document.implementation.createHTMLDocument( '' ); - return body; -} +/** + * Returns a new instance of a DOM tree upon which RichText operations can be + * applied. + * + * Note: The current implementation will return a shared reference, reset on + * each call to `createEmpty`. Therefore, you should not hold a reference to + * the value to operate upon asynchronously, as it may have unexpected results. + * + * @return {WPRichTextTree} RichText tree. + */ +const createEmpty = () => createElement( document, '' ); function append( element, child ) { if ( typeof child === 'string' ) {