Parse template HTML only once, as HTML parsing is expensive

When creating a popup, clone the previously created DOM element
and populate the attributes and content.

Ideally this would be done with a template element, but since IE11
is still supported this is not possible.

Change-Id: I347615cf1f613d97d767d60627b13b6b3ff9c762
Bug: T269338
This commit is contained in:
Noam Rosenthal 2020-12-01 10:47:11 +02:00 committed by Jdlrobson
parent ff2ba9ebf5
commit 497eb631d1
9 changed files with 116 additions and 60 deletions

View file

@ -67,7 +67,7 @@
"bundlesize": [
{
"path": "resources/dist/index.js",
"maxSize": "13.61kB"
"maxSize": "13.8kB"
}
]
}

Binary file not shown.

Binary file not shown.

View file

@ -3,9 +3,20 @@
*/
import { renderPopup } from '../popup/popup';
import { escapeHTML } from '../templateUtil';
import { createNodeFromTemplate } from '../templateUtil';
const defaultExtractWidth = 215;
const templateHTML = `
<div>
<a class="mwe-popups-discreet"></a>
<a class="mwe-popups-extract"></a>
<footer>
<a class="mwe-popups-settings-icon">
<span class="mw-ui-icon mw-ui-icon-element mw-ui-icon-small mw-ui-icon-settings"></span>
</a>
</footer>
</div>
`;
/**
* @param {ext.popups.PagePreviewModel} model
@ -15,24 +26,19 @@ const defaultExtractWidth = 215;
export function renderPagePreview(
model, thumbnail
) {
const url = escapeHTML( model.url ),
languageCode = escapeHTML( model.languageCode ),
languageDirection = escapeHTML( model.languageDirection );
const $el = renderPopup( model.type, createNodeFromTemplate( templateHTML ) );
const $el = renderPopup( model.type,
`
${thumbnail ? `<a href='${url}' class='mwe-popups-discreet'></a>` : ''}
<a dir='${languageDirection}' lang='${languageCode}' class='mwe-popups-extract' href='${url}'></a>
<footer>
<a class='mwe-popups-settings-icon'>
<span class="mw-ui-icon mw-ui-icon-element mw-ui-icon-small mw-ui-icon-settings"></span>
</a>
</footer>
`
);
$el.find( '.mwe-popups-discreet, .mwe-popups-extract' )
.attr( 'href', model.url );
$el.find( '.mwe-popups-extract' )
.attr( 'dir', model.languageDirection )
.attr( 'lang', model.languageCode );
if ( thumbnail ) {
$el.find( '.mwe-popups-discreet' ).append( thumbnail.el );
} else {
$el.find( '.mwe-popups-discreet' ).remove();
}
if ( model.extract ) {

View file

@ -2,19 +2,26 @@
* @module popup
*/
import { escapeHTML } from '../templateUtil';
import { createNodeFromTemplate } from '../templateUtil';
const templateHTML = `
<div class="mwe-popups" aria-hidden></div>
`;
/**
* @param {ext.popups.previewTypes} type
* @param {string} html HTML string.
* @param {Element} element The contents of the popup.
* @return {JQuery}
*/
export function renderPopup( type, html ) {
type = escapeHTML( type );
return $( $.parseHTML( `
<div class='mwe-popups mwe-popups-type-${type}' aria-hidden>
<div class='mwe-popups-container'>${html}</div>
</div>
`.trim() ) );
export function renderPopup( type, container ) {
const element = createNodeFromTemplate( templateHTML );
// The following classes are used here:
// * mwe-popups-type-reference
// * mwe-popups-type-unknown
// * mwe-popups-type-generic
// * mwe-popups-type-disambiguation
element.className = `mwe-popups mwe-popups-type-${type}`;
container.className = 'mwe-popups-container';
element.appendChild( container );
return $( element );
}

View file

@ -3,7 +3,20 @@
*/
import { renderPopup } from '../popup/popup';
import { escapeHTML } from '../templateUtil';
import { createNodeFromTemplate, escapeHTML } from '../templateUtil';
const templateHTML = `
<div class="mwe-popups-container">
<div class="mw-ui-icon mw-ui-icon-element"></div>
<strong class="mwe-popups-title"></strong>
<a class="mwe-popups-extract">
<span class="mwe-popups-message"></span>
</a>
<footer>
<a class="mwe-popups-read-link"></a>
</footer>
</div>
`;
/**
* @param {ext.popups.PagePreviewModel} model
@ -15,22 +28,24 @@ import { escapeHTML } from '../templateUtil';
export function renderPreview(
model, showTitle, extractMsg, linkMsg
) {
const title = escapeHTML( model.title ),
url = escapeHTML( model.url ),
type = escapeHTML( model.type );
extractMsg = escapeHTML( extractMsg );
linkMsg = escapeHTML( linkMsg );
const $popup = renderPopup( model.type, createNodeFromTemplate( templateHTML ) );
return renderPopup( model.type,
`
<div class='mw-ui-icon mw-ui-icon-element mw-ui-icon-preview-${type}'></div>
${showTitle ? `<strong class='mwe-popups-title'>${title}</strong>` : ''}
<a href='${url}' class='mwe-popups-extract'>
<span class='mwe-popups-message'>${extractMsg}</span>
</a>
<footer>
<a href='${url}' class='mwe-popups-read-link'>${linkMsg}</a>
</footer>
`
);
// The following classes are used here:
// * mw-icon-preview-reference
// * mw-icon-preview-unknown
// * mw-icon-preview-generic
// * mw-icon-preview-disambiguation
$popup.find( '.mw-ui-icon ' ).addClass( `mw-ui-icon-preview-${model.type}` );
$popup.find( '.mwe-popups-extract' ).attr( 'href', model.url );
$popup.find( '.mwe-popups-message' ).html( escapeHTML( extractMsg ) );
$popup.find( '.mwe-popups-read-link' )
.html( escapeHTML( linkMsg ) )
.attr( 'href', model.url );
if ( showTitle ) {
$popup.find( '.mwe-popups-title' ).html( escapeHTML( model.title ) );
} else {
$popup.find( '.mwe-popups-title' ).remove();
}
return $popup;
}

View file

@ -4,6 +4,21 @@
import { renderPopup } from '../popup/popup';
import { escapeHTML } from '../templateUtil';
import { createNodeFromTemplate } from '../templateUtil';
const templateHTML = `
<div class="mwe-popups-container">
<div class="mwe-popups-extract">
<div class="mwe-popups-scroll">
<strong class="mwe-popups-title">
<span class="mw-ui-icon mw-ui-icon-element"></span>
<span class="mwe-popups-title-placeholder"></span>
</strong>
<div class="mw-parser-output"></div>
</div>
<div class="mwe-popups-fade" />
</div>
</div>`;
// Known citation type strings currently supported with icons and messages.
const KNOWN_TYPES = [ 'book', 'journal', 'news', 'web' ];
@ -37,21 +52,18 @@ export function renderReferencePreview(
// * popups-refpreview-reference
// * popups-refpreview-web
title = escapeHTML( mw.msg( titleMsg ) );
const $el = renderPopup( model.type,
`
<div class='mwe-popups-extract'>
<div class='mwe-popups-scroll'>
<strong class='mwe-popups-title'>
<span class='mw-ui-icon mw-ui-icon-element mw-ui-icon-reference-${type}'></span>
${title}
</strong>
<div class='mw-parser-output'>${model.extract}</div>
</div>
<div class='mwe-popups-fade' />
</div>
`
);
const $el = renderPopup( model.type, createNodeFromTemplate( templateHTML ) );
$el.find( '.mwe-popups-title-placeholder' )
.replaceWith( title );
// The following classes are used here:
// * mw-icon-reference-reference
// * mw-icon-reference-unknown
// * mw-icon-reference-generic
// * mw-icon-reference-disambiguation
$el.find( '.mw-ui-icon' )
.addClass( `mw-ui-icon-reference-${type}` );
$el.find( '.mw-parser-output' )
.html( model.extract );
// Make sure to not destroy existing targets, if any
$el.find( '.mwe-popups-extract a[href][class~="external"]:not([target])' ).each( ( i, a ) => {

View file

@ -9,3 +9,19 @@
export function escapeHTML( str ) {
return mw.html.escape( str );
}
const templates = {};
/**
* @param {string} html markup of the template
* @return {Element} a cloned root element of the template
*/
export function createNodeFromTemplate( html ) {
if ( !templates[ html ] ) {
// TODO: use <template> element when IE11 dies
const div = document.createElement( 'div' );
div.innerHTML = html;
templates[ html ] = div.firstElementChild;
}
return templates[ html ].cloneNode( true );
}

View file

@ -112,8 +112,8 @@ module.exports = ( env, argv ) => ( {
// Minified uncompressed size limits for chunks / assets and entrypoints. Keep these numbers
// up-to-date and rounded to the nearest 10th of a kibibyte so that code sizing costs are
// well understood. Related to bundlesize minified, gzipped compressed file size tests.
maxAssetSize: 41.5 * 1024,
maxEntrypointSize: 41.5 * 1024,
maxAssetSize: 41.8 * 1024,
maxEntrypointSize: 41.8 * 1024,
// The default filter excludes map files but we rename ours.
assetFilter: ( filename ) => !filename.endsWith( srcMapExt )