Skip to content

Conversation

@nolanlawson
Copy link
Contributor

@nolanlawson nolanlawson commented Aug 1, 2022

Details

Fixes #2890.

Rather than avoiding unsafe top-level elements like <td> and <th>, we can use a <template> for parsing these. This allows us to extend the static optimization to more elements.

To support IE11, we can fall back to a partial implementation of the <template> element borrowed from webcomponents/polyfills. So this PR is not dropping IE11 support.

Does this pull request introduce a breaking change?

  • ✅ No, it does not introduce a breaking change.

Does this pull request introduce an observable change?

  • ✅ No, it does not introduce an observable change.

@nolanlawson
Copy link
Contributor Author

The tests are passing in all browsers, and as a reminder, we have a Karma test for static <td>s. 🙂

describe('tables and static content', () => {
it('should work with a static <td>', () => {
const elm = createElement('x-table', { is: Table });
document.body.appendChild(elm);
expect(elm.shadowRoot.querySelectorAll('td').length).toEqual(0);
elm.addRow();
return Promise.resolve().then(() => {
expect(elm.shadowRoot.querySelectorAll('td').length).toEqual(1);
expect(elm.shadowRoot.querySelector('td').textContent).toEqual('');
});
});
});

const template = document.createElement('template');
template.innerHTML = html;
return template.content.firstChild;
};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered caching and re-using the template here, but I decided against it because:

  1. This is just a one-time setup cost anyway – it wouldn't improve any of our benchmarks
  2. Holding onto the memory from the last HTML in the cached template strikes me as a little weird
  3. I'm not even sure if it's a big perf improvement in the first place

We might explore caching the template in a follow-up PR, though.

@nolanlawson
Copy link
Contributor Author

/nucleus test

@nolanlawson nolanlawson requested a review from jodarove August 1, 2022 17:52

// Via https://github.com/webcomponents/polyfills/blob/ee1db33/packages/template/template.js#L273-L280
const topLevelWrappingMap: { [key: string]: string[] } = {
option: ['select'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does tfoot, colgroup, tbody and caption also need to be on this map? (I don't think you can add head, body and html on an lwc template)

Copy link
Contributor Author

@nolanlawson nolanlawson Aug 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! Fixed and added a test to make sure it actually works in IE11. I did not include head/body/html.

let content: Node = doc.body;
if (!isUndefined(wrapperTags)) {
for (let i = 0; i < wrapperTags.length; i++) {
content = content.lastChild!;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why we use .lastChild here, and .firstChild at the end?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was copypasta from webcomponents/polyfills here, but I think you're right; we can just use firstChild which is less confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, switched to firstChild.

@nolanlawson nolanlawson requested a review from jodarove August 1, 2022 19:30
Copy link
Contributor

@jodarove jodarove left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥇 :shipit:


let content: Node = doc.body;
if (!isUndefined(wrapperTags)) {
for (let i = 0; i < wrapperTags.length; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (let i = 0; i < wrapperTags.length; i++) {
for (let i = 0, n = wrapperTags.length; i < n; i++) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last I checked, JS engines have optimized this; there's no need to cache the length.

There is some discussion on SO, although sadly all the JSPerf links are dead now. 😢

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, found a post from a V8 engineer; we do not need to cache length. 😁

@nolanlawson nolanlawson merged commit 9246638 into master Aug 2, 2022
@nolanlawson nolanlawson deleted the nolan/fragment-ie11 branch August 2, 2022 15:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Static optimization] Use template.innerHTML instead of createContextualFragment

3 participants