Skip to content

Commit e9978fb

Browse files
committed
Add description and screenshots for Demo1 (<style>/<link> in shadow). Minor cleanups for demo2.
1 parent 0cf2844 commit e9978fb

19 files changed

+72
-318
lines changed

README.md

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# JSON/CSS Module notes
22
[JSON modules](https://github.com/whatwg/html/pull/4407) and [CSS modules](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/css-modules-v1-explainer.md) provide several ergonomic benefits for web component developers. They provide easy integration into the JavaScript module graph with automatic deduping of dependencies, and eliminate the need to manually manage `fetch()`es or pollute the DOM with extra `<style>` and `<link rel="stylesheet">` elements.
33

4-
Additionally, there are important quantitative performance advantages to JSON and CSS modules over the current equivalents. This document describes these advantages and illustrates them with code samples.
4+
Additionally, there are important behavioral differences and quantitative performance wins to JSON and CSS modules over the current equivalents. This document describes these advantages and illustrates them with code samples.
55

6-
## A performance-equivalent JSON module can't be built with JavaScript modules
6+
## An equivalent to JSON modules can't be built with JavaScript modules
77

88
A naive attempt to replicate the functionality of JSON modules with a JavaScript module wrapper might look something like this:
99

@@ -58,7 +58,7 @@ export default jsonObject;
5858
or alternatively:
5959

6060
```JavaScript
61-
let response = await fetch("./data2.json")
61+
let response = await fetch("./data.json")
6262
let responseText = await response.text();
6363
let jsonObject = JSON.parse(responseText);
6464
export default jsonObject;
@@ -71,30 +71,53 @@ From the perspective of the importer this is ergonomically more or less equivale
7171

7272
And the above depends on the standardization and broad adoption of top-level await. Until that happens, devs are stuck exporting a Promise that the importer needs to deal with.
7373

74-
## Demo 2
75-
### [CSS/JSON modules have a lower memory footprint than inlining the CSS/JSON as a JavaScript string](https://dandclark.github.io/json-css-module-notes/demo2/index.html)
74+
## CSS Module Performance/Memory examples:
75+
76+
### Demo 1: [Overhead from `<style>` or `<link>` elements in custom element shadow root](https://dandclark.github.io/json-css-module-notes/demo1/index.html)
77+
78+
A common way of applying a custom element's styles is to include a `<style>` element or (perhaps less commonly?) a `<link>` element in the custom element's shadow root. This suffers from the problem that for each instance of the shadow element that exists on the page, there is an additional copy of the `<style>`/`<link>` element that gets stamped out. It costs CPU cycles to instantiate these extra elements, and do the necessary work when they are connected to the document. There is also memory overhead for their allocations.
79+
80+
A custom element that pulls in its styles with CSS module, however, only processes the stylesheet one time, when the sheet is imported, and there is no cost for an additional element in each custom element instance's shadow root. For a page that has many instances of the custom element, this can result in percievable performance and memory differences.
81+
82+
[Demo 1](https://dandclark.github.io/json-css-module-notes/demo1/index.html) illustrates this difference with a custom element written 3 different ways: with a `<style>` element in each shadow root, with a `<link>` element in each shadow root, and with a single CSS module applied to each shadow root's `adoptedStyleSheets`. To make the performance differences easily distinguishable, each page includes 15,000 instances of the respective custom element version.
83+
84+
I generally observe the CSS module element version loading about 1 second faster than the `<style>` element version, though the difference will vary per machine.
85+
86+
![`<style>` in shadow root load time](demo1/styleInShadowRootHighlight.PNG)
87+
88+
![CSS module load time](demo1/cssModuleHighlight.PNG)
89+
90+
The `<link>` element version loads much more slowly:
91+
92+
![`<link>` in shadow root load time](demo1/linkInShadowRootHighlight.PNG)
93+
94+
It's not clear why `<link>` is so much slower; caching should prevent it from making a new network request for each element instance. There might be a Chromium bug here, but in any case we expect that the performance best case should be similar to the `<style>` version.
95+
96+
There are also memory savings from omitting the extra elements. After reaching baseline (putting tabs in background and waiting for final GC at ~60 seconds) I see these numbers:
97+
98+
![steady-state memory usage](demo1/steadyStateHighlight.PNG)
99+
100+
### Demo 2: [CSS/JSON modules have a lower memory footprint than inlining the CSS/JSON as a JavaScript string](https://dandclark.github.io/json-css-module-notes/demo2/index.html)
76101
[https://dandclark.github.io/json-css-module-notes/demo2/index.html](https://dandclark.github.io/json-css-module-notes/demo2/index.html)
77102

78103
(There is no general CSS modules browser support as of this writing; that part of the demo was created and tested a custom Chromium build).
79104

80-
An alternative non-module approach for packaging CSS/JSON in a custom element is to inline the content as a JavaScript string rather than `fetch()`ing it dynamically. This string can be fed into a [Constructed Stylesheet](https://developers.google.com/web/updates/2019/02/constructable-stylesheets) or a `<style>` element.
81-
This eliminates any concerns about a a delay in the `fetch()` as outlined above. However, in addition to the clunky developer ergonimics
82-
of a bunch of inlined JavaScript string content in one's custom element JS logic, this approach has a quantifiable memory cost. This is due to the fact that the original JS string lives on alongside the CSSStyleSheet or JSON object that it is eventually parsed into. Whereas with CSS/JSON modules, nothing persists but the CSSStylesheet or JSON object.
105+
An alternative non-module approach for packaging CSS/JSON in a custom element is to inline the content as a JavaScript string rather than `fetch()`ing it dynamically, and feeding it into a [Constructed Stylesheet](https://developers.google.com/web/updates/2019/02/constructable-stylesheets).
106+
This eliminates the issues in Demo 1 where extra work is repeated for each custom element instance. However, in addition to the clunky developer ergonimics
107+
of a bunch of inlined JavaScript string content in one's custom element JS logic, this approach can still have an extra memory cost. This is due to the fact that the original JS string lives on alongside the CSSStyleSheet or JSON object that it is eventually parsed into. Whereas with CSS/JSON modules, nothing persists but the CSSStylesheet or JSON object.
83108

84109
[Demo 2](https://dandclark.github.io/json-css-module-notes/demo2/index.html) illustrates this difference. Both the no-module and the module case load a custom element that pulls in ~30MB of CSS. The no-module case imports inlines it in the JS file defining the custom element, in the style of some of the [existing Chromium layered API elements](https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/script/resources/layered_api/elements/). The module case imports the same CSS as a CSS module. After reaching steady-state, the memory difference is around the same ~30MB as the raw CSS text:
85110

86111
![Memory savings with CSS modules versus inlined CSS-as-JS-string](demo2/steadyState.PNG)
87112

88113
This steady state is reached after leaving both tabs in the background for ~60 seconds. Before this final garbage collection, the difference is even more stark; in my observations the inline CSS case hovers around ~95MB for the first 60 seconds, whereas the CSS modules tab goes down to ~38MB within the first few seconds.
89114

90-
91-
## Demo 3
92-
### [CSS modules vs `<link>` elements in shadow roots](https://dandclark.github.io/json-css-module-notes/demo3/index.html)
115+
### Demo 3: [Delayed stylesheet fetching for `<link>` elements in shadow roots](https://dandclark.github.io/json-css-module-notes/demo3/index.html)
93116
[https://dandclark.github.io/json-css-module-notes/demo3/index.html](https://dandclark.github.io/json-css-module-notes/demo3/index.html)
94117

95118
(There is no general CSS modules browser support as of this writing; that part of the demo was created and tested using a custom Chromium build).
96119

97-
This demo compares two similar custom elements written as a JavaScript module, each of which loads its styles from a separate `styles.css` file. The first custom element applies its styles by adding the styles via a `<link rel="stylesheet">` in the custom element shadow root. The second loads its styles via a CSS module.
120+
This demo shows an additional disadvantage to using `<link>` in a shadow root to load a custom element's styles. It compares two similar custom elements written as JavaScript modules, each of which loads its styles from a separate `styles.css` file. The first custom element applies its styles by adding the styles via a `<link rel="stylesheet">` in the custom element shadow root. The second loads its styles via a CSS module.
98121

99122
With the `<link>` element approach, the `<link>` isn't processed until an instance of the custom element is inserted into the document. In [demo3/NoModule.html](demo3/noModule.html), there is a delay before an instance of the custom element is created and inserted (to simulate, for example, a custom element that is only added based on some user action) and thus a corresponding delay before styles.css is fetched.
100123

demo1/cssModule.PNG

53.9 KB
Loading

demo1/module.html renamed to demo1/cssModule.html

Lines changed: 1 addition & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<script>
77
function logPerf(eventDesc) {
88
let li = document.createElement("li");
9-
li.appendChild(document.createTextNode(`${eventDesc}: ${Math.round(performance.now())}`));
9+
li.appendChild(document.createTextNode(`${eventDesc}: ${Math.round(performance.now())} ms`));
1010
document.getElementById("perfLog").appendChild(li);
1111
}
1212

@@ -15021,103 +15021,5 @@
1502115021
<styles-test-element></styles-test-element>
1502215022
<styles-test-element></styles-test-element>
1502315023
<styles-test-element></styles-test-element>
15024-
<styles-test-element></styles-test-element>
15025-
<styles-test-element></styles-test-element>
15026-
<styles-test-element></styles-test-element>
15027-
<styles-test-element></styles-test-element>
15028-
<styles-test-element></styles-test-element>
15029-
<styles-test-element></styles-test-element>
15030-
<styles-test-element></styles-test-element>
15031-
<styles-test-element></styles-test-element>
15032-
<styles-test-element></styles-test-element>
15033-
<styles-test-element></styles-test-element>
15034-
<styles-test-element></styles-test-element>
15035-
<styles-test-element></styles-test-element>
15036-
<styles-test-element></styles-test-element>
15037-
<styles-test-element></styles-test-element>
15038-
<styles-test-element></styles-test-element>
15039-
<styles-test-element></styles-test-element>
15040-
<styles-test-element></styles-test-element>
15041-
<styles-test-element></styles-test-element>
15042-
<styles-test-element></styles-test-element>
15043-
<styles-test-element></styles-test-element>
15044-
<styles-test-element></styles-test-element>
15045-
<styles-test-element></styles-test-element>
15046-
<styles-test-element></styles-test-element>
15047-
<styles-test-element></styles-test-element>
15048-
<styles-test-element></styles-test-element>
15049-
<styles-test-element></styles-test-element>
15050-
<styles-test-element></styles-test-element>
15051-
<styles-test-element></styles-test-element>
15052-
<styles-test-element></styles-test-element>
15053-
<styles-test-element></styles-test-element>
15054-
<styles-test-element></styles-test-element>
15055-
<styles-test-element></styles-test-element>
15056-
<styles-test-element></styles-test-element>
15057-
<styles-test-element></styles-test-element>
15058-
<styles-test-element></styles-test-element>
15059-
<styles-test-element></styles-test-element>
15060-
<styles-test-element></styles-test-element>
15061-
<styles-test-element></styles-test-element>
15062-
<styles-test-element></styles-test-element>
15063-
<styles-test-element></styles-test-element>
15064-
<styles-test-element></styles-test-element>
15065-
<styles-test-element></styles-test-element>
15066-
<styles-test-element></styles-test-element>
15067-
<styles-test-element></styles-test-element>
15068-
<styles-test-element></styles-test-element>
15069-
<styles-test-element></styles-test-element>
15070-
<styles-test-element></styles-test-element>
15071-
<styles-test-element></styles-test-element>
15072-
<styles-test-element></styles-test-element>
15073-
<styles-test-element></styles-test-element>
15074-
<styles-test-element></styles-test-element>
15075-
<styles-test-element></styles-test-element>
15076-
<styles-test-element></styles-test-element>
15077-
<styles-test-element></styles-test-element>
15078-
<styles-test-element></styles-test-element>
15079-
<styles-test-element></styles-test-element>
15080-
<styles-test-element></styles-test-element>
15081-
<styles-test-element></styles-test-element>
15082-
<styles-test-element></styles-test-element>
15083-
<styles-test-element></styles-test-element>
15084-
<styles-test-element></styles-test-element>
15085-
<styles-test-element></styles-test-element>
15086-
<styles-test-element></styles-test-element>
15087-
<styles-test-element></styles-test-element>
15088-
<styles-test-element></styles-test-element>
15089-
<styles-test-element></styles-test-element>
15090-
<styles-test-element></styles-test-element>
15091-
<styles-test-element></styles-test-element>
15092-
<styles-test-element></styles-test-element>
15093-
<styles-test-element></styles-test-element>
15094-
<styles-test-element></styles-test-element>
15095-
<styles-test-element></styles-test-element>
15096-
<styles-test-element></styles-test-element>
15097-
<styles-test-element></styles-test-element>
15098-
<styles-test-element></styles-test-element>
15099-
<styles-test-element></styles-test-element>
15100-
<styles-test-element></styles-test-element>
15101-
<styles-test-element></styles-test-element>
15102-
<styles-test-element></styles-test-element>
15103-
<styles-test-element></styles-test-element>
15104-
<styles-test-element></styles-test-element>
15105-
<styles-test-element></styles-test-element>
15106-
<styles-test-element></styles-test-element>
15107-
<styles-test-element></styles-test-element>
15108-
<styles-test-element></styles-test-element>
15109-
<styles-test-element></styles-test-element>
15110-
<styles-test-element></styles-test-element>
15111-
<styles-test-element></styles-test-element>
15112-
<styles-test-element></styles-test-element>
15113-
<styles-test-element></styles-test-element>
15114-
<styles-test-element></styles-test-element>
15115-
<styles-test-element></styles-test-element>
15116-
<styles-test-element></styles-test-element>
15117-
<styles-test-element></styles-test-element>
15118-
<styles-test-element></styles-test-element>
15119-
<styles-test-element></styles-test-element>
15120-
<styles-test-element></styles-test-element>
15121-
<styles-test-element></styles-test-element>
1512215024
</body>
1512315025
</html>

demo1/cssModuleElement.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ class CSSModuleTestElement extends HTMLElement {
1010
`<div class="outer-container"><div class="text-container">This text should be styled</div></div>`;
1111
}
1212
}
13-
export {CSSModuleTestElement};
13+
export {CSSModuleTestElement};

demo1/cssModuleHighlight.PNG

54.6 KB
Loading

demo1/index.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
<h2>Demo 4: Overhead of &lt;link&gt; elements for styles in shadow root</h2>
1+
<h2>Demo: Overhead of &lt;style&gt; and &lt;link&gt; elements in shadow root</h2>
22
<br>
33
<ul>
4-
<li><a href="./noModule.html">With &lt;link rel="stylesheet"&gt; in shadow root</a></li>
5-
<li><a href="./module.html">With CSS Module</a> (no browser support yet...)</li>
4+
<li><a href="./styleInShadowRoot.html">With &lt;style&gt; in shadow root</a></li>
5+
<li><a href="./linkInShadowRoot.html">With &lt;link rel="stylesheet"&gt; in shadow root</a></li>
6+
<li><a href="./cssModule.html">With CSS Module</a> (no browser support yet...)</li>
67
</ul>
78
<a href="../">[Back to main page]</a>

demo1/linkInShadowRoot.PNG

54.6 KB
Loading

0 commit comments

Comments
 (0)