子组件 父组件 dom里
By Dan Bellinski, 84.51° Lead Software Engineer
84.51°首席软件工程师Dan Bellinski
An application team using a design system should feel secure that the system’s components have been tested for performance, browser support, and accessibility. So when one of our applications was reporting slower than usual page loads on a template that was using our dropdown menu we knew something was amiss. The dropdown menu was used in a table row to group actions. A few dozen instances of the dropdown menu caused the page load to slow to a crawl — rendering most of the UI unresponsive. Perplexed, we investigated our StencilJS web components, spent hours debugging, and eventually found the root cause: not using Shadow DOM on our StencilJS components was a big mistake. Since then we’ve drastically improved the performance of our component and the page. In this article we’re going to share why Shadow DOM is important for StencilJS web component performance and other benefits we’ve learned along the way.
使用设计系统的应用程序团队应放心,已对系统的组件进行了性能,浏览器支持和可访问性测试。 因此,当我们的一个应用程序报告使用下拉菜单的模板上的页面加载速度比平时慢时,我们知道有些不对劲。 表格行中使用了下拉菜单来对操作进行分组。 下拉菜单的数十个实例导致页面加载速度缓慢,从而使大多数UI均无响应。 感到困惑,我们调查了StencilJS Web组件,花费了数小时进行调试,最终找到了根本原因:在我们的StencilJS组件上不使用Shadow DOM是一个大错误。 从那时起,我们就极大地提高了组件和页面的性能。 在本文中,我们将分享为什么Shadow DOM对于StencilJS Web组件的性能以及我们在此过程中学到的其他好处很重要。
性能改进1:渲染插槽 (Performance Improvement 1: Rendering Slots)
To understand the issue we need to first understand how StencilJS renders components, specifically components with slots. StencilJS lets you choose to render your components in 2 ways: with Shadow DOM disabled or enabled and by default is it disabled. Some of the choice depends on which browsers you need to support and if StencilJS’s Shadow DOM polyfills are “good enough” for your use case — e.g. IE11 doesn’t support CSS Custom Properties (variables) but there is a polyfill that provides some functionality.
要了解这个问题,我们首先需要了解StencilJS如何渲染组件,特别是具有插槽的组件。 StencilJS允许您选择通过两种方式呈现组件: 禁用或启用 Shadow DOM 默认情况下它是禁用的 。 一些选择取决于您需要支持的浏览器,以及StencilJS的Shadow DOM polyfill是否足以满足您的使用情况-例如IE11不支持CSS Custom Properties(变量),但是有一个polyfill提供了某些功能。
If browser support is a question for Shadow DOM, why would you want to use it? Shadow DOM offers both DOM and style encapsulation, preventing anything outside of your component from interfering with it. This is really helpful to preserve the intended functionality of your components if you don’t know where or how they’ll be used.
如果浏览器支持是Shadow DOM的问题,那么为什么要使用它? Shadow DOM同时提供DOM和样式封装,可防止组件外部的任何内容干扰它。 如果您不知道在哪里或如何使用它们,这对于保留组件的预期功能确实很有帮助。
When we first implemented StencilJS we thought the choice to use Shadow DOM was just going to impact how styles were rendered on our components and made our decision based on that. However, we’ve since learned that StencilJS will actually render your component mark-up in a completely different way when using slots with Shadow DOM disabled versus slots with Shadow DOM enabled — and that difference can have large performance implications.
当我们首次实现StencilJS时,我们认为使用Shadow DOM的选择只会影响样式在组件上的呈现方式,并以此为基础做出决定。 但是,我们已经知道,当使用禁用了Shadow DOM的插槽与启用了Shadow DOM的插槽时,StencilJS实际上将以完全不同的方式呈现组件标记,并且这种差异可能会影响性能。
The <slot> element is actually part of the Shadow DOM specification, but StencilJS will still allow you to use <slot> without Shadow DOM. Following are 2 examples of how StencilJS will render content passed into a slot with Shadow DOM disabled versus enabled.
<slot>元素实际上是Shadow DOM规范的一部分,但是StencilJS仍然允许您在没有Shadow DOM的情况下使用<slot>。 以下是两个示例,这些示例说明StencilJS如何在禁用和启用 Shadow DOM的情况下呈现传递到插槽的内容。
Take a simple “mds-card” component implementation:
采用简单的“ mds卡”组件实现:
And the following user provided usage of the component:
并且以下用户提供了该组件的用法:
这是StencilJS在禁用 Shadow DOM的情况下呈现<slot>内容的方式: (Here is how StencilJS renders the <slot> contents with Shadow DOM disabled:)
1) First, the user provided mark-up is added to the DOM:
1)首先,将用户提供的标记添加到DOM:
2) Next, the component is expanded in the DOM and the slotted content is moved in the DOM to the proper place for the final result:
2)接着,将组分在DOM扩大,开槽内容在DOM来对最终结果的适当位置移动 :
3) The browser can now render the full picture of the component using the above DOM representation.
3)现在,浏览器可以使用上述DOM表示形式呈现组件的完整图片。
Note here that the slotted content was actually moved in the DOM and replaced the <slot> element — the final DOM that the browser renders no longer has a <slot> element in it.
请注意,插入的内容实际上是在DOM中移动的,并替换了<slot>元素-浏览器呈现的最终DOM中不再包含<slot>元素。
另外,这是启用了Shadow DOM的StencilJS如何呈现<slot>内容的方法: (Alternatively, here is how StencilJS renders the <slot> contents with Shadow DOM enabled:)
1) First, the user provided mark-up is added to the DOM:
1)首先,将用户提供的标记添加到DOM:
2) Next, the component is expanded in the DOM. Notice that the <span> is not moved and stays where it is. The <span> is in what is called the “light DOM” — it isn’t rendered in it’s current state.
2)接下来,在DOM中扩展组件。 请注意,<span>不会移动,而是保持在原位置。 <span>处于所谓的“轻型DOM”中-它不是以当前状态呈现。
3) Lastly, the browser has to do some work. During the browser’s render step, the browser actually flattens the DOM to get it into the proper structure — but this change is only made for rendering! The DOM itself is not changed and remains as seen above.
3)最后,浏览器必须完成一些工作。 在浏览器的渲染步骤中,浏览器实际上是将DOM展平以使其具有适当的结构-但是此更改仅用于渲染! DOM本身未更改,并且保持不变。
The key difference between the two ways of rendering is how the DOM itself is rendered. Changing the DOM is a very expensive operation — which is why frameworks like React and Angular have implemented their own techniques to avoid updating the DOM as much as possible (Virtual DOM and Incremental DOM respectively). With StencilJS rendering a <slot> with Shadow DOM disabled, it is actually moving slotted content around in the DOM — and this is costly.
两种呈现方式之间的主要区别在于DOM本身的呈现方式。 更改DOM是一项非常昂贵的操作-这就是为什么像React和Angular这样的框架已实现了自己的技术,从而避免了尽可能多地更新DOM的原因(分别是虚拟DOM和增量DOM)。 使用StencilJS在禁用 Shadow DOM的情况下渲染<slot>时,实际上是在DOM中移动带槽内容- 这是昂贵的 。
绩效评估 (Evaluating Performance)
Our dropdown menu leverages a <slot> to allow the user to pass their own menu items. We took this poorly performing dropdown menu component and ran some performance tests against it. We rendered 200 instances of the dropdown menu with 3 elements slotted into each dropdown menu. We performed the render of the 200 dropdown menus on the click of a button to have control over the results. This is what we found:
我们的下拉菜单利用<slot>允许用户传递自己的菜单项。 我们采用了性能不佳的下拉菜单组件,并对其进行了一些性能测试。 我们渲染了200个下拉菜单实例,每个下拉菜单中包含3个元素。 单击按钮即可执行200个下拉菜单的渲染,以控制结果。 这是我们发现的:
With Shadow DOM disabled, rendering all 200 dropdown menus took:
在禁用 Shadow DOM的情况下,渲染所有200个下拉菜单需要:
5000ms
5000毫秒
With Shadow DOM enabled, rendering all 200 dropdown menus took:
启用 Shadow DOM后,呈现所有200个下拉菜单的过程如下:
1600ms
1600毫秒
The component renders 3x faster when using Shadow DOM! This discovery was a clear sign we needed to switch our StencilJS components to use Shadow DOM.
使用Shadow DOM时,该组件的渲染速度提高了3倍! 这个发现是我们需要切换StencilJS组件以使用Shadow DOM的明确标志。
We are now wrapping up the conversion of all 30 of our design system components over to use Shadow DOM. Through this process, we’ve learned a few more reasons that this was the right switch to make: “conditional slots” and “user driven slot changes”.
现在,我们结束了对所有30个设计系统组件的转换,以使用Shadow DOM。 通过此过程,我们了解了其他一些需要进行此切换的原因:“条件插槽”和“用户驱动的插槽更改”。
性能提升2:有条件的广告位 (Performance Improvement 2: Conditional Slotting)
With Shadow DOM disabled, if a user provides slotted content to a component that doesn’t have a slot to put it in, the slotted content still renders on the page (at the top of the component). This makes it difficult or near impossible to conditionally render the user’s provided content which means we always have to render it and only hide it with CSS.
在禁用 Shadow DOM的情况下,如果用户向没有插槽的组件提供插槽内容,则插槽内容仍呈现在页面上(组件顶部)。 这使得很难或几乎不可能有条件地呈现用户提供的内容,这意味着我们始终必须呈现它,而仅使用CSS隐藏它。
For example, here is our Tag component, which doesn’t have a <slot>, with content slotted into it and Shadow DOM disabled:
例如,这是我们的Tag组件,它没有<slot>,其中插入了内容,并且禁用了 Shadow DOM:
![Image for post](https://miro.medium.com/max/9999/1*JJknEp-ZNyf1MWYxp2TfKg.png)
With Shadow DOM enabled, if a user provides slotted content to a component that doesn’t have a <slot> to put it in, the slotted content is not rendered. This is because the browser will go to flatten the DOM at the time of render and see no place to put the content so it will stay in the “light DOM” which isn’t rendered. This opens up the door for conditionally rendering the user’s provided content which gives us further opportunities to tune the performance of our components.
启用 Shadow DOM后,如果用户向没有<slot>放置的组件提供带槽内容,则不会呈现带槽内容。 这是因为浏览器将在渲染时将DOM展平,并且看不到放置内容的位置,因此它将停留在未渲染的“轻型DOM”中。 这为有条件地呈现用户提供的内容打开了一扇门,这为我们提供了进一步调整组件性能的机会。
For example, here is our Tag component (which doesn’t have a slot) with content slotted into it and Shadow DOM enabled:
例如,这是我们的Tag组件(没有插槽),其内容放入其中并启用了 Shadow DOM:
![Image for post](https://miro.medium.com/max/9999/1*_-nXmZVLKZiXQEU9lp4YQg.png)
We used this concept to remove our dropdown-menu’s “content” <slot> from the DOM when the dropdown menu wasn’t open. After making this change, we re-ran it through our performance test done above.
当下拉菜单未打开时,我们使用此概念从DOM中删除了下拉菜单的“内容” <slot>。 进行更改后,我们通过上面进行的性能测试重新运行了它。
With Shadow DOM enabled + using a conditional slot, rendering all 200 dropdown menus took:
启用 Shadow DOM +使用条件插槽,呈现所有200个下拉菜单需要:
1200ms
1200毫秒
We went from 5000ms to 1200ms to render 200 dropdown menus, now a 4x improvement! Things are starting to look better for this component.
我们从5000毫秒增加到1200毫秒,以渲染200个下拉菜单,现在提高了4倍! 对于此组件,情况开始看起来更好。
性能改进3:用户驱动的插槽更改 (Performance Improvement 3: User Driven Slot Changes)
When we had Shadow DOM disabled on our components, we had a tough discovery — our Angular users were slotting content into our components using some Angular constructs like *ngIf and *ngFor and pointing them at asynchronous variables (pulled from NgRx state). The asynchronous behavior caused some of the slotted content to not be provided to the component at the first render of our component, but just shortly following the first render. Unfortunately this slotted content did not appear on the page at all until the next time a render was forced on that component.
当我们在组件上禁用 Shadow DOM时,我们有一个艰难的发现-我们的Angular用户正在使用* ngIf和* ngFor等一些Angular构造将内容放入我们的组件中,并将它们指向异步变量(从NgRx状态中提取)。 异步行为导致一些插入内容的内容在我们组件的第一个渲染时没有提供给组件,而是在第一个渲染之后不久提供给组件。 不幸的是,直到下一次在该组件上强制执行渲染时,该带槽内容才完全没有出现在页面上。
What we learned is that StencilJS is not listening for slotted content changes, so we had to use MutationObserver to do our own listening. The short of the implementation is that the MutationObserver would look for node changes in the component and if they were inside of a <slot>, we’d force a re-render of the component. This was a bit ugly and caused some components to render twice on the initial load when they really didn’t need to, resulting in extra time to load.
我们了解到StencilJS并未监听时隙内容的更改,因此我们不得不使用MutationObserver进行自己的监听。 该实现的不足之处在于MutationObserver将在组件中查找节点更改,如果它们在<slot>内部,我们将强制重新呈现该组件。 这有点丑陋,导致某些组件在真正不需要时在初始加载时渲染两次,从而导致额外的加载时间。
With Shadow DOM enabled, we actually get this listening for free. The top level slotted content is observed and when it changes, the component re-renders without our intervention. We can even utilize the onSlotchange callback to perform any required updates to our component when the top level slotted content changes, e.g.:
启用 Shadow DOM后,我们实际上是免费收听的。 会观察到顶级插槽中的内容,并且当它发生更改时,无需我们的干预即可重新渲染该组件。 当顶级插槽内容发生更改时,我们甚至可以利用onSlotchange回调对组件执行任何必需的更新,例如:
<slot
name=”popover-trigger”
onSlotchange={() => this.triggerSlotChanged()}>
</slot>
绿草丛 (Greener Grasses)
We covered 3 improvements to our component’s performance that were realized by enabling Shadow DOM on our StencilJS component:
我们介绍了通过在StencilJS组件上启用 Shadow DOM实现的3个组件性能改进:
- Performance of Rendering Slots 渲染槽的性能
- Conditional Slotting 有条件的广告位
- User Driven Slot Changes 用户驱动的插槽更改
We’re excited about the benefits we’ve gained and know it will lead to more performant user experiences in our applications leveraging our design system components. Aside from these benefits, there are plenty of articles on the web about the pros and cons of using Shadow DOM, mainly around style encapsulation. If you’re using StencilJS with Shadow DOM disabled on your components, we encourage you to take a look at what benefits you may gain from enabling it!
我们对所获得的好处感到兴奋,并知道它将利用我们的设计系统组件在我们的应用程序中带来更高性能的用户体验。 除了这些好处之外,网络上还有很多关于使用Shadow DOM的利弊的文章,主要涉及样式封装。 如果您在组件上禁用了Shadow DOM的情况下使用StencilJS,我们鼓励您看看启用它会带来什么好处!
翻译自: https://medium.com/8451/make-your-stenciljs-web-components-faster-by-using-shadow-dom-d010a9f0cdda
子组件 父组件 dom里