<?xml version="1.0" encoding="utf-8"?>
  <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
      <title>Project Wallace Blog</title>
      <link>https://www.projectwallace.com/blog</link>
      <description>You want to learn more about the workings of Wallace? You came to the right place! Product updates, in depth analysis and more.</description>
      <lastBuildDate>Fri, 27 Mar 2026 13:06:41 GMT</lastBuildDate>
      <language>en</language>
      <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
      <generator>manual</generator>
      <atom:link href="https://www.projectwallace.com/blog/feed.xml" rel="self" type="application/rss+xml"/>
      
        <item>
          <guid>https://www.projectwallace.com//blog/introducing-the-css-selection</guid>
          <link>https://www.projectwallace.com/blog/introducing-the-css-selection?utm_source=rss</link>
          <title><![CDATA[Announcing The CSS Selection]]></title>
          <description><![CDATA[The most comprehensive large-scale CSS analysis ever documented is now online.]]></description>
          <content:encoded><![CDATA[<!--[--><p>After weeks of scraping and analyzing more than 100,000 websites I’m happy to announce: <a href="/the-css-selection/2026">The CSS Selection 2026</a>! This is the biggest ever attempt (as far as I know) to look at CSS from a high level and draw conclusions from usage of new CSS features and anti-patterns. It features even more metrics than the <a href="https://almanac.httparchive.org/en/" rel="nofollow">Web Almanac</a>, although I hope it is obvious that the Web Almanac is the giant on whose shoulders this article stands. On top of that it also highlights some insane outliers, just because it’s fun to do, and it may help you feel good about your own situation.</p> <p>The CSS Selection is a new concept for Project Wallace that I’ve wanted to do for a long time. It has taken tremendous effort to get this across the finish line but now it’s here and I couldn’t be more proud of it. There is also a big list of improvements that I plan on incorporating in future editions so expect this to become even better.</p> <p>The run up to publishing this has been so much fun. During analysis I’ve shared several statistics and screenshots on social media. People’s emotions ranged from sheer laughter to outrage. The design aspect of the whole thing was also pretty nice to do. I even attempted so make a <a href="https://codepen.io/bartveneman/pen/azZYqLZ" rel="nofollow">card-like image</a> for previews on social media. It took a little bit of fumbling, but I’m happy with how it turned out.</p> <p>Another very cool aspect of The CSS Selection is that it’s publicly sponsored by <a href="https://polypane.app/" rel="nofollow">Polypane</a>. That means that I had to work in a few pieces of sponsored content, but man, does it fit the rest of the content well! I had linked to at least two Polypane articles/pages already before Kilian came on board. It highlights how well Polypane fits this article and I’m proud to now have a second sponsor, next to <a href="https://www.netlify.com/" rel="nofollow">Netlify</a>, who kindly sponsor the hosting and deployment of this website.</p> <p><a href="/the-css-selection/2026">Read The CSS Selection now!</a></p><!--]-->]]></content:encoded>
          <pubDate>Fri, 06 Feb 2026 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/how-to-calculate-css-code-coverage-with-playwright</guid>
          <link>https://www.projectwallace.com/blog/how-to-calculate-css-code-coverage-with-playwright?utm_source=rss</link>
          <title><![CDATA[How to calculate CSS code coverage with @playwright/test]]></title>
          <description><![CDATA[Collect and analyze CSS coverage using Playwright tests to detect and prevent unused CSS in production.]]></description>
          <content:encoded><![CDATA[<!--[--><p>One of the most common questions around analyzing CSS quality is how to detect unused CSS. Shipping unused CSS to the browser is a waste so we want to avoid it! This post shows how to collect CSS code coverage data with the help of <a href="https://playwright.dev/docs/intro" rel="nofollow"><code>@playwright/test</code></a>.</p> <h2 id="table-of-contents"><a href="#table-of-contents">Table of contents</a></h2> <ol><li><a href="#writing-playwright-tests">Writing Playwright tests</a></li> <li><a href="#collect-css-coverage-from-playwright-tests">Collect CSS Coverage from Playwright tests</a></li> <li><a href="#bonus-analyzing-and-linting-css-coverage">Bonus: Analyzing and linting CSS Coverage</a></li></ol> <h2 id="writing-playwright-tests"><a href="#writing-playwright-tests">Writing Playwright tests</a></h2> <p>Start with writing Playwright tests for our website. Playwright tests run in a headless browser and they emulate user behaviour to verify that your website works as intended. The Project Wallace website has 230 of these tests. Here is one of them:</p> <pre class="language-ts"><!----><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">&#123;</span> test<span class="token punctuation">,</span> expect <span class="token punctuation">&#125;</span> <span class="token keyword">from</span> <span class="token string">'./tests/fixtures'</span>

<span class="token function">test</span><span class="token punctuation">(</span><span class="token string">'Navigation: pressing Escape on the popover closes the popover'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">&#123;</span> page <span class="token punctuation">&#125;</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>
	<span class="token keyword">let</span> trigger <span class="token operator">=</span> page<span class="token punctuation">.</span><span class="token function">getByRole</span><span class="token punctuation">(</span><span class="token string">'navigation'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getByLabel</span><span class="token punctuation">(</span><span class="token string">'Additional navigation items'</span><span class="token punctuation">)</span>
	<span class="token keyword">let</span> popover <span class="token operator">=</span> page<span class="token punctuation">.</span><span class="token function">locator</span><span class="token punctuation">(</span><span class="token string">'.nav-popover'</span><span class="token punctuation">)</span>

	<span class="token keyword">await</span> page<span class="token punctuation">.</span><span class="token function">setViewportSize</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> width<span class="token operator">:</span> <span class="token number">420</span><span class="token punctuation">,</span> height<span class="token operator">:</span> <span class="token number">800</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
	<span class="token keyword">await</span> trigger<span class="token punctuation">.</span><span class="token function">click</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

	<span class="token comment">// Press Escape</span>
	<span class="token keyword">await</span> page<span class="token punctuation">.</span>keyboard<span class="token punctuation">.</span><span class="token function">press</span><span class="token punctuation">(</span><span class="token string">'Escape'</span><span class="token punctuation">)</span>

	<span class="token comment">// Check that the popover is not visible</span>
	<span class="token keyword">await</span> expect<span class="token punctuation">.</span><span class="token function">soft</span><span class="token punctuation">(</span>popover<span class="token punctuation">)</span><span class="token punctuation">.</span>not<span class="token punctuation">.</span><span class="token function">toBeVisible</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token keyword">await</span> expect<span class="token punctuation">.</span><span class="token function">soft</span><span class="token punctuation">(</span>trigger<span class="token punctuation">)</span><span class="token punctuation">.</span>not<span class="token punctuation">.</span><span class="token function">toHaveAttribute</span><span class="token punctuation">(</span><span class="token string">'aria-expanded'</span><span class="token punctuation">,</span> <span class="token string">'true'</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">)</span></code><!----></pre> <p>That’s just one test, but imagine you have your whole website covered by these sort of tests. Extensive test coverage is essential for calculating CSS coverage.</p> <p>Notice the import of a fixture instead of the default <code>import { test, expect } from playwright/test'</code>. In the next step we’ll look at how to collect the data using these tests and why we use this fixture.</p> <h2 id="collect-css-coverage-from-playwright-tests"><a href="#collect-css-coverage-from-playwright-tests">Collect CSS Coverage from Playwright tests</a></h2> <p>Now that we have tests, we can start collecting data! Playwright provides two functions related to CSS Coverage: <a href="https://playwright.dev/docs/api/class-coverage#coverage-start-css-coverage" rel="nofollow"><code>coverage.startCSSCoverage()</code></a> and <a href="https://playwright.dev/docs/api/class-coverage#coverage-stop-css-coverage" rel="nofollow"><code>coverage.stopCSSCoverage()</code></a>. If you combine these functions with Playwright <a href="https://playwright.dev/docs/test-fixtures#creating-a-fixture" rel="nofollow">fixtures</a> you can collect coverage data from within your Playwright tests:</p> <pre class="language-ts"><!----><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">Fixtures</span> <span class="token operator">=</span> <span class="token punctuation">&#123;</span>
	cssCoverage<span class="token operator">:</span> <span class="token keyword">void</span>
<span class="token punctuation">&#125;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> test <span class="token operator">=</span> base_test<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">extend</span><span class="token generic class-name"><span class="token operator">&lt;</span>Fixtures<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>
	cssCoverage<span class="token operator">:</span> <span class="token punctuation">[</span>
		<span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">&#123;</span> page <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> use<span class="token punctuation">,</span> testInfo<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>
			<span class="token comment">// start before test</span>
			<span class="token keyword">await</span> page<span class="token punctuation">.</span>coverage<span class="token punctuation">.</span><span class="token function">startCSSCoverage</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
			<span class="token comment">// run the test</span>
			<span class="token keyword">await</span> <span class="token function">use</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
			<span class="token comment">// stop after test</span>
			<span class="token keyword">let</span> coverage <span class="token operator">=</span> <span class="token keyword">await</span> page<span class="token punctuation">.</span>coverage<span class="token punctuation">.</span><span class="token function">stopCSSCoverage</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

			<span class="token comment">// write coverage to disk</span>
			<span class="token keyword">await</span> fs<span class="token punctuation">.</span><span class="token function">writeFile</span><span class="token punctuation">(</span><span class="token string">'path-to-file.json'</span><span class="token punctuation">,</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>coverage<span class="token punctuation">)</span><span class="token punctuation">)</span>
		<span class="token punctuation">&#125;</span><span class="token punctuation">,</span>
		<span class="token punctuation">&#123;</span> auto<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">&#125;</span>
	<span class="token punctuation">]</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">)</span></code><!----></pre> <p>You can peek at <a href="https://github.com/projectwallace/projectwallace.com/blob/68080570ce614335bd6c10d5980f767c2628de86/tests/fixtures.ts#L6-L41" rel="nofollow">our implementation on GitHub</a>. That one is a bit more involved because it automatically generates a unique file name for each coverage file and attaches it to the test.</p> <p>Let’s break it down:</p> <h3 id="creating-the-fixture"><a href="#creating-the-fixture">Creating the fixture</a></h3> <p>This creates the actual <a href="https://playwright.dev/docs/test-fixtures#creating-a-fixture" rel="nofollow">fixture</a> <em>and</em> it tells Playwright to <a href="https://playwright.dev/docs/test-fixtures#automatic-fixtures" rel="nofollow">automatically</a> set up this fixture for every test.</p> <pre class="language-ts"><!----><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">Fixtures</span> <span class="token operator">=</span> <span class="token punctuation">&#123;</span>
	cssCoverage<span class="token operator">:</span> <span class="token keyword">void</span>
<span class="token punctuation">&#125;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> test <span class="token operator">=</span> base_test<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">extend</span><span class="token generic class-name"><span class="token operator">&lt;</span>Fixtures<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>
	cssCoverage<span class="token operator">:</span> <span class="token punctuation">[</span>
		<span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">&#123;</span> page <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> use<span class="token punctuation">,</span> testInfo<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>
			<span class="token comment">// etc.</span>
		<span class="token punctuation">&#125;</span><span class="token punctuation">,</span>
		<span class="token punctuation">&#123;</span> auto<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">&#125;</span>
	<span class="token punctuation">]</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">)</span></code><!----></pre> <h3 id="collecting-coverage"><a href="#collecting-coverage">Collecting coverage</a></h3> <p>The core of the fixture is surprisingly small! Start collecting, run the test and stop collecting.</p> <pre class="language-ts"><!----><code class="language-ts"><span class="token comment">// start before test</span>
<span class="token keyword">await</span> page<span class="token punctuation">.</span>coverage<span class="token punctuation">.</span><span class="token function">startCSSCoverage</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token comment">// Run the test</span>
<span class="token keyword">await</span> <span class="token function">use</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token comment">// stop after test</span>
<span class="token keyword">let</span> coverage <span class="token operator">=</span> <span class="token keyword">await</span> page<span class="token punctuation">.</span>coverage<span class="token punctuation">.</span><span class="token function">stopCSSCoverage</span><span class="token punctuation">(</span><span class="token punctuation">)</span></code><!----></pre> <h3 id="write-coverage-to-disk"><a href="#write-coverage-to-disk">Write coverage to disk</a></h3> <p>To be able to use the coverage data later on we need to write it to disk. In practice you’ll give this JSON file a unique name, probably the name or path of your actual test.</p> <pre class="language-ts"><!----><code class="language-ts"><span class="token keyword">await</span> fs<span class="token punctuation">.</span><span class="token function">writeFile</span><span class="token punctuation">(</span><span class="token string">'./css-coverage/path-to-file.json'</span><span class="token punctuation">,</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>coverage<span class="token punctuation">)</span><span class="token punctuation">)</span></code><!----></pre> <p>That’s all! Because of the auto-fixture every test will now self-report all CSS Coverage data.</p> <h2 id="bonus-analyzing-and-linting-css-coverage"><a href="#bonus-analyzing-and-linting-css-coverage">Bonus: Analyzing and linting CSS Coverage</a></h2> <p>After running our 230 Playwright tests we have megabytes of coverage data that we can start analyzing. As you could read in my <a href="https://www.projectwallace.com/blog/better-coverage-ranges" rel="nofollow">previous blog post</a> there are a lot of gaps to fill. On top of that many tests report the same coverage ranges for the same files so there is also a lot deduplication to do. And prettifying the CSS because no-one likes inspecting minified CSS. This is where our new <a href="https://github.com/projectwallace/css-code-coverage" rel="nofollow">@projectwallace/css-code-coverage</a> package comes in handy. It does all that for us and generates handy statistics. It even ships with a CLI that works as a CSS Coverage linter!</p> <p>The previous step generated a <code>/css-coverage</code> folder full with 230 JSON files. We’re going to use @projectwallace/css-code-coverage to make sure that our coverage is at an acceptable number:</p> <pre class="language-sh"><!----><code class="language-sh">css-coverage --coverage-dir=./css-coverage --min-line-coverage=.9  --min-file-line-coverage=.7 --show-uncovered=all</code><!----></pre> <p>See <a href="https://github.com/projectwallace/projectwallace.com/blob/68080570ce614335bd6c10d5980f767c2628de86/package.json#L18" rel="nofollow">our implementation here</a> as a <code>package.json</code> script.</p> <p>This command:</p> <ul><li>Tells the linter that our coverage JSON files live at <code>./css-coverage</code></li> <li>Sets the threshold for line coverage to .9 (or 90%) meaning that at least 90% of <em>all</em> lines of CSS combined must be covered</li> <li>Sets the threshold of file line coverage to .7 (or 70%) meaning that each individual CSS file must at least have 70% line coverage</li> <li>Instructs the linter to report all CSS files that don’t have 100% coverage</li></ul> <p>This is the result when running it for projectwallace.com’s repository:</p> <figure><a href="/_app/immutable/assets/css-code-coverage-linter-cli.x8jfP7Qn.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/css-code-coverage-linter-cli.DmTSBuHf.avif 717w, /_app/immutable/assets/css-code-coverage-linter-cli.B6bLgQY4.avif 1434w" type="image/avif"/><source srcset="/_app/immutable/assets/css-code-coverage-linter-cli.CLhZMC2p.webp 717w, /_app/immutable/assets/css-code-coverage-linter-cli.3inxQJdg.webp 1434w" type="image/webp"/><source srcset="/_app/immutable/assets/css-code-coverage-linter-cli.UKRM9d66.png 717w, /_app/immutable/assets/css-code-coverage-linter-cli.D_DbHq8g.png 1434w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/css-code-coverage-linter-cli.D_DbHq8g.png" alt="A terminal showing 'Failed: line coverage is 82.50%% which is lower than the threshold of 0.9; Failed: 4 files do not meet the minimum line coverage of 70.00% (minimum coverage was 47.75%)' and part of a CSS file where some lines are marked as uncovered." loading="lazy" width="1434" height="1242" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a> <figcaption>The CLI tool highlights which lines are not covered and even gives a hint with how many lines to go to meet the threshold.</figcaption></figure> <p>The @projectwallace/css-code-coverage package focuses on lines and not on bytes because as developers we also look at lines of code.</p> <p>Running this CLI in a GitHub action after the Playwright test gives you have accurate test data in each run and helps you catch shipping unused CSS!</p><!--]-->]]></content:encoded>
          <pubDate>Fri, 31 Oct 2025 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/better-coverage-ranges</guid>
          <link>https://www.projectwallace.com/blog/better-coverage-ranges?utm_source=rss</link>
          <title><![CDATA[Can we have better CSS Coverage ranges, please?]]></title>
          <description><![CDATA[Browers often don't report atrule ranges correctly in the CSS Coverage ranges.]]></description>
          <content:encoded><![CDATA[<!--[--><p>The last couple of months I’ve been working hard on improving how we can <a href="/css-coverage/">inspect</a> what parts of CSS are used by the browser and which parts aren’t. There’s some stuff I’m doing to <a href="https://github.com/projectwallace/css-code-coverage" rel="nofollow">make</a> large coverage reports so you can inspect several pages in one go by combining coverage JSON files into a single one, prettifying all the CSS etc. But there’s one thing that is being consistently troublesome for me:</p> <p><strong>Browers often don’t report atrule ranges correctly.</strong></p> <p>‘Often’ doesn’t mean ‘always’, but more than enough that it has bitten me more than I care to admit. It depends on where in they live and whether neighbouring rules are covered. But take this example that I’ve created using Playwright’s <a href="https://playwright.dev/docs/api/class-coverage#coverage-start-css-coverage" rel="nofollow">coverage API</a>, but I’ve observed this in Edge/Chrome as well:</p> <pre class="language-html"><!----><code class="language-html"><span class="token doctype"><span class="token punctuation">&lt;!</span><span class="token name">doctype</span> <span class="token name">html</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>html</span><span class="token punctuation">></span></span>
	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span>
		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>stylesheet<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://localhost/style.css<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span>
	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>
		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span>Hello world<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span>
		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Text<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>html</span><span class="token punctuation">></span></span></code><!----></pre> <pre class="language-css"><!----><code class="language-css"><span class="token comment">/* style.css */</span>
<span class="token atrule"><span class="token rule">@media</span> all</span> <span class="token punctuation">&#123;</span>
	<span class="token selector">h1</span> <span class="token punctuation">&#123;</span>
		<span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span>
<span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">)</span></span> <span class="token punctuation">&#123;</span>
	<span class="token selector">h1</span> <span class="token punctuation">&#123;</span>
		<span class="token property">font-size</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>Which parts do you think are marked as covered by the browser? The <code>@media all {}</code> rule? Or <code>@supports (display: grid) {}</code>. Well yes. No. Both. Neither. Let’s look at what the browser/Playwright generates for this:</p> <pre class="language-js"><!----><code class="language-js"><span class="token keyword">let</span> coverage <span class="token operator">=</span> <span class="token punctuation">[</span>
	<span class="token punctuation">&#123;</span> <span class="token literal-property property">start</span><span class="token operator">:</span> <span class="token number">7</span><span class="token punctuation">,</span> <span class="token literal-property property">end</span><span class="token operator">:</span> <span class="token number">11</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>
	<span class="token punctuation">&#123;</span> <span class="token literal-property property">start</span><span class="token operator">:</span> <span class="token number">14</span><span class="token punctuation">,</span> <span class="token literal-property property">end</span><span class="token operator">:</span> <span class="token number">37</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>
	<span class="token punctuation">&#123;</span> <span class="token literal-property property">start</span><span class="token operator">:</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token literal-property property">end</span><span class="token operator">:</span> <span class="token number">66</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>
	<span class="token punctuation">&#123;</span> <span class="token literal-property property">start</span><span class="token operator">:</span> <span class="token number">69</span><span class="token punctuation">,</span> <span class="token literal-property property">end</span><span class="token operator">:</span> <span class="token number">95</span> <span class="token punctuation">&#125;</span>
<span class="token punctuation">]</span></code><!----></pre> <p>It doesn’t mark the whole stylesheet as covered. Instead there are 4 different ranges. Let’s highlight the bits that the browser says are covered, leaving out tabs and newlines for readability:</p> <ol><li><code>all</code> (Missing <code>@media</code> and opening <code>{</code>)</li> <li><code>h1 { color: green; }</code></li> <li><code>(display: grid)</code> (Missing <code>@supports</code> and opening <code>{</code>)</li> <li><code>h1 { font-size: 24px; }</code></li></ol> <p>And both atrule ranges also miss their closing <code>}</code>.</p> <h2 id="what-i-want"><a href="#what-i-want">What I want</a></h2> <p>Can browsers please:</p> <ul><li>Include the atrule name in the ranges</li> <li>Include the opening <em>and</em> closing brackets (<code>{}</code>) in the ranges</li> <li>Be smart and then mark my example in a <em>single</em> range covering the whole stylesheet</li></ul> <p>Thanks.</p><!--]-->]]></content:encoded>
          <pubDate>Fri, 24 Oct 2025 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/ways-to-scrape-css</guid>
          <link>https://www.projectwallace.com/blog/ways-to-scrape-css?utm_source=rss</link>
          <title><![CDATA[3 ways to scrape CSS from a website]]></title>
          <description><![CDATA[Over the years I have explored several ways to get hold of a website's CSS. So far I've found these three options, each with their pros and cons.]]></description>
          <content:encoded><![CDATA[<!--[--><p>Over the years I have explored several ways to get hold of a website’s CSS. So far I’ve found these three options, each with their pros and cons. None of these methods actually check if the CSS is actually used, they only collect as much CSS as they possibly can.</p> <h2 id="table-of-contents"><a href="#table-of-contents">Table of contents</a></h2> <ol><li><a href="#option-1-scrape-the-html">Option 1: Scrape the HTML</a></li> <li><a href="#option-2-use-cssom">Option 2: Use CSSOM</a></li> <li><a href="#option-3-use-csscoverage-api">Option 3: Use CSSCoverage API</a></li> <li><a href="#summary">Summary</a></li></ol> <h2 id="option-1-scrape-the-html"><a href="#option-1-scrape-the-html">Option 1: Scrape the HTML</a></h2> <p>Project Wallace scrapes websites by fetching the HTML and then going through all HTML elements to grab bits of CSS out of them. This is a fast and cheap way to scrape websites because it does not involve a headless browser but only a handful of dependencies and some clever thinking. More specifically it looks like this:</p> <ol><li>Fetch the HTML document belonging to the URL you’ve entered (this works best in a NodeJS enviroment but in some cases is also possible in the browser)</li> <li>Parse the HTML into an AST <ol><li>Tip: use <a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString" rel="nofollow"><code>DOMParser.parseFromString()</code></a> if you’re in a browser environment or <a href="https://github.com/WebReflection/linkedom" rel="nofollow">linkedom</a> if you’re in a JavaScript engine like NodeJS</li></ol></li> <li>Walk the AST and grab every <code>&lt;style></code> element <ol><li>Each <code>&lt;style></code>’s contents can be added to our CSS as-is</li></ol></li> <li>Walk the tree and grab every <code>&lt;link rel~="stylesheet"></code> <ol><li>Grab the <code>href</code> from the <code>&lt;link></code></li> <li>Fetch the <code>href</code>’s contents</li> <li>Add the contents to our CSS</li></ol></li> <li>Walk the tree and grab every <code>[style]</code> element <ol><li>The <code>style</code> contents of <code>&lt;div style="color: red; margin: 0"></code> can be taken as-is</li> <li>Make up a selector and rule for the single element (like <code>div</code> in this example), or one selector and rule for <em>all</em> elements with inline styles (<code>inline-styles { color: red, etc. }</code>)</li> <li>Add the inline CSS to the rule</li> <li>Add the rule(s) to our CSS</li></ol></li> <li>Recursively scrape any CSS <code>@import</code> <ol><li>Parse the CSS into an AST</li> <li>Walk the tree and take each <code>import</code> atrule</li> <li>Take the <code>url()</code> of the import</li> <li>Download the contents of the URL</li> <li>Add to our CSS</li></ol></li></ol> <h3 id="pros-and-cons"><a href="#pros-and-cons">Pros and cons</a></h3> <table><thead><tr><th>✅ Pros</th><th>❌ Cons</th></tr></thead><tbody><tr><td>Cheap to run on a server</td><td>A lot of work to manage state, timeouts, error handling and data flows</td></tr><tr><td>Returns the CSS as it was sent to the browser / as authored</td><td>Does not easily fit in a bookmarklet</td></tr><tr><td>Can be run in your browser or other JavaScript runtimes</td><td>Does not find adoptedStylesheets or CSS injected with runtime CSS-in-JS</td></tr></tbody></table> <h2 id="option-2-use-cssom"><a href="#option-2-use-cssom">Option 2: Use CSSOM</a></h2> <p>The CSSOM is a collection of APIs that can be used to manipulate CSS from JavaScript. Part of this is the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/styleSheets" rel="nofollow"><code>document.styleSheets</code></a> property that we can use to grab all the CSS from a webpage. It’s such a small task that I’ll put the entire script here:</p> <h3 id="cssom-example"><a href="#cssom-example">CSSOM Example</a></h3> <pre class="language-js"><!----><code class="language-js"><span class="token keyword">function</span> <span class="token function">scrape_css</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token keyword">let</span> css <span class="token operator">=</span> <span class="token string">''</span>

	<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> stylesheet <span class="token keyword">of</span> document<span class="token punctuation">.</span>styleSheets<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span> <span class="token comment">// [1]</span>
		<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> rule <span class="token keyword">of</span> stylesheet<span class="token punctuation">.</span>cssRules<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span> <span class="token comment">// [2]</span>
			css <span class="token operator">=</span><span class="token operator">+</span> rule<span class="token punctuation">.</span>cssText <span class="token comment">// [3]</span>
		<span class="token punctuation">&#125;</span>
	<span class="token punctuation">&#125;</span>

	<span class="token keyword">return</span> css
<span class="token punctuation">&#125;</span></code><!----></pre> <h3 id="explanation"><a href="#explanation">Explanation</a></h3> <ol><li>Go over all the stylesheets of <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/styleSheets" rel="nofollow"><code>document.styleSheets</code></a></li> <li>Take the <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/cssRules" rel="nofollow"><code>cssRules</code></a> of each <code>styleSheet</code></li> <li>Read the <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSRule/cssText" rel="nofollow"><code>cssText</code></a> property from each <code>CSSRule</code> and add it to our <code>css</code> string. This sometimes causes Cross Origin issues so you may want to wrap that in a try-catch block.</li></ol> <h3 id="pros-and-cons-1"><a href="#pros-and-cons-1">Pros and cons</a></h3> <table><thead><tr><th>✅ Pros</th><th>❌ Cons</th></tr></thead><tbody><tr><td>Much simpler than HTML scraping</td><td>Requires a browser (‘real’ or headless), making it more expensive than HTML scraping to run on a server</td></tr><tr><td>Fits in a bookmarklet easily</td><td>Does not return the CSS in the format that it was authored in (it changes color notations, etc.)</td></tr><tr><td>Can be run in your browser or any JavaScript runtime that supports running (headless) browsers</td><td>Does not scrape inline styles</td></tr><tr><td></td><td>Cross Origin errors sometimes happen and are hard to solve</td></tr></tbody></table> <h2 id="option-3-use-csscoverage-api"><a href="#option-3-use-csscoverage-api">Option 3: Use CSSCoverage API</a></h2> <p>Headless browsers and Chromium-based browsers have the <code>CSSCoverage</code> API which can be used to detect which parts of your CSS are actually used and which parts aren’t. A <a href="https://playwright.dev/docs/api/class-coverage#coverage-start-css-coverage" rel="nofollow">great API</a> in itself but we can also use it to find <em>all</em> the CSS.</p> <h3 id="csscoverage-example"><a href="#csscoverage-example">CSSCoverage Example</a></h3> <pre class="language-js"><!----><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">&#123;</span> chromium <span class="token punctuation">&#125;</span> <span class="token keyword">from</span> <span class="token string">'playwright'</span> <span class="token comment">// or 'puppeteer'</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">scrape</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	<span class="token keyword">let</span> browser <span class="token operator">=</span> <span class="token keyword">await</span> chromium<span class="token punctuation">.</span><span class="token function">launch</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// [1a]</span>
	<span class="token keyword">let</span> page <span class="token operator">=</span> <span class="token keyword">await</span> browser<span class="token punctuation">.</span><span class="token function">newPage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// [1b]</span>

	<span class="token keyword">await</span> page<span class="token punctuation">.</span>coverage<span class="token punctuation">.</span><span class="token function">startCSSCoverage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// [2]</span>
	<span class="token keyword">await</span> page<span class="token punctuation">.</span><span class="token function">goto</span><span class="token punctuation">(</span><span class="token string">'https://example.com'</span><span class="token punctuation">)</span> <span class="token comment">// [3]</span>
	<span class="token keyword">let</span> coverage <span class="token operator">=</span> <span class="token keyword">await</span> page<span class="token punctuation">.</span>coverage<span class="token punctuation">.</span><span class="token function">stopCSSCoverage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// [4]</span>

	<span class="token keyword">let</span> css <span class="token operator">=</span> <span class="token string">''</span>
	<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> entry <span class="token keyword">of</span> coverage<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
		css <span class="token operator">+=</span> entry<span class="token punctuation">.</span>text
	<span class="token punctuation">&#125;</span>

	<span class="token keyword">return</span> css
<span class="token punctuation">&#125;</span></code><!----></pre> <h3 id="explanation-1"><a href="#explanation-1">Explanation</a></h3> <ol><li>Create a new browser and page</li> <li>Tell the browser to prepare to collect some coverage information. This must be done before going to a URL if you want to know all the CSS on the page after it loads</li> <li>Go to the actual URL you want to scrape</li> <li>Collect the coverage that the browser has covered</li> <li>Go over the coverage report and extract the CSS</li></ol> <h3 id="pros-and-cons-2"><a href="#pros-and-cons-2">Pros and cons</a></h3> <table><thead><tr><th>✅ Pros</th><th>❌ Cons</th></tr></thead><tbody><tr><td>Much simpler than HTML scraping</td><td>Requires a browser (‘real’ or headless), making it more expensive than HTML scraping to run on a server</td></tr><tr><td>Can be run in any JavaScript runtime that supports running (headless) browsers</td><td>Does not run in a bookmarklet</td></tr><tr><td>CSSCoverage can also be collected between opening a page, doing interactions and navigating to other pages</td><td></td></tr></tbody></table> <h2 id="summary"><a href="#summary">Summary</a></h2> <p>Each of these methods has their pros and cons so it really depends on the use case what you’ll end up using.</p> <table><thead><tr><th></th><th>HTML Scraper</th><th>CSSOM</th><th>CSSCoverage API</th></tr></thead><tbody><tr><td>Leaves CSS intact</td><td>✅</td><td>❌</td><td>✅</td></tr><tr><td>Cost to run on server</td><td>💰</td><td>💰💰</td><td>💰💰</td></tr><tr><td>Complexity</td><td>100</td><td>10</td><td>30</td></tr><tr><td>Runs in bookmarklet</td><td>✅ (a big bookmarklet)</td><td>✅</td><td>❌</td></tr><tr><td>Scrape inline styles</td><td>✅</td><td>❌</td><td>❌</td></tr></tbody></table> <p>Hope this was helpful. Did I miss anything? Let me know!</p><!--]-->]]></content:encoded>
          <pubDate>Mon, 04 Aug 2025 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/how-to-naked-css</guid>
          <link>https://www.projectwallace.com/blog/how-to-naked-css?utm_source=rss</link>
          <title><![CDATA[How to create a Naked CSS theme without removing CSS]]></title>
          <description><![CDATA[7 lines of code to make your website look naked]]></description>
          <content:encoded><![CDATA[<!--[--><p><a href="https://css-naked-day.org/" rel="nofollow">CSS Naked Day</a> is celebrated each year on 9 April. The point is that navigating a website without CSS applied is a powerful way of doing accessibility testing and adhering to Web Standards. How many accessibility flaws is your CSS hiding for you? Just as much as <a href="https://www.kryogenix.org/code/browser/everyonehasjs.html" rel="nofollow">JS might not always be loaded/executed</a> your CSS might not load as well and that’s equally problematic.
The official site suggests ways to <em>remove</em> all CSS, but sometimes that’s not practical. Maybe you’re working in a locked-down CMS, or dealing with a complex build setup (hi Vite and SvelteKit). But <em>adding</em> CSS? That’s usually pretty easy.</p> <p>So this post shows you how to make your website look naked in just 7 lines of code!</p> <pre class="language-css"><!----><code class="language-css"><span class="token selector">[data-theme="naked"]</span> <span class="token punctuation">&#123;</span>
	<span class="token property">color-scheme</span><span class="token punctuation">:</span> dark light <span class="token important">!important</span><span class="token punctuation">;</span>

	<span class="token selector">&amp;,
	&amp; :is(*, *::before, *::after)</span> <span class="token punctuation">&#123;</span>
		<span class="token property">all</span><span class="token punctuation">:</span> revert <span class="token important">!important</span><span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span>

	<span class="token selector">&amp; [aria-hidden='true']</span> <span class="token punctuation">&#123;</span>
		<span class="token property">display</span><span class="token punctuation">:</span> none <span class="token important">!important</span><span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <h2 id="code-breakdown"><a href="#code-breakdown">Code breakdown</a></h2> <ol><li><p>Select the document and all elements inside it. For me the <code>data-theme="naked"</code> is applied to the <code>&lt;html></code> element: <code>&lt;html data-theme="naked"></code></p> <pre class="language-css"><!----><code class="language-css"><span class="token selector">[data-theme="naked"]</span> <span class="token punctuation">&#123;</span>
	<span class="token comment">/* ... */</span>
<span class="token punctuation">&#125;</span></code><!----></pre></li> <li><p>Revert everything</p> <p>Modern CSS gives us <code>all: revert</code> and when applying that in my website’s CSS <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/all#values" rel="nofollow">it basically means</a> that it ‘removes’ all of my styles without deleting my CSS.</p> <pre class="language-css"><!----><code class="language-css"><span class="token selector">[data-theme="naked"]</span> <span class="token punctuation">&#123;</span>
	<span class="token comment">/* ... */</span>

	<span class="token selector">&amp;,
	&amp; :is(*, *::before, *::after)</span> <span class="token punctuation">&#123;</span>
		<span class="token property">all</span><span class="token punctuation">:</span> revert <span class="token important">!important</span><span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span>

	<span class="token comment">/* ... */</span>
<span class="token punctuation">&#125;</span></code><!----></pre></li> <li><p>Set color scheme (optional)</p> <p>Wallace has a dark theme by default so here I tell the browser that removing all styles makes the website still dark by default but also respond to a light preference.</p> <pre class="language-css"><!----><code class="language-css"><span class="token selector">[data-theme="naked"]</span> <span class="token punctuation">&#123;</span>
	<span class="token property">color-scheme</span><span class="token punctuation">:</span> dark light <span class="token important">!important</span><span class="token punctuation">;</span>

	<span class="token comment">/* ... */</span>
<span class="token punctuation">&#125;</span></code><!----></pre></li> <li><p>Bonus: hide inaccessible content</p> <p>I’m hiding all elements that have <code>aria-hidden="true"</code> because they are only useful visually, not for assistive technology. So that’s why it makes sense for me to hide these elements just to check how I navigate the website without them.</p> <pre class="language-css"><!----><code class="language-css"><span class="token selector">[data-theme="naked"]</span> <span class="token punctuation">&#123;</span>
	<span class="token comment">/* ... */</span>

	<span class="token selector">&amp; [aria-hidden='true']</span> <span class="token punctuation">&#123;</span>
		<span class="token property">display</span><span class="token punctuation">:</span> none <span class="token important">!important</span><span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span></code><!----></pre></li></ol> <hr/> <p>There is no automation on this site to automatically apply the naked theme on April 9th. That seems a bit drastic for me. You can choose it in the theme picker in the header, though. I do it myself from time to time and it’s really helpful.</p><!--]-->]]></content:encoded>
          <pubDate>Sat, 05 Jul 2025 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/june-2025-release-notes</guid>
          <link>https://www.projectwallace.com/blog/june-2025-release-notes?utm_source=rss</link>
          <title><![CDATA[June 2025 release notes]]></title>
          <description><![CDATA['Only' 20 releases on the website this month but also lots of releases outside in our open source projects.]]></description>
          <content:encoded><![CDATA[<!--[--><p>‘Only’ 20 releases on the website this month but also lots of releases outside in our open source projects. Strap yourself in for a big list!</p> <h2 id="new-features"><a href="#new-features">New features</a></h2> <h3 id="analyze-spacing-resets"><a href="#analyze-spacing-resets">Analyze spacing resets.</a></h3> <p>Sparked by Ana Rodrigues’ talk on CSS Day which showed that she used Project Wallace (yay!) and <a href="https://cssstats.com/" rel="nofollow">CSSStats</a> for auditing purposes. Turns out CSSStats does CSS resets analysis which we didn’t offer before. Now we do (since <a href="https://github.com/projectwallace/css-analyzer/pull/467" rel="nofollow">@projectwallace/css-analyzer@7.2.0</a>) with some improvements over CSSStats’ implementation:</p> <ul><li>Also check for logical properties (<code>margin-inline</code>, <code>padding-block-start</code>, etc.)</li> <li>Account for zero values that have a unit (<code>0px</code>, <code>0.0vh</code>, etc.)</li> <li>Account for shorthand values that are all zero (<code>0 0 0 0</code>, <code>0px 0 0em 0.0px</code>)</li></ul> <figure><a href="/_app/immutable/assets/resets.BSFjc0My.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/resets.D8fcnu0t.avif 860w, /_app/immutable/assets/resets.D_avKetb.avif 1719w" type="image/avif"/><source srcset="/_app/immutable/assets/resets.B8LLFMx_.webp 860w, /_app/immutable/assets/resets.Bs9ejQU5.webp 1719w" type="image/webp"/><source srcset="/_app/immutable/assets/resets.B1huHfaY.png 860w, /_app/immutable/assets/resets.CxNHbBdP.png 1719w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/resets.CxNHbBdP.png" alt="A table showing CSS spacing resets found on a page." loading="eager" width="1719" height="1158" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a> <figcaption>Spacing resets can be considered a code smell sometimes.</figcaption></figure> <h3 id="analyze-nesting-depth"><a href="#analyze-nesting-depth">Analyze nesting depth</a></h3> <p>Initially sparked by the <a href="https://almanac.httparchive.org/en/2022/css" rel="nofollow">Web Almanac CSS chapter</a>, this has been on my wish list for years and I finally got to it. It wasn’t even that hard to be honest so I’m a little disappointed in myself for not implementing this sooner. <a href="https://github.com/projectwallace/css-analyzer/pull/468" rel="nofollow">@projectwallace/css-analyzer@7.4.0</a> has all the details and the website now shows how deep you’re willing to go with your CSS.</p> <figure><a href="/_app/immutable/assets/nesting.BDRar8qL.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/nesting.B4aNP_CW.avif 860w, /_app/immutable/assets/nesting.BOnJayXu.avif 1719w" type="image/avif"/><source srcset="/_app/immutable/assets/nesting.tNevQG7r.webp 860w, /_app/immutable/assets/nesting.BZaY1pZU.webp 1719w" type="image/webp"/><source srcset="/_app/immutable/assets/nesting.OjhzdPez.png 860w, /_app/immutable/assets/nesting.DeKDxAc-.png 1719w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/nesting.DeKDxAc-.png" alt="A table showing CSS nesting depths found on a page." loading="lazy" width="1719" height="1320" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a> <figcaption>Oh, Adam, you're different than the rest of us</figcaption></figure> <h3 id="atrule-composition"><a href="#atrule-composition">Atrule composition</a></h3> <p>For a quick overview of what your atrule game looks like you can look at the new atrules composition chart. It shows an overview of which atrule was used how often. Neat way to get a quick view of the landscape before you dive deeper.</p> <figure><a href="/_app/immutable/assets/atrules.BxMxpZzC.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/atrules.qxtQFFKY.avif 637w, /_app/immutable/assets/atrules.NEhGGdBI.avif 1273w" type="image/avif"/><source srcset="/_app/immutable/assets/atrules.Mzeigra0.webp 637w, /_app/immutable/assets/atrules.DdPZ8Cfa.webp 1273w" type="image/webp"/><source srcset="/_app/immutable/assets/atrules.CQ9ZP3eU.png 637w, /_app/immutable/assets/atrules.FiYZxXGe.png 1273w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/atrules.FiYZxXGe.png" alt="A table showing CSS atrules found on a page." loading="lazy" width="1273" height="466" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a> <figcaption>Oh look, this looks like modern stuff. Look at how many <code>@property</code> and <code>@layer</code> atrules.</figcaption></figure> <h3 id="new-css-design-tokens-library"><a href="#new-css-design-tokens-library">New CSS design tokens library</a></h3> <p>Our design tokens page has had a panel for design tokens for a long time but now the output is more compliant with the <a href="https://tr.designtokens.org/" rel="nofollow">Design Tokens specification</a>. The code to create these tokens moved to a new package <a href="https://github.com/projectwallace/css-design-tokens" rel="nofollow">@projectwallace/css-design-tokens</a> and it also powers the <a href="/design-tokens">design tokens page</a> it was extracted from.</p> <h3 id="css-selector-complexity-calculator-page"><a href="#css-selector-complexity-calculator-page">CSS Selector complexity calculator page</a></h3> <p>Because I <em>sometimes</em> wish I had it. Nothing more. The CSS analyzer page shows a graph and table of selector complexity. Because of that it makes sense to offer <a href="/selector-complexity">a standalone tool</a> to test individual selectors quickly.</p> <a href="/_app/immutable/assets/complexity.CLOLeDGu.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/complexity.pttSrztR.avif 496w, /_app/immutable/assets/complexity.Citg0Lyz.avif 992w" type="image/avif"/><source srcset="/_app/immutable/assets/complexity.D3JSVMX0.webp 496w, /_app/immutable/assets/complexity.D9vna9Z0.webp 992w" type="image/webp"/><source srcset="/_app/immutable/assets/complexity.DJxhuPmx.png 496w, /_app/immutable/assets/complexity.BSZyuzLU.png 992w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/complexity.BSZyuzLU.png" alt="The complexity calculator showing the complexity of a long selector" loading="lazy" width="992" height="399" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a> <h2 id="updated-features"><a href="#updated-features">Updated features</a></h2> <ul><li>When Adam Argyle comes on stage at CSS Day you know you’re up for some great takeaways. This year <a href="https://nerdy.dev/cssday-2025" rel="nofollow">Adam did a talk</a> on scrollers and my first goal was to fix the ugly scrollbars that appear absolutely everywhere! I highly recommend you watch the talk when it appears on <a href="https://www.youtube.com/@WebConferencesAmsterdam" rel="nofollow">CSS Day’s YouTube channel</a>. Until then you can check out the slides on Adam’s website.</li> <li>Implemented additional error boundaries and error logging. <a href="https://sentry.io" rel="nofollow">Sentry</a> kindly offers an <a href="https://sentry.io/for/open-source/" rel="nofollow">Open-Source Sponsorship Plan</a> which allows me to do more fine grained error logging than  I could in the free plan. Thanks Sentry for sponsoring!</li> <li>We do a ton of syntax highlight across this website but the highlighting in docs and blog posts looked different from everything else. Well, no more!</li> <li>The <a href="/prettify-css">Prettify CSS page</a> now also allows indenting with spaces. But tabs are still default.</li></ul> <h2 id="bug-fixes"><a href="#bug-fixes">Bug fixes</a></h2> <ul><li><a href="https://www.projectwallace.com/blog/february-2025-release-notes" rel="nofollow">A while ago (February 2025)</a> we implemented persisted state when navigating from one tool to another: you no longer need to re-enter your URL and hit that button. The page would remember it for you. Great! Unless you were going to the <a href="/get-css">CSS Scraper</a> page. It would forget. What an odd one. Well, that’s fixed now. Going to get-css keeps the CSS you’ve scraped before.</li> <li>Syntax highlighting the CSS input field on the <a href="/ast-explorer">AST Explorer</a> page would often fail miserably. Now it fails less often and a little less miserable.</li> <li>When searching for a <a href="/custom-property-inspector">custom property</a> that contained an uppercase character you’d always be disappointed. Not just by how life treats you but also because Wallace was shit at comparing characters. I taught him a lesson and it’s such a good boy at finding your properties now.</li></ul> <h2 id="dependencies"><a href="#dependencies">Dependencies</a></h2> <ul><li>Updated <a href="https://github.com/bramus/specificity" rel="nofollow">@bramus/specificity</a> to 2.4.2 to fix a crash that would sometimes occur when people write CSS like <code>:nth-child()</code> or <code>:has()</code> without any contents inside the parentheses. Happens to the best of us.</li></ul> <h2 id="website-analytics"><a href="#website-analytics">Website analytics</a></h2> <ul><li>Visitors: 4.2K</li> <li>Page Views: 8.3K</li> <li>Top pages: <a href="/analyze-css">analyze-css</a> (25%), <a href="/get-css">get-css</a> (21.7%), <a href="/css-code-quality">css-code-quality</a> (18%), <a href="/design-tokens">design-tokens</a> (7%)</li></ul> <a href="/_app/immutable/assets/counterscale.GtFbgXJr.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/counterscale.CBDYB1Gd.avif 556w, /_app/immutable/assets/counterscale.CohC1UWF.avif 1111w" type="image/avif"/><source srcset="/_app/immutable/assets/counterscale.CxuTuseA.webp 556w, /_app/immutable/assets/counterscale.wTRJ70Sy.webp 1111w" type="image/webp"/><source srcset="/_app/immutable/assets/counterscale.DE6nBMVi.png 556w, /_app/immutable/assets/counterscale.C3dxPrDF.png 1111w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/counterscale.C3dxPrDF.png" alt="Dashboard overview of Counterscale website analytics for this website over the month June of 2025" loading="lazy" width="1111" height="1325" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a> <hr/> <p>Every time I think the monthly update is going to take a little while to put together and every time I am amazed at how much work it is to slap together all these links, images and ramblings into a somewhat coherent post.</p><!--]-->]]></content:encoded>
          <pubDate>Mon, 30 Jun 2025 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/april-may-2025-release-notes</guid>
          <link>https://www.projectwallace.com/blog/april-may-2025-release-notes?utm_source=rss</link>
          <title><![CDATA[April + May 2025 release notes]]></title>
          <description><![CDATA[Close your eyes! Or not. We have a light theme now. And no theme at all. And some other new stuff that you will probably like.]]></description>
          <content:encoded><![CDATA[<!--[--><h2 id="new-features"><a href="#new-features">New features</a></h2> <ul><li><p>A light theme! For those who prefer a light website or for those standing in bright sunlight.</p> <a href="/_app/immutable/assets/light-theme.BDDMgOzg.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/light-theme.5JsFeLcY.avif 855w, /_app/immutable/assets/light-theme.CBVBwa5-.avif 1710w" type="image/avif"/><source srcset="/_app/immutable/assets/light-theme.ChRJ2eaH.webp 855w, /_app/immutable/assets/light-theme.CqzZonw-.webp 1710w" type="image/webp"/><source srcset="/_app/immutable/assets/light-theme.mrO4y_Ta.png 855w, /_app/immutable/assets/light-theme.BtQRbftW.png 1710w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/light-theme.BtQRbftW.png" alt="This very website shown in a light theme: a white background with dark text." loading="eager" width="1710" height="1328" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a> <p>The idea for this had long been on the todo list but <a href="https://www.youtube.com/watch?v=F1s8MZoGVL8" rel="nofollow">a recent Syntax video</a> opened my eyes and made me bite the bullet. My implementation uses cookies instead of localStorage but most of the heavy lifting is still done by the CSS <code>light-dark()</code> function.</p></li> <li><p>A ‘naked CSS’ theme! For those celebrating <a href="https://css-naked-day.org/" rel="nofollow">CSS Naked Day</a>.</p> <a href="/_app/immutable/assets/naked-theme.Bw4GCEq5.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/naked-theme.BiQ6uzt7.avif 855w, /_app/immutable/assets/naked-theme.Cbh7ih3w.avif 1709w" type="image/avif"/><source srcset="/_app/immutable/assets/naked-theme.qv_COgh2.webp 855w, /_app/immutable/assets/naked-theme.DxUMFdmT.webp 1709w" type="image/webp"/><source srcset="/_app/immutable/assets/naked-theme.DCqIw93c.png 855w, /_app/immutable/assets/naked-theme.C7TI24kf.png 1709w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/naked-theme.C7TI24kf.png" alt="This very website shown without a theme. It shows the bare text elements with no styling applied at all." loading="lazy" width="1709" height="1326" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a> <p>Fun fact: implementing this was only 8 lines of code!</p> <pre class="language-css"><!----><code class="language-css"><span class="token selector">[data-theme="naked"],
[data-theme="naked"] *</span> <span class="token punctuation">&#123;</span>
	<span class="token property">all</span><span class="token punctuation">:</span> revert <span class="token important">!important</span><span class="token punctuation">;</span>
	<span class="token property">color-scheme</span><span class="token punctuation">:</span> dark light <span class="token important">!important</span><span class="token punctuation">;</span>

	<span class="token selector">[aria-hidden='true']</span> <span class="token punctuation">&#123;</span>
		<span class="token property">display</span><span class="token punctuation">:</span> none <span class="token important">!important</span><span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>But this is also a darn good way of finding accessibility issues.</p></li> <li><p>The <a href="/css-layers-visualizer">CSS <code>@layer</code> visualizer</a> page already had some devtools panels on it and now also includes the JSON output panel from the <a href="https://github.com/projectwallace/css-layer-tree" rel="nofollow">css-layer-tree library</a> that powers it. This allows for easy copy-pasting so you can use the result somewhere else.</p></li> <li><p>The <a href="/custom-property-inspector">Custom Property Inspector</a> now also has some devtools panels, like a network panel and a panel with the JSON output of all the properties that were inspected. In there you’ll find arrays of All properties, Unused properties, Undefined properties and Underfined with fallback properties.</p></li> <li><p>The selectors you enter on the <a href="/specificity-calculator">Specificity Calculator</a> are now synced to the URL for easy sharing.</p></li> <li><p>New website analytics. After saying goodbye to Fathom last fall after many years of using it I found myself needing a new source of analytics. Sentry catches the occassional error but when Sentry stays quiet I get nervous, because how do I know if anyone is still using the website? Luckily <a href="https://counterscale.dev/" rel="nofollow">Counterscale</a> is a free, performant and privacy-focused solution!</p> <a href="/_app/immutable/assets/counterscale.BXlY5pGV.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/counterscale.BXxdD9UR.avif 734w, /_app/immutable/assets/counterscale.0UZttNcY.avif 1467w" type="image/avif"/><source srcset="/_app/immutable/assets/counterscale.B_nQ1uO3.webp 734w, /_app/immutable/assets/counterscale.DmuVT9zz.webp 1467w" type="image/webp"/><source srcset="/_app/immutable/assets/counterscale.DkBx2FLk.png 734w, /_app/immutable/assets/counterscale.DWNbWzvM.png 1467w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/counterscale.DWNbWzvM.png" alt="Counterscale analytics dashboard. 4.1K visitors, 7.9K views." loading="lazy" width="1467" height="1322" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a> <p>I haven’t looked at my ‘old’ numbers but I have the feeling that there are more page views and visitors than a year ago. Over 4000 visitors and almost 8000 page views seems like a lot! The high number of page views for the CSS Scraper is surprising but according to Google they have been sending more traffic that way so I guess that checks out.</p> <p>It’ll be fun to an end-of-year summary again although I need to keep track of these numbers myself because they’ll be gone after 90 days.</p></li></ul> <h2 id="updated-features"><a href="#updated-features">Updated features</a></h2> <ul><li>Removed the resizeable panes from the AST Explorer and Custom Properties Inspector pages. <a href="https://paneforge.com" rel="nofollow">Paneforge</a> is great but for some reason the panes would always collapse on initial page render without a good reason and even worse: no errors logged.</li></ul> <h2 id="bug-fixes"><a href="#bug-fixes">Bug fixes</a></h2> <ul><li>Clicking a used property in the custom property inspector sidebar would always scroll and highlight the declared property, not the used one. This is now fixed.</li> <li>Guess what? Some of our own custom properties were broken. Using the custom property inspector helped me find and fix them 🙈</li> <li>Several unlisted bugs that Sentry caught. Several. Take that from me.</li></ul> <h2 id="dependencies"><a href="#dependencies">Dependencies</a></h2> <ul><li>All of the <code>@projectwallace</code> packages are now ESM only for a smaller install size, mostly cutting their <code>node_modules</code> size by 50% <ul><li><a href="https://github.com/projectwallace/css-analyzer" rel="nofollow">https://github.com/projectwallace/css-analyzer</a></li> <li><a href="https://github.com/projectwallace/css-layer-tree" rel="nofollow">https://github.com/projectwallace/css-layer-tree</a></li> <li><a href="https://github.com/projectwallace/color-sorter" rel="nofollow">https://github.com/projectwallace/color-sorter</a></li> <li><a href="https://github.com/projectwallace/css-code-quality" rel="nofollow">https://github.com/projectwallace/css-code-quality</a></li> <li><a href="https://github.com/projectwallace/format-css" rel="nofollow">https://github.com/projectwallace/format-css</a></li> <li><a href="https://github.com/projectwallace/css-time-sort" rel="nofollow">https://github.com/projectwallace/css-time-sort</a></li></ul></li> <li>Previously <em>all</em> tests for this website were done by Playwright. Now they’re split into unit tests (Vitest) and end to end tests (Playwright). This makes discovering bugs a little easier because of a better distinction in CI.</li> <li>Implemented Stylelint in the repository powering this website. Only a fairly basic config, but helpful nonetheless.</li></ul> <h2 id="performance"><a href="#performance">Performance</a></h2> <ul><li>Previous release notes mentioned a performance improvement for syntax highlighting blocks of CSS. This is now updated from a arbitrary <code>setTimeout()</code> to <code>requestIdleCallback()</code> if supported by the browser.</li> <li>The theme switch, navigation popover and CMD+K menu were previously powered by a <a href="https://www.melt-ui.com/docs/builders/popover" rel="nofollow">Melt popover</a>. They’re now fully <a href="https://developer.mozilla.org/en-US/docs/Web/API/Popover_API/Using" rel="nofollow">HTML <code>popover</code></a> elements! Hooray for the web!</li></ul> <hr/> <p>There are fewer notes than usual but that’s because the weather here has been very nice and I’ve been doing a ton of gardening and chores around the house. I highly recommend going outside more! After all, this is just a side project. 😉</p><!--]-->]]></content:encoded>
          <pubDate>Sat, 31 May 2025 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/march-2025-release-notes</guid>
          <link>https://www.projectwallace.com/blog/march-2025-release-notes?utm_source=rss</link>
          <title><![CDATA[March 2025 release notes]]></title>
          <description><![CDATA[35 releases but hardly anything that you will notice. Read on to learn why that is a good thing.]]></description>
          <content:encoded><![CDATA[<!--[--><p>35 production releases this month, 4 less than <a href="/blog/february-2025-release-notes">last month</a>. And almost half of those were related to replacing Tailwind CSS with ‘plain’ CSS and Svelte component-based styles. Yup I finally did it.</p> <p>Apart from that most effort went into making existing more resilient and adding more test coverage to increase stability.</p> <h2 id="new-features"><a href="#new-features">New features</a></h2> <p>None this month.</p> <h2 id="updated-features"><a href="#updated-features">Updated features</a></h2> <ul><li>The filter buttons in the network panel have been removed. Before this you were able to filter network resources by type like link tags or style tags. It occurred to me I hadn’t used this feature in years and had no test coverage for it. Additionally the usage differed a little from how the checkboxes in front of the network resources worked so I decided it’s time to let go.</li> <li>Aria attributes in sortable table columns have been added in places where they were missing and improved in places where they were already present. Under the hood this involved a bunch of deduplication whic akes it easier to do it right next time.</li></ul> <h2 id="bug-fixes"><a href="#bug-fixes">Bug fixes</a></h2> <ul><li>💥 Do not crash when <code>@font-face</code> contains invalid CSS</li> <li>💥 Do not crash when someone enters a non-URL in a URL field to analyze</li></ul> <h2 id="dependencies"><a href="#dependencies">Dependencies</a></h2> <p>Dropped Tailwind CSS, autoprefixer and postcss. There, it’s out there. I still like Tailwind, it’s been good for me the last couple of years. But there’s this itch that tells me that this silly website about CSS should be written in CSS. But even more so I feel I need to reconnect with modern CSS and learn more about view transitions, cascade layers (aka <code>@layer</code>), container queries and theming. So, consider this my first step trying to ship a light theme or a dim one. Or both.</p> <p>Even though I’ve tried to do the migration in small pieces you may still occasionally see some broken styling here and there.</p> <a href="/_app/immutable/assets/layers.COnW8ib3.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/layers.nWxcLwhH.avif 438w, /_app/immutable/assets/layers.CXzWw1nX.avif 876w" type="image/avif"/><source srcset="/_app/immutable/assets/layers.D6PPC17L.webp 438w, /_app/immutable/assets/layers.hBxaYzRw.webp 876w" type="image/webp"/><source srcset="/_app/immutable/assets/layers.BNRsLz2F.png 438w, /_app/immutable/assets/layers.BPj5PFok.png 876w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/layers.BPj5PFok.png" alt="A layer tree showing 11 layers." loading="lazy" width="876" height="704" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a> <p>I even implemented a rudimentary layer architecture right after I finished the migration!</p> <h2 id="performance"><a href="#performance">Performance</a></h2> <ul><li>Syntax highlighting of <code>&lt;pre></code> blocks is now postponed 100ms. This should give the browser some breathing room to first apply layout and paint and do syntax highlighting after that. This is mostly noticable when opening our devtools, like when you click a specific color or selector.</li> <li>Additionally we now keep track of highlighted strings that we also clean up when the <code>&lt;pre></code> element is unmounted. This solves a memory leak that was actually noticable after analyzing lots of sites.</li> <li>The network panel used to keep a <em>full copy</em> of all resources in memory. That’s right, even with <em>all</em> the CSS that you downloaded. So your CSS could be 1MB and we would keep another 1MB in memory just so you could sort the whole thing by size or URL. Well, now we store the sorting in a <code>Uint8Array</code> that’s only as long as the amount of network resources you have. Phew.</li> <li>Sorting design token colors by color would always convert every color twice. It’s now faster because we <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort?utm_source=chatgpt.com#sorting_with_map" rel="nofollow">first map the entire array and then sort the result</a>. This is faster because converting the color is relatively slow.</li></ul> <h2 id="resilience"><a href="#resilience">Resilience</a></h2> <p>Lots of tests were added this month for the AST Explorer and CSS Coverage pages. This helps me add features more easily because Playwright will warn me when I break something. (not if; <em>when</em>)</p> <hr/> <p>Do you enjoy these release notes? Please let me know via BlueSky or LinkedIn.</p> <p>That’s it for this month!</p><!--]-->]]></content:encoded>
          <pubDate>Mon, 31 Mar 2025 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/february-2025-release-notes</guid>
          <link>https://www.projectwallace.com/blog/february-2025-release-notes?utm_source=rss</link>
          <title><![CDATA[February 2025 release notes]]></title>
          <description><![CDATA[A new feature on the site that has been on the list for years and it's finally here!]]></description>
          <content:encoded><![CDATA[<!--[--><p>39 production releases with performance improvements, bug fixes and a couple of new features. Let’s start with an absolute banger!</p> <h2 id="new-features"><a href="#new-features">New features</a></h2> <p>This one has been on my list for <em>ages</em> and the recent upgrade to Svelte 5 and this <a href="https://www.youtube.com/watch?v=e1vlC31Sh34" rel="nofollow">video from Huntabyte about Svelte 5, context and classes</a> helped me get it over the line:</p> <h3 id="-css-is-maintained-between-page-navigations"><a href="#-css-is-maintained-between-page-navigations">🎉 CSS is maintained between page navigations</a></h3> <p>You rarely audit one website or file in isolation. Wallace has many pages and depending on the CSS or the goal you want to either check code quality scores, CSS <code>@layer</code> composition or custom property usage. Before this change you always needed to re-enter the URL or upload the file again. Well, no more! Click to another page and the CSS will still be there.</p> <h3 id="other-new-stuff"><a href="#other-new-stuff">Other new stuff</a></h3> <ul><li>The CSS Scraper pages now has it’s own network panel. It’s the same one you’re familiar with from the analyzer and layers pages. While writing these release notes I spotted a very nasty UI bug so expect that to be fixed soon.</li> <li>Pseudo classes analysis because this is rather useful when looking for usage of <code>:popover-open</code> or other new-ish selectors that might not be readily available yet in all browsers. <a href="/_app/immutable/assets/pseudo-classes.BvFuAThQ.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/pseudo-classes.sPrvWWva.avif 639w, /_app/immutable/assets/pseudo-classes.Dj1c1NQ5.avif 1277w" type="image/avif"/><source srcset="/_app/immutable/assets/pseudo-classes.DDalvs_F.webp 639w, /_app/immutable/assets/pseudo-classes.CNULR8pM.webp 1277w" type="image/webp"/><source srcset="/_app/immutable/assets/pseudo-classes.CDnrLN7p.png 639w, /_app/immutable/assets/pseudo-classes.zV4-Mlco.png 1277w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/pseudo-classes.zV4-Mlco.png" alt="A table of 12 CSS pseudo classes, sorted by count." loading="eager" fetchproprity="high" width="1277" height="921" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a></li></ul> <h2 id="updated-features"><a href="#updated-features">Updated features</a></h2> <ul><li><p>Syntax highlighting in the <a href="/ast-explorer">AST Explorer</a> input field. It was a tricky one, but some blog posts from GitHub and Sentry helped me along the way to finetune it. We’re (still) not using a virtualized list because the input for the AST Explorer is usually quite small and doesn’t warrant the overhead of such a tool.</p></li> <li><p>Syntax highlighting in item usage devtools.</p> <a href="/_app/immutable/assets/item-usage-highlight.Bn5wwlam.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/item-usage-highlight.Dw6uzVkM.avif 860w, /_app/immutable/assets/item-usage-highlight.DTUERBlo.avif 1719w" type="image/avif"/><source srcset="/_app/immutable/assets/item-usage-highlight.GBUb5TEt.webp 860w, /_app/immutable/assets/item-usage-highlight.BnYDz0W-.webp 1719w" type="image/webp"/><source srcset="/_app/immutable/assets/item-usage-highlight.cmcivLe1.png 860w, /_app/immutable/assets/item-usage-highlight.DOdTAWWa.png 1719w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/item-usage-highlight.DOdTAWWa.png" alt="Item usage devtools showing a table of syntax highlighted CSS rulesets" loading="lazy" width="1719" height="942" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a> <p>After implementing sytax highlighting in most large <code>&lt;pre></code> blocks I fgured it was time for smaller chunks of code to look nice too.</p></li> <li><p>Calculating the diffstat on the <a href="/css-diff">CSS Diff page</a> is now a lot more accurate on large diffs as well as being a bunch faster. I’m really into using typed arrays in JavaScript lately, so the diffstat is now a <code>Uint8Array()</code> with 5 integers: <code>0</code> for unchanged, <code>1</code> for deletions and <code>2</code> for additions.</p> <a href="/_app/immutable/assets/diffstat.CWpUewI1.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/diffstat.Di2BWOW1.avif 438w, /_app/immutable/assets/diffstat.MeEnktQP.avif 875w" type="image/avif"/><source srcset="/_app/immutable/assets/diffstat.DqEWupSY.webp 438w, /_app/immutable/assets/diffstat.CIHNiyJs.webp 875w" type="image/webp"/><source srcset="/_app/immutable/assets/diffstat.Dh4yBCNw.png 438w, /_app/immutable/assets/diffstat.Bl_Lj22n.png 875w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/diffstat.Bl_Lj22n.png" alt="A git diff shown with a diffstat above it saying that there were 5 changes: 3 additions and 2 deletions. It shows 5 squares, 3 green and 2 red." loading="lazy" width="875" height="406" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a></li></ul> <h3 id="split-panes"><a href="#split-panes">Split panes</a></h3> <p>The AST Explorer was the first page where I implemented the excellent <a href="https://paneforge.com/" rel="nofollow">PaneForge</a> library to have resizable elements. I liked the ability of dragging interface elements so much that this is now available in the following places:</p> <ul><li>AST Explorer</li> <li>Custom Properties analyzer</li> <li>CSS Coverage analyzer</li> <li>Devtools networkpanel</li> <li>Devtools item usage</li></ul> <p>Resize and render performance isn’t exactly great in some of these places when bombarding it with big CSS input, but it’s on my radar and some day I’ll fgure out how to tackle that.</p> <a href="/_app/immutable/assets/resize.MXfpn-P0.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/resize.DR0qWg7i.avif 860w, /_app/immutable/assets/resize.0C8pbDPv.avif 1719w" type="image/avif"/><source srcset="/_app/immutable/assets/resize.D2SPfHjt.webp 860w, /_app/immutable/assets/resize.Drc0Zv-u.webp 1719w" type="image/webp"/><source srcset="/_app/immutable/assets/resize.YzwkPJd3.png 860w, /_app/immutable/assets/resize.DH4P9P2o.png 1719w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/resize.DH4P9P2o.png" alt="Wallace devtools network panel with the horizontal resize handle being highlighted" loading="lazy" width="1719" height="796" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a> <h2 id="dependencies"><a href="#dependencies">Dependencies</a></h2> <ul><li><a href="https://github.com/bramus/specificity/releases/tag/v2.4.0" rel="nofollow">@bramus/specificity version 2.4.0</a> was released and contains some of my own fixes and improvements so projectwallace.com now also uses the latest and greatest.</li> <li><a href="https://github.com/projectwallace/css-analyzer/releases/tag/v6.0.0" rel="nofollow">css-analyzer v6 is released</a> and it contains some long overdue breaking changes and some bug fixes for issues that I was able to spot because Sentry flagged them.</li> <li><a href="https://github.com/projectwallace/css-code-quality/releases/tag/v2.0.0" rel="nofollow">css-code-quality v2 is released</a> as an effect of releasing css-analyzer v6. It depends on the analyzer and had one deprecation to be removed.</li></ul> <h3 id="performance"><a href="#performance">Performance</a></h3> <p>A lot of the sorting functions used on the analyzer page are now faster because we store the sorted elements in a <code>Uint32Array()</code> instead of creating copies for <em>every single list</em>. This is worth a blog post on it’s own, so I’ll probably postpone that until… Well. Long.</p> <hr/> <p>A lot of this month was preparing releases, upgrading dependencies across the board and making sure nothing broke in the process.</p> <p>Do you enjoy these release notes? Please let me know via BlueSky or LinkedIn.</p> <p>That’s it for this month!</p><!--]-->]]></content:encoded>
          <pubDate>Fri, 28 Feb 2025 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/january-2025-release-notes</guid>
          <link>https://www.projectwallace.com/blog/january-2025-release-notes?utm_source=rss</link>
          <title><![CDATA[January 2025 release notes]]></title>
          <description><![CDATA[It has been quite the month with 40+ releases. Many changes happened in the design tokens page. Read about the highlights here.]]></description>
          <content:encoded><![CDATA[<!--[--><p>January has been a very busy month with over 40+ PR’s merged to production and two new major features added. Here are the most important changes for you.</p> <h2 id="new-features"><a href="#new-features">New features</a></h2> <ul><li><p><a href="/css-coverage"><strong>CSS Coverage viewer</strong></a>.</p> <a href="/_app/immutable/assets/css-coverage.CM2bLxog.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/css-coverage.Bi-XXTvA.avif 1378w, /_app/immutable/assets/css-coverage.DxmjgE_D.avif 2756w" type="image/avif"/><source srcset="/_app/immutable/assets/css-coverage.Cq7dYXsa.webp 1378w, /_app/immutable/assets/css-coverage.BvKtUmgn.webp 2756w" type="image/webp"/><source srcset="/_app/immutable/assets/css-coverage.ogny5lPT.png 1378w, /_app/immutable/assets/css-coverage.BDLm5qvb.png 2756w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/css-coverage.BDLm5qvb.png" alt="A webpage showing CSS Coverage report." loading="eager" fetchproprity="high" width="2756" height="1554" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a> <p>This deserves a blog post in itself because it’s quite a big one. DevTools like in Puppeteer, Playwright, Chrome and Edge provide an option to inspect what portions of CSS are actually covered. Playwright and Puppeteer allow to keep track of coverage across pages but it’s hard to inspect. Our new viewer accepts a coverage JSON file, prettifies all the CSS (and adjusts the coverage ranges) and shows you which lines are covered and which are not. This is also the first page where I’ve implemented the loading an example file. This is an advanced tool and getting a coverage JSON isn’t that easy for most developers.</p></li> <li><p><a href="/ast-explorer"><strong>CSS AST Explorer</strong></a></p> <a href="/_app/immutable/assets/ast-explorer.BoUWA5Ur.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/ast-explorer.TyeR_48h.avif 1415w, /_app/immutable/assets/ast-explorer.C9kMbUjf.avif 2830w" type="image/avif"/><source srcset="/_app/immutable/assets/ast-explorer.BmIzz6mf.webp 1415w, /_app/immutable/assets/ast-explorer.k1FqHmB2.webp 2830w" type="image/webp"/><source srcset="/_app/immutable/assets/ast-explorer.D-sFypFD.png 1415w, /_app/immutable/assets/ast-explorer.Ce-qqqUv.png 2830w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/ast-explorer.Ce-qqqUv.png" alt="A CSS AST explorer. A webpage with two panels side by side. The left panel shows some CSS. The right panel shows a tree structure with items like SelectorList and AtRule." loading="lazy" fetchproprity="low" width="2830" height="1202" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a> <p>Almost every tool on Project Wallace uses CSSTree under the hood. <a href="https://astexplorer.net/" rel="nofollow">ASTExplorer.net</a> is a tools that visualizes AST’s like the ones generated by CSSTree. However, ASTExplorer is not running the latest version of CSSTree, so in the interest of learning a new skill and getting a useful tool out of it I created this page as well as opening <a href="https://github.com/fkling/astexplorer/pull/729" rel="nofollow">a PR to update CSSTree at ASTExplorer</a>.</p></li></ul> <h2 id="updated-features"><a href="#updated-features">Updated features</a></h2> <ul><li><p>Many sections of the <a href="/analyze-css">css analysis</a> and <a href="/design-tokens">design tokens</a> pages can be sorted by source order or by count. Many of them can now also be sorted alphabetically. This can lead to better discoverability of issues. Try sorting media queries or selectors alphabetically and you’ll see why this exists.</p> <a href="/_app/immutable/assets/list-sorting.CtG22fRN.png"><!--[-1--><picture><!--[--><source srcset="/_app/immutable/assets/list-sorting.C-Oj1FDx.avif 1082w, /_app/immutable/assets/list-sorting.C9nh7f6Z.avif 2164w" type="image/avif"/><source srcset="/_app/immutable/assets/list-sorting.Og71sgRP.webp 1082w, /_app/immutable/assets/list-sorting.M40cQLK6.webp 2164w" type="image/webp"/><source srcset="/_app/immutable/assets/list-sorting.DhBF1z45.png 1082w, /_app/immutable/assets/list-sorting.BP2Xq2oS.png 2164w" type="image/png"/><!--]--> <img src="/_app/immutable/assets/list-sorting.BP2Xq2oS.png" alt="Wallace's value count lists for @media and @keyframes, sorted alphabetically" loading="lazy" fetchproprity="low" width="2164" height="1370" onload="this.__e=event" onerror="this.__e=event"/></picture><!--]--><!----></a></li></ul> <h3 id="design-tokens"><a href="#design-tokens">Design tokens</a></h3> <p>So many changes happened in the <a href="/design-tokens">Design Tokens</a> page that it warrants it’s own section.</p> <ul><li><strong>BREAKING</strong>: Design token <code>$extension</code>s were renamed from <code>project-wallace.css-authored-as</code> to <code>com.projectwallace.css-authored-as</code> to align with how the <a href="https://tr.designtokens.org/format/#extensions" rel="nofollow">spec suggests</a> authors to prefix their extensions</li> <li>Design token names are now hashed for easier readbility and smaller JSON output. <ul><li>Before: <code>black-rgba(0, 0, 0, .1)</code></li> <li>After: <code>black-1755eb96</code>.</li></ul></li> <li>Font family tokens are now automatically unquoted. <ul><li>Before: <code>['"Arial Black"']</code></li> <li>After: <code>['Arial Black']</code></li></ul></li> <li>Duration tokens are now formatted per latest <a href="https://tr.designtokens.org/format/#duration" rel="nofollow">spec</a> changes (<code>duration</code> objects with a <code>unit</code> and <code>value</code>)</li> <li>Box-shadow tokens are now formatted per latest <a href="https://tr.designtokens.org/format/#shadow" rel="nofollow">spec</a> changes</li> <li>Line-height tokens are now formatted per latest <a href="https://tr.designtokens.org/format/#dimension" rel="nofollow">spec</a> changes (they’re either a Dimension or Number type)</li> <li>Font-size tokens are now formatted per latest <a href="https://tr.designtokens.org/format/#dimension" rel="nofollow">spec</a> changes</li> <li>Change the <a href="https://tr.designtokens.org/format/#file-extensions" rel="nofollow">file extension</a> of the downloaded JSON to <code>.tokens.json</code> as the spec suggests.</li></ul> <h2 id="dependencies"><a href="#dependencies">Dependencies</a></h2> <ul><li>Updated <a href="https://github.com/projectwallace/color-sorter" rel="nofollow">color-sorter</a> to the latest version (6.1.2). It features a smaller bundle size and uses the latest <a href="https://colorjs.io/" rel="nofollow">Colorjs.io</a> package.</li> <li>All dependencies were updated to their latest versions, including some security updates.</li> <li>Replaced eslint and it’s adjecent packages, making installing packages noticably faster. Linting now happens with <a href="https://oxc.rs/docs/guide/usage/linter.html" rel="nofollow">OxLint</a>.</li> <li>Replaced <a href="https://github.com/sindresorhus/got" rel="nofollow">got</a> with the fetch API and <code>AbortController</code>. This saves 24 extra dependencies but more importantly doesn’t require me to read about breaking changes every time we head into a new Node LTS.</li></ul> <h2 id="accessibility"><a href="#accessibility">Accessibility</a></h2> <ul><li>Some improvements to how we handle (scrollable) tables, like providing tab access to the element that allows the table to scroll; Also added <code>scope="col"</code> and <code>aria-sort</code> in most places where apropriate.</li></ul> <h3 id="performance"><a href="#performance">Performance</a></h3> <ul><li>Refactored some cases where we would <code>JSON.stringify()</code> some data and later on would <code>JSON.parse()</code> the same data again. This is suboptimal in terms of memory usage, so it’s rewritten to handle plain numbers.</li> <li>The <a href="/custom-property-inspector">custom property tree viewer</a> would pass lots of large objects back and forth. These objects are now a lot smaller, reducing memory usage.</li> <li>Avoid thousands of DOM nodes in memory: last year we added an option to use arrow keys to navigate through most lists and tables. To achieve that we used to store every row’s DOM node in memory. It is now rewritten to only store row indexes and do a quick DOM node lookup when necessary which in most cases isn’t even needed.</li> <li>Upon inspection we saw that the design tokens JS file contained <em>a lot</em> of <code>com.projectwallace.css-authored-as</code> strings. Moving this to a <code>const</code> reduced the bundle size significantly.</li> <li>The design tokens JSON would be generated from a ton of <code>Object.entries()</code> with <code>.filter()</code>, <code>.map()</code> and <code>.reduce()</code>. They’re now a single <code>for (let prop in my_object) {}</code> loop per token type.</li></ul> <hr/> <p>That was quite the list. Apart from what’s relevent for you, there have been tons of new unit tests, Playwright tests and dependency updates without user facing impact.</p> <p>The upcoming months will probably introduce a lot of additional updates to the Design Tokens page. I’m also hoping to spend some time to make your CSS ‘stick’ between page navigations to make it easier to navigate between pages without having to pass in your URL every time.</p> <p>That’s it for this month!</p><!--]-->]]></content:encoded>
          <pubDate>Fri, 31 Jan 2025 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/2023-in-review</guid>
          <link>https://www.projectwallace.com/blog/2023-in-review?utm_source=rss</link>
          <title><![CDATA[2023 in review]]></title>
          <description><![CDATA[This was a year full of new releases and a bunch of exposure in public places. Let's look at the numbers and see how much time you've spent here. We're also looking at the plans for next year because it's going to be pretty special.]]></description>
          <content:encoded><![CDATA[<!--[--><p>It’s becoming sort of a tradition to look back on another year working on Project Wallace just as I did in <a href="/blog/2022-in-review">2022</a> and <a href="/blog/2021-review-in-numbers">2021</a>. This year marked a change in approach as I focused a lot more on things I <strong>wanted</strong> to build and maintain and not work on things that didn’t <em>spark joy</em>.</p> <h2 id="last-years-ideas"><a href="#last-years-ideas">Last year’s ideas</a></h2> <p>Last year I made this list of ideas, so let’s have a quick look at how it turned out.</p> <ol><li><strong>Update the CSS Analyzer to use Bramus’ Specificity calculator. It’s more correct and saves me a bunch of work in maintaining;</strong> Done!</li> <li><strong>Adding a page to the website to quickly calculate specificity. There’s already a bunch of them out there, but like the prettifier, I don’t like context switching;</strong> Done!</li> <li><strong>Improving the CSS Code Quality page to use the analyzed CSS to explain the bits that are sub-optimal;</strong> Sort of done. It’s better than it was before, but not perfect.</li> <li><strong>Implementing lots of new analysis features, like native CSS nesting, reporting on color formats used and most importantly: calculating total CSS complexity!</strong> Still didn’t do nesting and total complexity but instead focused more on features that are broadly used already, like the color formats one and a bunch of others.</li></ol> <p>Let’s call it a 3 out of 4 score in total. Not too bad, as it shows I can stick to my own plan, but also adapt to my own appetite and user demands.</p> <h2 id="celebrating-successes"><a href="#celebrating-successes">Celebrating successes</a></h2> <ul><li>Project Wallace wasn’t mentioned <a href="https://syntax.fm/show/678/the-2023-state-of-css-survey-part-2-css-frameworks-tooling-browser-usage" rel="nofollow">once</a>, but <a href="https://syntax.fm/show/698/why-you-should-be-using-css-layers" rel="nofollow"><em>twice</em></a> on the Syntax.fm podcast this year. I’ll admit that my heart absolutely jumped when I found out. Fun fact: I used the power of Wallace to submit 2 <a href="https://github.com/syntaxfm/website/pull/1333" rel="nofollow">pull</a> <a href="https://github.com/syntaxfm/website/pull/1403" rel="nofollow">requests</a> to the new Syntax website to improve their CSS.</li> <li><a href="https://2023.stateofcss.com/en-US/other-tools/" rel="nofollow">The State of CSS survey</a> included Project Wallace this year under the ‘other tools’ section</li> <li>I was recognized by <em>several people</em> on <a href="https://cssday.nl/2023" rel="nofollow">CSS Day</a> this year for my work on Wallace (this branded shirt may have helped). It’s a vain metric, but it felt like a validation for all the hard work and I highly enjoyed talking to a couple of you.</li> <li><a href="https://csswizardry.com/" rel="nofollow">Harry Roberts</a> (of CSSWizardry fame) let me put <a href="https://twitter.com/csswizardry/status/1618305495949008902" rel="nofollow">one of the best endorsements ever</a> on the homepage!</li></ul> <h2 id="notable-releases"><a href="#notable-releases">Notable releases</a></h2> <p>Let’s kick things off by looking at what’s new on the website this year. Some features like the prettifier and the specificity calculator aren’t the best in the market, but they’re small, standalone tools. This fits in my plan to make Project Wallace <em>the</em> online workspace for CSS engineers to hone their skills and ease all forms of CSS auditing.</p> <p>This year I merged <strong>171 pull requests</strong> with all kinds of new features, tests, bug fixes, performance improvements and maintenance work. Yes, I’m using pull requests because I don’t trust myself so I review every change. It also makes sure my tests are successful before I merge. Here’s a summary of the list:</p> <ul><li>All kinds of new analysis for the <a href="/analyze-css">CSS analyzer</a> page</li> <li>A page to <a href="/specificity-calculator">calculate selector specificity</a></li> <li>A page to <a href="/prettify-css">prettify CSS</a></li> <li>A page to <a href="/minify-css">minify CSS</a></li> <li>A <a href="/css-units-game">fun little game</a> where you have to guess all CSS units!</li> <li>A <a href="/css-layers-visualizer">CSS <code>@layer</code> visualizer</a> page to inspect your layer composition</li> <li>A page to <a href="/custom-property-linter">lint</a> your unused and undeclared custom properties</li> <li>There’s a <code>CMD+K</code> shortcut dialog now to quickly jump to the tool you need</li> <li>Lots of performance improvements, like the one I blogged about earlier this year: <a href="/blog/making-analyze-css-render-6x-faster">Making Analyze CSS render 6 times faster</a></li> <li>Rewrote <a href="https://github.com/projectwallace/wallace-cli" rel="nofollow">wallace-cli</a> from scratch with only 2 dependencies this time (<a href="https://github.com/alexeyraspopov/picocolors" rel="nofollow">picocolors</a> and our own css-analyzer)</li></ul> <h2 id="interesting-numbers"><a href="#interesting-numbers">Interesting numbers</a></h2> <p>Fathom has been collecting stats over the last year again, so let’s have a look!</p> <figure><img width="800" height="263" alt="Line graph showing website traffic for the year 2023 with a huge spike around November" loading="lazy" fetchpriority="low" src="/_app/immutable/assets/fathom.BHmE3wIU.jpg"/> <figcaption>Pretty stable traffic over the year until <a href="https://twitter.com/wesbos/status/1719460340197400604">Wes Bos starts tweeting</a>…</figcaption></figure> <h3 id="traffic"><a href="#traffic">Traffic</a></h3> <p>For the first time ever there’s a slight decrease in these stats. My explanation for this is that last year’s traffic was exceptionally high: twice that of the year before. In my review of last year, I had a suspicion that there is a high amount of passers-by traffic. I guess I’m calling that one confirmed. Traffic last year was also particularly high because of my <a href="/blog/css-complexity">CSS Complexity blog post</a>. That one is still attracting a bit of traffic but it caused a <em>huge</em> spike last year.</p> <table><thead><tr><th></th><th>2020</th><th>2021</th><th>2022</th><th>2023</th></tr></thead><tbody><tr><td>Visitors</td><td>6,510</td><td>10,942</td><td>20,702</td><td>17,535</td></tr><tr><td>Page views</td><td>24,767</td><td>40,256</td><td>47,031</td><td>42,325</td></tr></tbody></table> <h3 id="website-activity"><a href="#website-activity">Website activity</a></h3> <p>Website activity has seen a bit of growth even though traffic went down a little bit. Should I tell my Marketing Director that my engagement tactics are working? I don’t know, I’ll stick to analyzing CSS.</p> <table><thead><tr><th></th><th>2020</th><th>2021</th><th>2022</th><th>2023</th></tr></thead><tbody><tr><td>Analyze CSS (url)</td><td>742</td><td>3,161</td><td>6,379</td><td>7,997</td></tr><tr><td>Analyze CSS (raw input)</td><td>8</td><td>503</td><td>2,345</td><td>3,385</td></tr><tr><td>CSS Code Quality (url)</td><td>-</td><td>-</td><td>4,788</td><td>2,797</td></tr><tr><td>CSS Code Quality (raw input)</td><td>-</td><td>-</td><td>790</td><td>1,967</td></tr><tr><td>Scrape CSS</td><td>138</td><td>264</td><td>248</td><td>378</td></tr><tr><td>Prettify CSS</td><td>-</td><td>-</td><td>40</td><td>153</td></tr><tr><td>CSS Units game plays</td><td>-</td><td>-</td><td>-</td><td>223</td></tr><tr><td>CSS Layer analysis</td><td>-</td><td>-</td><td>-</td><td>23</td></tr></tbody></table> <p>New activities like layer analysis are below 100 completions probably because they are very new and more importantly <em>very niche</em> tools.</p> <h3 id="time-on-site"><a href="#time-on-site">Time on site</a></h3> <p>My friend <a href="https://blog.jellesmeets.nl/personal/2023-in-review/" rel="nofollow">Jelle</a> showed me a year-in-review blog post recently where the author calculated time-on-site which seems like a fun one to do. Let’s define it as <code>_visitors × average time on site_</code>.</p> <table><thead><tr><th></th><th>2020</th><th>2021</th><th>2022</th><th>2023</th></tr></thead><tbody><tr><td>Visitors</td><td>6,510</td><td>10,942</td><td>20,702</td><td>17,535</td></tr><tr><td>Average time on site</td><td>01:09</td><td>00:46</td><td>01:22</td><td>01:39</td></tr><tr><td>Total time on site</td><td>5d 4h 46m</td><td>5d 19h 48m</td><td>19d 15h 32m</td><td>20d 02h 12m</td></tr></tbody></table> <p>Well, it looks like 2023 is a successful year if you look at the total time spent. Even though the amount of page views and visitors went down, the average time on site went up.</p> <aside>If you enjoy looking at these numbers as much as I do, please consider <a href="https://usefathom.com/ref/I6TUXR">Fathom Analytics</a> using this referral link. You will save $10 on your first invoice and I will get a discount on mine as well.</aside> <h2 id="failures"><a href="#failures">‘Failures’</a></h2> <ul><li>Tried to build a <a href="https://addons.mozilla.org/en-GB/firefox/addon/css-analyzer" rel="nofollow">browser extension</a> to run the CSS Analyzer in your browser, but building cross-browser extensions is horrible and sharing a codebase between this website and the (open source) extension was not a problem I was interested in solving. There is also an issue with cross-site stylesheets and JS not having access to them that was hard to debug, so I gave up for now. Maybe later.</li> <li>Multiple times I’ve tried to do a proper upgrade on the color-sorter for it to support additional color spaces like OKLCH, but my brain could not yet figure out how to do the conversion. The library is very simple in its current form and adding another color converter would mean I have to rewrite the whole thing and I simply didn’t have the appetite for that yet.</li> <li>Looking ahead to the <a href="#what-is-next">ideas for next year</a> I started poking around a potential architecture for Project Wallace’s new Projects feature. I know what it looked like in the previous iteration, but I can’t seem to wrap my mind around the problems I encountered there and how to solve them. Not sure how to proceed, but I probably can’t keep running away from it forever. Or wait, I can. It’s <em>my</em> side project, I can do whatever the f*ck I want.</li></ul> <h2 id="blogging"><a href="#blogging">Blogging</a></h2> <p>I wrote 10 blog posts this year, including the one you are reading now. That’s a whopping 100% increase from the 5 posts I wrote the year before. I still don’t really like doing it. It involves a lot of work like research and testing for the technical posts. This year’s most popular post was <a href="/blog/making-analyze-css-render-6x-faster">Making Analyze CSS render 6 times faster</a> with 1500+ views.</p> <h2 id="tech-changes"><a href="#tech-changes">Tech changes</a></h2> <p>There’s some work I’ve had to do to keep Wallace in tip-top shape for some new things I’m planning.</p> <img width="600" height="335" alt="An excerpt of Playwright's test output showing all green test results" loading="lazy" fetchpriority="low" src="/_app/immutable/assets/playwright.B5db98h5.jpg"/> <p>This year I’ve added a total of 111 <a href="https://playwright.dev/" rel="nofollow">Playwright</a> tests to make sure that new releases don’t break any existing functionality. I love using Playwright as it’s quite easy to add new tests and incredibly fast to run them. It takes around 20 seconds on my M2 Macbook, while GitHub needs 2 minutes to run them all in a single worker.</p> <p>Keeping SvelteKit up to date has been a bit of work since it’s so new and still evolving (<a href="https://kit.svelte.dev/docs/migrating-to-sveltekit-2" rel="nofollow">2.0 was just released</a> this month), so I had to spend some time here and there to keep things in check.</p> <h2 id="what-is-next"><a href="#what-is-next">What is next?</a></h2> <p>So, what will happen next year? Well, I’m not sure yet. There is a lot going on in my private life and Project Wallace is and continues to be a side project, so my main focus will be on maintaining a healthy balance in my day job and my personal life.</p> <p>I keep wondering if I’m ready for Wallace’s next big step: CSS complexity history. We used to have this in the past, but I had to shut it down because of (how ironic) massive complexity, technical debt and a lack of funding. So I’m planning to do things differently this time and make it a much greater experience for you, the user, but for myself as well. I haven’t figured out the architecture for this yet, so if you’re interested in helping me out with this, feel free to reach out! No promises or release dates, but if you want to stay up to date: <a href="/projects-coming-soon">subscribe to updates</a>.</p> <p>A thing I’ve wanted to try <em>for years</em> is to write a collection of <a href="https://stylelint.io/" rel="nofollow">Stylelint</a> rules based on our metrics. It looks like I’ve figured out how to do that now, so let’s add this to the list. For now, I’m combining them all in a <a href="https://github.com/projectwallace/stylelint-plugin" rel="nofollow">single Stylelint plugin</a>. It’s nowhere near ready to use, because it’s dog-slow and I have to figure out how to solve that first. But it’s a start!</p> <p>Lastly, I’d like to order a big box full of Wallace-themed t-shirts. During CSS Day I came up with some designs that I’d like printed myself. Asking around on Twitter even yielded some enthusiastic folks who’d buy one of them. Again, I’m hesitant because A) shipping outside of The Netherlands is costly and B) it’s a huge gamble to order a lot of shirts upfront not knowing if they’ll ever sell. But still, look at them… I think they’re too good to pass on.</p> <figure><img width="600" height="600" loading="lazy" fetchpriority="low" src="/_app/immutable/assets/css-is-rad-tee.3wmL48kC.png" alt="Black T-Shirt with white letters saying 'CSS is Rad'. The word Rad is shown in a grunge font and a teal color."/> <figcaption><q>CSS is rad</q> as a play on the usual <q>CSS is awesome</q> meme.</figcaption></figure> <figure><img width="600" height="600" loading="lazy" fetchpriority="low" src="/_app/immutable/assets/css-css-css-tee.DtFVnTgw.png" alt="Black T-Shirt with the text 'CSS CSS CSS' in a teal color in a grunge font"/> <figcaption><q>CSS! CSS! CSS!</q> was the recurring chant of CSS Day 2023 thanks to MC Adam Argyle.</figcaption></figure> <hr/> <p>It’s been a great year and I absolutely love working on Project Wallace. It’s been my playground for a long time now and I hope to be playing around for many years to come. I really appreciate you following along and let’s connect in the new year!</p> <p>Bart</p><!--]-->]]></content:encoded>
          <pubDate>Sun, 24 Dec 2023 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/space-around-slash-in-font-shorthand</guid>
          <link>https://www.projectwallace.com/blog/space-around-slash-in-font-shorthand?utm_source=rss</link>
          <title><![CDATA[Is space allowed around the slash in CSS font shorthand?]]></title>
          <description><![CDATA[Everywhere in CSS we see space around the / symbol, but not for the font shorthand. What's up with that?]]></description>
          <content:encoded><![CDATA[<!--[--><p>Let’s say you’re writing a CSS pretty printer and you come across the <code>/</code> symbol: what do you do? You add space around it. Except in <code>font: 16px/1.2 serif;</code>, because no one ever seems to do it there. This leads me to think: is space around <code>/</code> even allowed in the font shorthand and does it actually work?</p> <p><a href="https://codepen.io/bartveneman/pen/yLZGoqY?editors=1100" rel="nofollow">Testing</a> shows that <code>16px/2</code> is the same as <code>16px / 2</code> and the spacing seems to work in all major browsers (Safari 17.1, Firefox 119, Chrome 119). The spec also does not seem to mention anything around whitespace, so we’re good there too. So why do we write it like this?</p> <p>I could not find any sources or style guides that dictate the use (or lack) of space of <code>/</code> in shorthands, so if you do, please let me know! I guess the lack of space around the slash in the <code>font</code> shorthand is just an age-old preference and not commonly applied to other places.</p> <p>What does this mean for <a href="https://github.com/projectwallace/format-css" rel="nofollow">our CSS formatter</a>? Well, I’ve grown so used to not using spaces in the <code>font</code> shorthand that I’ve written <a href="https://github.com/projectwallace/format-css/blob/4fd80c7b3e33413ddd01ccfac2626a8912e045f4/index.js#L289-L292" rel="nofollow">a single if-statement</a> to not add whitespace in that particular scenario. Other cases, like <code>calc(2em / 2)</code> and <code>background: no-repeat center / cover url('image.png');</code> still get the extra space.</p><!--]-->]]></content:encoded>
          <pubDate>Tue, 05 Dec 2023 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/new-online-css-minifier</guid>
          <link>https://www.projectwallace.com/blog/new-online-css-minifier?utm_source=rss</link>
          <title><![CDATA[A new online CSS minifier]]></title>
          <description><![CDATA[We all need that quick online CSS minifier every once in a while, so new we have our own.]]></description>
          <content:encoded><![CDATA[<!--[--><p><em>TL;DR: Project Wallace has it’s own <a href="/minify-css">CSS Minifier</a>!</em></p> <p>Manually minifying CSS is one of those tasks you run into every once in a while. Sometimes it’s just too much work to setup a bundler or some script to do it for you, so you quickly do a search for an online CSS minifier and pick the first one.</p> <p>Now, with more CSS tools on this website than pretty much anywhere else, it only makes sense that we have our own page for compressing your CSS as well. Not because we think we do the best job (certainly not), but because for those quick scenarios it’s usually good enough to remove most whitespace and CSS comments. And that’s exactly what our minifier does:</p> <ul><li>remove whitespace</li> <li>remove comments</li> <li>render all CSS on a single line</li> <li>it all runs in your browser; no data is sent to us, so your privacy is guaranteed</li></ul> <hr/> <p>Like it says on the page: if you need more thorough minification, you’ll need to look at some of the great solutions out there, because they will remove empty rules and atrules, minify colors, strip unnecessary units where possible and so, so much more:</p> <ul><li><a href="https://lightningcss.dev/" rel="nofollow">LightningCSS</a></li> <li><a href="https://github.com/css/csso" rel="nofollow">CSSO</a></li> <li><a href="https://github.com/cssnano/cssnano" rel="nofollow">cssnano</a></li></ul> <p>We chose not to do that, because there are a lot of intricate problems to solve with removing parts of CSS, replacing values and rewriting rules. We choose to go for a small, fast, client-side solution. The projects listed above are separate projects, maintained by excellent people who spent a lot of time on improving their setups in ways that we never could.</p><!--]-->]]></content:encoded>
          <pubDate>Fri, 17 Nov 2023 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/in-the-news</guid>
          <link>https://www.projectwallace.com/blog/in-the-news?utm_source=rss</link>
          <title><![CDATA[Wallace in the news]]></title>
          <description><![CDATA[Excitement all around as Project Wallace is featured in the State of CSS survey, mentioned on Syntax.fm and helped improve Polypane's devtools!]]></description>
          <content:encoded><![CDATA[<!--[--><p>Just a short post to share my excitement that Project Wallace has been mentioned in public by some pretty cool people!</p> <h2 id="state-of-css"><a href="#state-of-css">State of CSS</a></h2> <img src="/_app/immutable/assets/survey.eymqLKKl.png" alt="Overview of all utilities listed on the State of CSS" loading="eager" width="1092" height="755"/> <p>Thanks to <a href="https://twitter.com/romainmenke/status/1670852482174468096" rel="nofollow">Romain</a> we were listed under <a href="https://2023.stateofcss.com/en-US/other-tools/" rel="nofollow">the ‘utilities’ section</a>, which means we’re not a framework or a bundler, basically. It really helps getting this sort of exposure and I can see a slight increase in traffic and CSS analyzed because of this. Very cool to see Project Wallace listed in such an influential survey, even though I’m fully aware that my impact is very, very small. 30 of you indicated that they’ve been using Project Wallace from time and even though that’s not much compared to the other tools, it’s enough validation for me to keep going!</p> <h2 id="syntaxfm"><a href="#syntaxfm">Syntax.fm</a></h2> <img src="/_app/immutable/assets/syntax.BPVrYHvG.png" alt="Screenshot of the Syntax.fm website with the show notes" loading="lazy" width="1081" height="738"/> <p>Listening to the latest Syntax.fm episode certainly had me excited when Scott and Wes started talking about Project Wallace! I’ve been a Syntax listener from day one and I really appreciate the hard work they put into making the podcast. They’ve clarified dozens of On this episode they’re discussing the results of the <a href="https://2023.stateofcss.com/en-US" rel="nofollow">State of CSS survey</a> where they discover that Project Wallace is a pretty neat tool to keep your color distribution in check or to review all font-families in use. My favorite quote from the show, mostly because of Scott’s audible amazement:</p> <blockquote><p>This <strong>is</strong> cool!</p></blockquote> <cite>Scott Tolinski</cite> <p>You can listen to <a href="https://syntax.fm/show/678/the-2023-state-of-css-survey-part-2-css-frameworks-tooling-browser-usage#t=22:30" rel="nofollow">the full episode on the Syntax.fm website</a>. Project Wallace is mentioned around the 22m30s mark.</p> <h2 id="polypane-blog"><a href="#polypane-blog">Polypane blog</a></h2> <img src="/_app/immutable/assets/polypane.BjU5SGAv.png" alt="Screenshot of the Polypane blog with the section about CSS cascade layers and Project Wallace's visualizer" loading="lazy" width="1333" height="1153"/> <p>Kilian from Polypane recently reached out to ask for some feedback about devtools he was building to make using <code>@layer</code> easier. He probably spotted that I was working on the <a href="/css-layers-visualizer">layer visualizer</a> and by the looks of it, he did a great job of visualizing layers in Polypane itself. The devtools now show the full layer tree related to the style rule you’re debugging, which is really helpful. He wrote about it in his <a href="https://polypane.app/blog/polypane-15-fully-featured-browser-in-the-browse-panel-performance-improvements-chromium-116-and-more/#support-for-complex-layer-nesting" rel="nofollow">Polypane 15 release notes</a>, so go try out Polypane if you haven’t yet.</p><!--]-->]]></content:encoded>
          <pubDate>Fri, 13 Oct 2023 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/css-imports-are-awesome</guid>
          <link>https://www.projectwallace.com/blog/css-imports-are-awesome?utm_source=rss</link>
          <title><![CDATA[CSS @imports are awesome]]></title>
          <description><![CDATA[CSS imports have been popping up a lot lately for me so I thought it's time to have a deeper look a t them. Was not disappointed!]]></description>
          <content:encoded><![CDATA[<!--[--><p>One of the most inspirational things lately is to watch at <a href="https://nerdy.dev/" rel="nofollow">Adam Argyle’s</a> side projects and see how he’s doing some really nerdy CSS work. It was <a href="https://codepen.io/argyleink/pen/PoxQrNj" rel="nofollow">somewhere in his work</a> where I found it, in all it’s glory. Waiting to be explored, a rich journey ahead, anxious for the CSS developer community’s love and approval. It’s everyone’s favorite CSS at-rule: <code>@import</code>!</p> <p>Triggered by my feature requests for project Wallace to extract media queries and supports rules from imports, I started reading into the humble rule. And then Romain <a href="https://github.com/romainmenke/css-import-tests" rel="nofollow">started a repository</a> with a bunch of browser and bundler tests to verify different levels of support. In this day of the componentized web and scoped styling solutions it’s grown out of favour. For years we’ve been told to <a href="https://csswizardry.com/2017/02/code-smells-in-css-revisited/#css-import" rel="nofollow">avoid <code>@import</code></a> for performance reasons. But if you take a closer look, you’ll find that <code>@import</code> is <strong>packed</strong> with a ton of features that actually make you want to use this bad boy.</p> <ol><li><a href="#flexible-syntax-url-url-or-url">Flexible URL syntax</a></li> <li><a href="#cascade-layers">Cascade Layers</a></li> <li><a href="#supports-in-import">Conditional imports: <code>supports()</code></a></li> <li><a href="#media-queries-in-import">Conditional imports: media queries</a></li></ol> <h2 id="flexible-syntax-url-url-or-url"><a href="#flexible-syntax-url-url-or-url">Flexible syntax: <code>"url"</code>, <code>url('')</code>, or <code>url()</code></a></h2> <p>The most important thing an <code>@import</code> needs to do is to import CSS rules some location. The location part here is quite important to the at-rule, so luckily it’s very easy to write the URL correctly. Right?</p> <p>Well. Let’s have a look at these examples:</p> <pre class="language-css"><!----><code class="language-css"><span class="token atrule"><span class="token rule">@import</span> <span class="token string">'https://example.com/style.css'</span><span class="token punctuation">;</span></span>
<span class="token atrule"><span class="token rule">@import</span> <span class="token string">"https://example.com/style.css"</span><span class="token punctuation">;</span></span>
<span class="token atrule"><span class="token rule">@import</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span>https://example.com/style.css<span class="token punctuation">)</span></span><span class="token punctuation">;</span></span>
<span class="token atrule"><span class="token rule">@import</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'https://example.com/style.css'</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span></span>
<span class="token atrule"><span class="token rule">@import</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">"https://example.com/style.css"</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span></span></code><!----></pre> <p>Yup, they’re all the same thing. Even the one wrapped within <code>url()</code> with no quotes at all! Apparently there are <a href="https://drafts.csswg.org/css-values-4/#urls" rel="nofollow">legacy reasons</a> to allow that. The spec also says this:</p> <blockquote><p>Some CSS contexts (such as <code>@import</code>) also allow a <code>&lt;url></code> to be represented by a bare <code>&lt;string></code>, without the function wrapper. In such cases the string behaves identically to a <code>url()</code> function containing that string.</p></blockquote> <p>It’s good to know that there’s at least 5 different ways to specify the URL for <code>@import</code>. Look at you being all flexible.</p> <h2 id="cascade-layers"><a href="#cascade-layers">Cascade Layers</a></h2> <p>Next up: one of the best additions to CSS in recent years: Cascade Layers! This has me all excited because Bramus gave a thrilling talk at CSS Day last year about it’s workings and capabilities. And then I saw Adam’s CodePen profile packed with example usage of <code>layer()</code> in <code>@import</code>. Here’s three from the Pen I linked in the intro:</p> <pre class="language-css"><!----><code class="language-css"><span class="token atrule"><span class="token rule">@import</span> <span class="token string">'https://unpkg.com/open-props'</span> <span class="token function">layer</span><span class="token punctuation">(</span>design.system<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="token atrule"><span class="token rule">@import</span> <span class="token string">'https://unpkg.com/open-props/normalize.min.css'</span> <span class="token function">layer</span><span class="token punctuation">(</span>demo.support<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="token atrule"><span class="token rule">@import</span> <span class="token string">'https://unpkg.com/open-props/buttons.min.css'</span> <span class="token function">layer</span><span class="token punctuation">(</span>demo.support<span class="token punctuation">)</span><span class="token punctuation">;</span></span></code><!----></pre> <p>Because <code>@import</code> needs to be defined at the top of the document it can be troublesome to let the CSS end up in the correct layer, but the import sytax filled that gap by allowing you to specify which layer you want to put the imported rules in. If you know how <code>@layer</code> works, you can probably tell that his layering system looks something like this:</p> <pre class="language-css"><!----><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> design</span> <span class="token punctuation">&#123;</span>
	<span class="token comment">/* ... */</span>

	<span class="token atrule"><span class="token rule">@layer</span> system</span> <span class="token punctuation">&#123;</span>
		<span class="token comment">/* ... */</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span>

<span class="token atrule"><span class="token rule">@layer</span> demo</span> <span class="token punctuation">&#123;</span>
	<span class="token comment">/* ... */</span>

	<span class="token atrule"><span class="token rule">@layer</span> support</span> <span class="token punctuation">&#123;</span>
		<span class="token comment">/* ... */</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>These were all named layers, but you can also import into an anonymous layer. It is allowed to specify layers before imports, so you could also name you layers first and then specify your imports:</p> <pre class="language-css"><!----><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> design.system<span class="token punctuation">,</span> demo.support<span class="token punctuation">;</span></span>
<span class="token atrule"><span class="token rule">@import</span> <span class="token string">'https://unpkg.com/open-props'</span> <span class="token function">layer</span><span class="token punctuation">(</span>design.system<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="token atrule"><span class="token rule">@import</span> <span class="token string">'https://cookie-consent-stinks.com/bad-css-1.css'</span> layer<span class="token punctuation">;</span></span>
<span class="token atrule"><span class="token rule">@import</span> <span class="token string">'https://marketing-junk.com/bad-css-2.css'</span> layer<span class="token punctuation">;</span></span></code><!----></pre> <p>Now that last import’s rules will be assigned to a new, anonymous layer. The benefit here is that the bad CSS in each of these imports will be contained withing their own layers and don’t leak out to the other layers. This might save you a bunch of headaches down the road.</p> <h3 id="browser-support"><a href="#browser-support">Browser support</a></h3> <p>It seems like <a href="https://caniuse.com/mdn-css_at-rules_import_supports" rel="nofollow"><code>layer</code> browser support in imports</a> has been around for for over a year already. They probably picked this up right alongside developing initial <code>@layer</code> efforts.</p> <p>The <code>&lt;link></code> element <a href="https://twitter.com/bramus/status/1593376033725591552" rel="nofollow">does not (yet) support</a> importing into a layer, so if you really must add some 3rd party CSS into a layer, this is your bet. For now. Keep an eye on <a href="https://github.com/whatwg/html/issues/7540" rel="nofollow">this GitHub thread</a> if you want to know if and when support is coming.</p> <h2 id="import-conditions"><a href="#import-conditions">Import conditions</a></h2> <p>Just like how <code>@import</code> cannot be nested inside <code>@layer</code>, you also can’t conditionally load CSS by writing <code>@import</code> inside an <code>@supports</code> or <code>@media</code>. But you <strong>can</strong> conditionally load CSS by appending your supports or media query to the end of the import rule.</p> <h2 id="supports-in-import"><a href="#supports-in-import"><code>supports()</code> in <code>@import</code></a></h2> <p>You can append a <code>supports()</code> supports-condition to an import to only import the specific CSS in case the supports-condition is met. The spec has a pretty cool example where they load a fallback stylesheet in case the browser does not support flexbox.</p> <pre class="language-css"><!----><code class="language-css"><span class="token atrule"><span class="token rule">@import</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'fallback-layout.css'</span><span class="token punctuation">)</span></span> <span class="token function">supports</span><span class="token punctuation">(</span><span class="token keyword">not</span> <span class="token punctuation">(</span><span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>

<span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">)</span></span> <span class="token punctuation">&#123;</span>
	<span class="token comment">/* ... */</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>You could think of this import as this (although this is not valid and will not work):</p> <pre class="language-css"><!----><code class="language-css"><span class="token comment">/* Mental picture only, this does not work and is not valid CSS */</span>
<span class="token atrule"><span class="token rule">@supports</span> <span class="token keyword">not</span> <span class="token punctuation">(</span><span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">)</span></span> <span class="token punctuation">&#123;</span>
	<span class="token atrule"><span class="token rule">@import</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'fallback-layout.css'</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span></span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@import#importing_css_rules_conditional_on_feature_support" rel="nofollow">MDN goes a step beyond</a> that and loads this CSS only if <code>display: flex</code> is supported and <code>display: grid</code> is not:</p> <pre class="language-css"><!----><code class="language-css"><span class="token atrule"><span class="token rule">@import</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'flex-layout.css'</span><span class="token punctuation">)</span></span> <span class="token function">supports</span><span class="token punctuation">(</span><span class="token keyword">not</span> <span class="token punctuation">(</span><span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">)</span> <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span></code><!----></pre> <p>The supports-condition is worthy of it’s own blog post, because the amount of checks you can do there is absolutely wild. Check out <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@supports#examples" rel="nofollow">the examples over at MDN</a> if you want to see some really cool stuff.</p> <h3 id="browser-support-1"><a href="#browser-support-1">Browser support</a></h3> <p>It seems that <a href="https://caniuse.com/mdn-css_at-rules_import_supports" rel="nofollow">only Firefox has shipped support</a> for <code>supports()</code> in <code>@import</code> at the time of writing. And only two weeks ago. Sad trombone.</p> <h2 id="media-queries-in-import"><a href="#media-queries-in-import">Media queries in <code>@import</code></a></h2> <p>This is one that most developers might actually be familiar with: specifying a media query list to conditionally load CSS. Again some examples:</p> <pre class="language-css"><!----><code class="language-css"><span class="token atrule"><span class="token rule">@import</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'desktop.css'</span><span class="token punctuation">)</span></span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 1024px<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="token atrule"><span class="token rule">@import</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'print.css'</span><span class="token punctuation">)</span></span> <span class="token keyword">only</span> print<span class="token punctuation">;</span></span>
<span class="token atrule"><span class="token rule">@import</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'dark.css'</span><span class="token punctuation">)</span></span> screen <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">prefers-color-scheme</span><span class="token punctuation">:</span> dark<span class="token punctuation">)</span><span class="token punctuation">;</span></span></code><!----></pre> <p>Let’s make a mental model of this, as with the previous section:</p> <pre class="language-css"><!----><code class="language-css"><span class="token comment">/* Mental picture only, this does not work and is not valid CSS */</span>
<span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 1024px<span class="token punctuation">)</span></span> <span class="token punctuation">&#123;</span>
	<span class="token atrule"><span class="token rule">@import</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'desktop.css'</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span></span>
<span class="token punctuation">&#125;</span>

<span class="token atrule"><span class="token rule">@media</span> <span class="token keyword">only</span> print</span> <span class="token punctuation">&#123;</span>
	<span class="token atrule"><span class="token rule">@import</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'print.css'</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span></span>
<span class="token punctuation">&#125;</span>

<span class="token atrule"><span class="token rule">@media</span> screen <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">prefers-color-scheme</span><span class="token punctuation">:</span> dark<span class="token punctuation">)</span></span> <span class="token punctuation">&#123;</span>
	<span class="token atrule"><span class="token rule">@import</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'dark.css'</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span></span>
<span class="token punctuation">&#125;</span></code><!----></pre> <h3 id="browser-support-2"><a href="#browser-support-2">Browser support</a></h3> <p>I can’t find any notes for media query list as a separate thing to supported alognside <code>@import</code>, so I’m going to assume here that support has been around since the early days of CSS imports.</p> <h2 id="mixing-it-up"><a href="#mixing-it-up">Mixing it up</a></h2> <p>Now we’ve seen some pretty incredible features of <code>@import</code>, but we can combine all of them! I don’t know if anyone would ever need something like this, but I guess this is something you could do (but not saying you should):</p> <pre class="language-css"><!----><code class="language-css"><span class="token atrule"><span class="token rule">@import</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'desktop-fallback.css'</span><span class="token punctuation">)</span></span> <span class="token function">layer</span><span class="token punctuation">(</span>base.elements<span class="token punctuation">)</span> <span class="token function">supports</span><span class="token punctuation">(</span><span class="token keyword">not</span> <span class="token punctuation">(</span><span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">)</span> <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">only</span> screen <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 1024px<span class="token punctuation">)</span><span class="token punctuation">;</span></span>

<span class="token comment">/* Mental picture of the above import */</span>
<span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token keyword">not</span> <span class="token punctuation">(</span><span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">)</span> <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">&#123;</span>
	<span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token keyword">only</span> screen <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 1024px<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">&#123;</span>
		<span class="token atrule"><span class="token rule">@layer</span> base</span> <span class="token punctuation">&#123;</span>
			<span class="token atrule"><span class="token rule">@layer</span> element</span> <span class="token punctuation">&#123;</span>
				<span class="token comment">/* CSS of desktop-fallback.css here */</span>
			<span class="token punctuation">&#125;</span>
		<span class="token punctuation">&#125;</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>This example does not make a ton of sense, but I bet there are some real-world scenarios that we could solve with clever combinations of import conditions and layers. Even more so with the addition of <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@supports#function_syntax" rel="nofollow">more <code>supports()</code> capabilities</a> and added <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media#media_features" rel="nofollow">media features</a>.</p> <hr/> <p>See!? I told you! This at-rule is full of good stuff. After diving into this I’m mostly left with questions though.</p> <ul><li>Does anyone actually use this? At what scale?</li> <li>Do modern CSS parsers support all these new conditions and layers?</li> <li>Will browsers pick up support for <code>supports()</code>?</li> <li>What would be a good trigger for us to start using <code>@import</code> again. There’s probably a way to mitigate some of the <a href="https://gtmetrix.com/avoid-css-import.html" rel="nofollow">performance drawbacks</a>, right?</li> <li><del>I remember seeing a GitHub thread of some CSS working group around adding support for <code>layer</code> and <code>supports</code> in the <code>&lt;link></code> element, but I can’t seem to find the relevant issue. If you know where it is, please send me a message, because I think that really fits the theme here as well.</del> Thanks for <a href="https://twitter.com/bramus/status/1593376033725591552" rel="nofollow">tweeting</a> Bramus and Barry!</li></ul> <p>Some of this is just more research material that I haven’t got to yet. I’d love to know more about <code>@import</code>, so I encourage you to <a href="mailto:bart@projectwallace.com?subject=CSS%20at-import">mail</a> or <a href="https://twitter.com/projectwallace" rel="nofollow">tweet</a> me some good reading material.</p><!--]-->]]></content:encoded>
          <pubDate>Fri, 21 Jul 2023 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/css-day-2023-takeaways</guid>
          <link>https://www.projectwallace.com/blog/css-day-2023-takeaways?utm_source=rss</link>
          <title><![CDATA[CSS Day 2023 takeaways]]></title>
          <description><![CDATA[CSS Day 2023 was once again an amazing conference! So many new things coming to means that we need to look at them for this website too.]]></description>
          <content:encoded><![CDATA[<!--[--><p>Imagine a conference completely dedicated to CSS. And a former church filled with over 300 CSS enthusiasts. It exists! It’s the amazing <a href="https://cssday.nl/2023" rel="nofollow">CSS Day conference</a> and I was lucky enough to attend again this year. During the talks I’ve been taking notes to see <strong>what all the new trends and techniques mean for authoring and auditing CSS</strong>. Note: while it may seem skeptical at many points I do think we’re living in a golden era of CSS as Una proved in <a href="https://www.youtube.com/watch?v=TVkwiUFbtyQ" rel="nofollow">the opening talk</a> this year.</p> <h2 id="contents"><a href="#contents">Contents</a></h2> <ol><li><a href="#css-nesting">CSS Nesting</a></li> <li><a href="#css-cascade-layers">CSS Cascade Layers</a></li> <li><a href="#css-container-queries">CSS Container Queries</a></li> <li><a href="#scroll-driven-animations-and-the-shorthand-trash-fire">Scroll driven animations and the shorthand trash fire</a></li></ol> <h2 id="css-nesting"><a href="#css-nesting">CSS Nesting</a></h2> <p>CSS Nesting was mentioned in almost every single talk! Even though it doesn’t really seem to add any new capabilities to the language itself, everyone seems to agree that the syntactic sugar adds a lot of benefits to the authoring experience. It’s not just nesting selectors, but also nesting atrules like <code>@media</code> inside regular old CSSRules.</p> <p>This will change what CSS ships to the browser, because preprocessors (PostCSS, Sass) currently ‘unwrap’ nested selectors and make it plainly visible how much nesting authors are applying to their selectors. CSS Nesting will make it harder to debug which ‘resolved’ selector is being applied, so we need browser vendors to level up their devtools to make inspection of nested selectors easier.</p> <a href="/_app/immutable/assets/nested-selector-devtools.BGfbte6z.png"><img loading="lazy" decoding="async" width="1120" height="504" alt="Example 'unwrapping' in Chrome Devtools" src="/_app/immutable/assets/nested-selector-devtools.BGfbte6z.png"/></a> <p>In the example above you can see that Chrome Devtools (Chrome 114) are trying to add helpful context about the selector, but there’s multiple levels of nesting, so we don’t get to see the full resolved selector here, which is inconvenient.</p> <aside>Update 13-06-2023: Apparently <a href="https://twitter.com/bramus/status/1668711607222902785">the Chrome team is aware</a> of this inconvenience and they're looking into it.</aside> <p>Time will tell how much profit CSS nesting will bring us. Writing the CSS will become faster, but debugging it will become harder as it requires more tooling or mental gymnastics to surface the resolved CSS.</p> <aside><p>Project Wallace should definitely help you figure out the complexity of your CSS, even when CSS nesting is present. It will involve a structural overhaul of the core of our <a href="https://github.com/projectwallace/css-analyzer">CSS analyzer</a>, but I feel strongly about this and our analysis should be able to help calculate Selector and AtRule complexity, even in complex situations.</p></aside> <h2 id="css-cascade-layers"><a href="#css-cascade-layers">CSS Cascade Layers</a></h2> <p>Many talks mentioned CSS Cascade Layers as a good way to manage specificity and the cascade. And it is! It’s such a useful addition to CSS that will help us avoid overly complex selectors and resorting to <code>!important</code>.</p> <p>Layers can be nested, it’s even a very nice method to group certain parts of the CSS, like a sort of namespace. But, like CSS Nesting, it brings with it some difficulties in inspecting the final CSS. A declaration can be nested several layers deep, so to debug why certain properties are applied we need some sort of view of the resolved layer tree. Luckily, both Firefox and Chrome devtools are already on the case, but with different levels of completeness.</p> <figure><a href="/_app/immutable/assets/cascade-layers-devtools.CuuDh5yy.png"><img loading="lazy" decoding="async" width="2313" height="1314" alt="Firefox (v113) devtools showing the cascade layers involved" src="/_app/immutable/assets/cascade-layers-devtools.CuuDh5yy.png"/></a> <figcaption>Firefox (v113) devtools showing the cascade layers involved</figcaption></figure> <figure><a href="/_app/immutable/assets/cascade-layers-devtools2.Nd1-irxC.png"><img loading="lazy" decoding="async" width="1970" height="1494" alt="Chrome (v114) devtools showing the resolved layers as well as the resolved layer tree of all layers in the CSS." src="/_app/immutable/assets/cascade-layers-devtools2.Nd1-irxC.png"/></a> <figcaption>Chrome (v114) devtools showing the resolved layers as well as the resolved layer tree of all layers in the CSS.</figcaption></figure> <p>Chrome showing the full layer tree is something I very much appreciate, because it will help you visualize the high-level architecture of your CSS.</p> <aside><p>Wallace should add support for showing the layer tree as well and it's been something I've been thinking of for over a year now, because <a href="https://www.bram.us/">Bramus</a> showed <a href="https://slidr.io/bramus/the-css-cascade-a-deep-dive-2022-06-09-css-day#85" rel="noreferrer">this excellent visual</a> last year at the same conference (!).</p> <figure><a href="/_app/immutable/assets/cascade-layers-bramus-visual.BbUmtGlQ.png"><img loading="lazy" decoding="async" width="2010" height="1184" alt="Screenshot of Bramus' slide of last year's talk about CSS cascade layers, showing a stacked bar chart of colord blocks, each representing a distinct CSS layer atrule, proportionally sized to the amount of rules/declarations inside them." src="/_app/immutable/assets/cascade-layers-bramus-visual.BbUmtGlQ.png"/></a> <figure>This visual has been on my mind all year and I hope we can bring it to this silly little website very soon!</figure></figure></aside> <h2 id="css-container-queries"><a href="#css-container-queries">CSS Container Queries</a></h2> <p>Container queries will change our code from mostly writing <code>@media</code> to writing <code>@container</code> to proportionally size our layouts and components. <a href="https://www.miriamsuzanne.com/" rel="nofollow">Miriam Suzanne</a> <a href="https://www.youtube.com/watch?v=-Fw8GSksUIo&amp;list=PLjnstNlepBvOG299LOrvMFJ8WreCDWWd4&amp;index=9" rel="nofollow">articulated quite well</a> that it is very likely that we’ll use <code>@container</code> for sizing elements and use <code>@media</code> to make decisions based on the OS level, like dark mode, and reduced motion.</p> <figure><a href="/_app/immutable/assets/container-queries-devtools.B4PxGBxU.png"><img loading="lazy" decoding="async" width="2872" height="1284" alt="Example Firefox devtools for container query" src="/_app/immutable/assets/container-queries-devtools.B4PxGBxU.png"/></a> <figcaption>Firefox devtools shows the actual size of the container element which makes for easy debugging, as well as showing the <code>container-type</code>, <code>container-name</code> and size query.</figcaption></figure> <figure><a href="/_app/immutable/assets/container-queries-devtools2.5VEXOfAs.png"><img loading="lazy" decoding="async" width="2880" height="1092" alt="Example Chrome devtools for container query" src="/_app/immutable/assets/container-queries-devtools2.5VEXOfAs.png"/></a> <figcaption>Chrome devtools shows the <code>container-name</code>, <code>container-type</code> and size query.</figcaption></figure> <p>It looks like Firefox is very much on top of the game here, although Chrome’s devtools are <em>perfectly</em> usable as well.</p> <aside><p>One thing I would add to our own analyzer is to write detection for unused <code>container-name</code>s and unknown named <code>@container</code>s. We warn for empty CSS Rules, so why not unused/undeclared container names?</p></aside> <h2 id="scroll-driven-animations-and-the-shorthand-trash-fire"><a href="#scroll-driven-animations-and-the-shorthand-trash-fire">Scroll driven animations and the shorthand trash fire</a></h2> <p>A couple of demos and talks showed off the amazing things we can build with Scroll Driven Animations. I’m not going to explain them here, so if you need examples you can check the ones from <a href="https://jhey.dev/cheep/rotating-gallery-with-css-scroll-driven-animations/" rel="nofollow">Jhey</a> or <a href="https://www.bram.us/2023/05/16/whats-new-in-web-animations/" rel="nofollow">Bramus</a>.</p> <p>One thing that stood out is that the new <code>animation-timeline</code>, <code>animation-composition</code> and <code>animation-range</code> properties <a href="https://twitter.com/bramus/status/1667916024107216897" rel="nofollow">cannot be combined</a> with the <code>animation</code>-shorthand property.</p> <pre class="language-css"><!----><code class="language-css"><span class="token comment">/* WARNING: will not work */</span>
<span class="token selector">#square</span> <span class="token punctuation">&#123;</span>
	<span class="token property">animation</span><span class="token punctuation">:</span> 3s demoAnimation alternate <span class="token function">scroll</span><span class="token punctuation">(</span>block nearest<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span>

<span class="token comment">/* This works */</span>
<span class="token selector">#square</span> <span class="token punctuation">&#123;</span>
	<span class="token property">animation-name</span><span class="token punctuation">:</span> demoAnimation<span class="token punctuation">;</span>
	<span class="token property">animation-duration</span><span class="token punctuation">:</span> 3s<span class="token punctuation">;</span>
	<span class="token property">animation-direction</span><span class="token punctuation">:</span> alternate<span class="token punctuation">;</span>
	<span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">scroll</span><span class="token punctuation">(</span>block nearest<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span>

<span class="token comment">/* Or, if you're a fan of shorthand, this works too */</span>
<span class="token selector">#square</span> <span class="token punctuation">&#123;</span>
	<span class="token property">animation</span><span class="token punctuation">:</span> 3s demoAnimation alternate<span class="token punctuation">;</span>
	<span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">scroll</span><span class="token punctuation">(</span>block nearest<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>Adding that extra property to the <code>animation</code> shorthand would have made it a bit difficult, if not impossible, to parse correctly. Or as Tab Atkins said it:</p> <blockquote><q>the animation shorthand is already a trash fire of parsing</q></blockquote> <p><a href="https://github.com/w3c/csswg-drafts/issues/8054#issuecomment-1476502277" rel="nofollow">CSS Working Group meeting notes, 20-03-2023</a></p> <aside><p>This problem strengthens my belief that <a href="https://csswizardry.com/2016/12/css-shorthand-syntax-considered-an-anti-pattern/">CSS shorthands are an anti-pattern</a> and should be avoided in most cases. Maybe we should even go so far as actively warning against them on this website as they make auditing your CSS more complex too. Anyway, this could easily be a blog post in itself…</p></aside> <hr/> <p>CSS is moving at rocket-speed pace and I’m a big fan of all the attention it’s getting from all browser vendors. There’s <a href="https://hacks.mozilla.org/2023/02/announcing-interop-2023/" rel="nofollow">more tools coming in our browsers on a daily basis</a> and I’m so grateful to all browser and devtools teams for their hard work.</p><!--]-->]]></content:encoded>
          <pubDate>Sun, 11 Jun 2023 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/tiny-css-formatter</guid>
          <link>https://www.projectwallace.com/blog/tiny-css-formatter?utm_source=rss</link>
          <title><![CDATA[Building a lightweight CSS formatter]]></title>
          <description><![CDATA[After using Prettier for a while it became apparent that both speed and bundle size were slowing down the CSS auditing process, so it's time to build a faster alternative.]]></description>
          <content:encoded><![CDATA[<!--[--><p>Last year we introduced a prettifier to this website because it’s one of those things you often want to do when auditing CSS. Then, not long after that, we added the option to prettify your CSS before analyzing it. Because we have a DevTools panel now on the analyzer page, it makes sense to view the CSS usage in a formatted manner. But this came at a cost: prettifying the CSS took up to twice as long analyzing the CSS! Time to look at a more performant alternative.</p> <figure><img alt="Example output of formatted CSS that we need to audit" src="/_app/immutable/assets/example.X4QeXMrb.png" loading="lazy" decoding="async" width="856" height="429"/> <figcaption>CSS that is being audited where the source code was originally minified, making the auditing process difficult, because those lines become pretty much unreadable.</figcaption></figure> <h2 id="the-naive-way-prettier"><a href="#the-naive-way-prettier">The naive way: Prettier</a></h2> <p>The first iteration of our prettifier used <a href="https://prettier.io/" rel="nofollow">Prettier</a>, a very popular and respectable project that can pretty-print hundreds of different languages. Even before testing, I was already pretty sure this was going to be a slow function call, so I went ahead and made sure to only import the relevant modules <em>and</em> to put the function in a WebWorker to run it off the UI thread.</p> <pre class="language-js"><!----><code class="language-js"><span class="token comment">// prettify-worker.js</span>
<span class="token keyword">import</span> prettier <span class="token keyword">from</span> <span class="token string">'prettier/esm/standalone.mjs'</span>
<span class="token keyword">import</span> cssParser <span class="token keyword">from</span> <span class="token string">'prettier/esm/parser-postcss.mjs'</span>

<span class="token comment">// The &#96;event&#96; here is the message we send from the UI to</span>
<span class="token comment">// the worker and &#96;event.data&#96; contains the string of CSS.</span>
<span class="token function-variable function">onmessage</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	<span class="token keyword">try</span> <span class="token punctuation">&#123;</span>
		<span class="token keyword">let</span> result <span class="token operator">=</span> prettier<span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>
			parser<span class="token operator">:</span> <span class="token string">'css'</span><span class="token punctuation">,</span>
			plugins<span class="token operator">:</span> <span class="token punctuation">[</span>cssParser<span class="token punctuation">]</span>
		<span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
		<span class="token comment">// Send result back to UI thread</span>
		<span class="token function">postMessage</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span>
	<span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
		<span class="token function">postMessage</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> error <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>This worked quite well, but after some weeks I noticed more often that the progress back on the analyzer page got stuck on <em>“prettifying CSS”</em> step. It’s not necessarily a bad thing, but if you analyze CSS as much as I do it becomes annoying after some time. And since prettifying CSS isn’t even our core business (if you can call it that), it’s even more frustrating.</p> <h2 id="csstree-to-the-rescue"><a href="#csstree-to-the-rescue">CSSTree to the rescue</a></h2> <p>After thinking about the problem for a while I realized that I could enlist the help of <a href="https://github.com/csstree/csstree" rel="nofollow">CSSTree</a> to do some of the work. The CSS Analyzer is based on CSSTree’s AST, so I know how the thing works and the dependency is already on the page, so no need to download more dependencies. Prettier + Postcss cost almost 340kB to download, which isn’t huge, but it would be nice if we could reduce that amount.</p> <p>So how do you turn a string of (potentially minified) CSS into a string of mostly readable CSS with CSSTree? Let’s start by parsing the CSS, so we get an AST to work with. Then, using that AST, we apply our knowledge of CSS structure to turn them into readable strings, line by line.</p> <h3 id="fast-css-parsing"><a href="#fast-css-parsing">Fast CSS parsing</a></h3> <p>CSSTree has some neat parsing options to speed things up a bit. It allows you to skip certain tokens, which will reduce memory usage and all that. The following script creates an AST of our CSS. An example of such an AST can be inspected on <a href="https://astexplorer.net/#/gist/0619055be1fcbec410702a63db37806f/59bddd15b3446ba5b4bb5878647c6584b4feb2cf" rel="nofollow">ASTExplorer</a>.</p> <pre class="language-js"><!----><code class="language-js"><span class="token keyword">let</span> ast <span class="token operator">=</span> <span class="token function">parse</span><span class="token punctuation">(</span>css<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>
	positions<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
	parseAtrulePrelude<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
	parseCustomProperty<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
	parseValue<span class="token operator">:</span> <span class="token boolean">false</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">)</span></code><!----></pre> <p>We can skip parsing Atrule preludes, custom properties and values because we’re only interested in their ‘raw’ string values, not the deeper tokens within them. We do need <code>positions</code> because this will allow us to do a lot of <code>css.substring(x, y)</code> later on.</p> <h2 id="creating-a-readable-string-of-css"><a href="#creating-a-readable-string-of-css">Creating a readable string of CSS</a></h2> <p>With the AST in hand, we can begin thinking about how to turn it into pretty-looking CSS. We need the CSS to be pretty enough to show it in a readable way in our DevTools, not any fancier than that. After some thinking, I came up with the following rules:</p> <ol><li>Every <strong>AtRule</strong> starts on a new line</li> <li>Every <strong>Rule</strong> starts on a new line</li> <li>Every <strong>Selector</strong> starts on a new line</li> <li>A comma is placed after every <strong>Selector</strong> that’s not the last in the <strong>SelectorList</strong></li> <li>Every <strong>Block</strong> (<code>{}</code>) is indented with 1 tab more than the previous indentation level</li> <li>Every <strong>Declaration</strong> starts on a new line</li> <li>Every <strong>Declaration</strong> ends with a semicolon (<code>;</code>)</li> <li>An empty line is placed after a <strong>Block</strong> unless it’s the last in the surrounding block</li> <li>Unknown syntax is rendered as-is</li></ol> <p>As you can see from this list, we’re dealing with a very limited subset of CSS tokens here: Stylesheet, Atrule, Rule, SelectorList, Selector, Block and Declaration. We’re starting our formatting from the Stylesheet level:</p> <pre class="language-js"><!----><code class="language-js"><span class="token keyword">function</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token parameter">node<span class="token punctuation">,</span> indent_level <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> css</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	<span class="token keyword">let</span> buffer <span class="token operator">=</span> <span class="token string">''</span>

	<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> child <span class="token keyword">of</span> node<span class="token punctuation">.</span>children<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
		<span class="token keyword">if</span> <span class="token punctuation">(</span>child<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'Rule'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
			buffer <span class="token operator">+=</span> <span class="token function">print_rule</span><span class="token punctuation">(</span>child<span class="token punctuation">,</span> indent_level<span class="token punctuation">,</span> css<span class="token punctuation">)</span>
			buffer <span class="token operator">+=</span> <span class="token string">'&#92;n'</span>
		<span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>child<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'Atrule'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
			buffer <span class="token operator">+=</span> <span class="token function">print_atrule</span><span class="token punctuation">(</span>child<span class="token punctuation">,</span> indent_level<span class="token punctuation">,</span> css<span class="token punctuation">)</span>
			buffer <span class="token operator">+=</span> <span class="token string">'&#92;n'</span>
		<span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>
			buffer <span class="token operator">+=</span> <span class="token function">print_unknown</span><span class="token punctuation">(</span>child<span class="token punctuation">,</span> indent_level<span class="token punctuation">,</span> css<span class="token punctuation">)</span>
		<span class="token punctuation">&#125;</span>
		buffer <span class="token operator">+=</span> <span class="token string">'&#92;n'</span>
	<span class="token punctuation">&#125;</span>

	<span class="token keyword">return</span> buffer
<span class="token punctuation">&#125;</span></code><!----></pre> <p>This kicks of our prettification, calling <code>print_atrule</code> and <code>print_rule</code>, which look like this:</p> <pre class="language-js"><!----><code class="language-js"><span class="token keyword">function</span> <span class="token function">print_atrule</span><span class="token punctuation">(</span><span class="token parameter">node<span class="token punctuation">,</span> indent_level<span class="token punctuation">,</span> css</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	<span class="token keyword">let</span> buffer <span class="token operator">=</span> <span class="token function">indent</span><span class="token punctuation">(</span>indent_level<span class="token punctuation">)</span>
	buffer <span class="token operator">+=</span> <span class="token string">'@'</span> <span class="token operator">+</span> node<span class="token punctuation">.</span>name

	<span class="token keyword">if</span> <span class="token punctuation">(</span>node<span class="token punctuation">.</span>prelude<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
		buffer <span class="token operator">+=</span> <span class="token string">' '</span> <span class="token operator">+</span> <span class="token function">substr</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>prelude<span class="token punctuation">,</span> css<span class="token punctuation">)</span>
	<span class="token punctuation">&#125;</span>
	<span class="token keyword">if</span> <span class="token punctuation">(</span>node<span class="token punctuation">.</span>block <span class="token operator">&amp;&amp;</span> node<span class="token punctuation">.</span>block<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'Block'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
		buffer <span class="token operator">+=</span> <span class="token function">print_block</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>block<span class="token punctuation">,</span> indent_level<span class="token punctuation">,</span> css<span class="token punctuation">)</span>
	<span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>
		<span class="token comment">// &#96;@import url(style.css);&#96; has no block, neither does &#96;@layer layer1;&#96;</span>
		buffer <span class="token operator">+=</span> <span class="token string">';'</span>
	<span class="token punctuation">&#125;</span>

	<span class="token keyword">return</span> buffer
<span class="token punctuation">&#125;</span>

<span class="token keyword">function</span> <span class="token function">print_rule</span><span class="token punctuation">(</span><span class="token parameter">node<span class="token punctuation">,</span> indent_level<span class="token punctuation">,</span> css</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	<span class="token keyword">let</span> buffer <span class="token operator">=</span> <span class="token string">''</span>

	<span class="token keyword">if</span> <span class="token punctuation">(</span>node<span class="token punctuation">.</span>prelude <span class="token operator">&amp;&amp;</span> node<span class="token punctuation">.</span>prelude<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'SelectorList'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
		buffer <span class="token operator">+=</span> <span class="token function">print_selectorlist</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>prelude<span class="token punctuation">,</span> indent_level<span class="token punctuation">,</span> css<span class="token punctuation">)</span>
	<span class="token punctuation">&#125;</span>

	<span class="token keyword">if</span> <span class="token punctuation">(</span>node<span class="token punctuation">.</span>block <span class="token operator">&amp;&amp;</span> node<span class="token punctuation">.</span>block<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'Block'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
		buffer <span class="token operator">+=</span> <span class="token function">print_block</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>block<span class="token punctuation">,</span> indent_level<span class="token punctuation">,</span> css<span class="token punctuation">)</span>
	<span class="token punctuation">&#125;</span>

	<span class="token keyword">return</span> buffer
<span class="token punctuation">&#125;</span></code><!----></pre> <p>And this goes on a bit for all the other types as well. There’s even some recursion in here because Atrules can be nested (<code>@media</code> in <code>@layer</code> and CSS nesting, to name a few), so we need to make sure to account for those as well.</p> <p>If you want to see more: the <a href="https://github.com/projectwallace/format-css/blob/main/index.js" rel="nofollow">source code</a> for this can be found <a href="https://github.com/projectwallace/format-css" rel="nofollow">on GitHub</a>, where I’m currently in the process of making this a standalone NPM package.</p> <h2 id="tradeoffs"><a href="#tradeoffs">Tradeoffs</a></h2> <p>No project is perfect and neither is this little script. There are some things that it does well and in a simple fashion, but some things I’ll consider beyond the scope and necessity of this package. And that’s ok because we just need it to format your CSS well enough to audit it easily.</p> <ul><li>✅ super fast (>90% faster than Prettier)</li> <li>✅ ‘tiny’ bundle size (~99% smaller than Prettier)</li> <li>✅ prettifies well enough for our use cases</li> <li>🔸 if your source CSS renders things multi-line (like long selectors or values), they’ll stay multi-line (fine, I guess)</li> <li>🔺 not as configurable and extensive as Prettier</li></ul> <p>These are tradeoffs I can live with.</p><!--]-->]]></content:encoded>
          <pubDate>Wed, 07 Jun 2023 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/making-analyze-css-render-6x-faster</guid>
          <link>https://www.projectwallace.com/blog/making-analyze-css-render-6x-faster?utm_source=rss</link>
          <title><![CDATA[Making Analyze CSS render 6 times faster]]></title>
          <description><![CDATA[A deep-dive in how the Analyze CSS page renders 6 times faster by applying 2 basic principles.]]></description>
          <content:encoded><![CDATA[<!--[--><p>One of the main drivers for Project Wallace is performance. Not just analyzing the performance of CSS as you can see in our code quality tools, but also doing the work in a fast and efficient way. With all the extra analysis that we’ve added over the last year, the analyzer page started to become a little slow. Even though the analysis happens completely off the main thread using a WebWorker. But rendering the results on the page sometimes took longer than parsing and analyzing the CSS itself! So I set out to fix that and the result is that the CSS report now renders more than 6 times faster than before! Here’s what I found.</p> <p>TL;DR;</p> <ol><li>Render expensive components only when they are within the viewport</li> <li>Reduce the amount of DOM nodes</li></ol> <h2 id="setting-a-baseline"><a href="#setting-a-baseline">Setting a baseline</a></h2> <p>All test cases were run against the CSS of CNN.com, because it’s a large dataset (800+ kB), but also a pretty good representation of <em>real-world</em> CSS. Many websites out there are like CNN’s, with lots of unique colors, font-sizes, rules, everything.</p> <p>Before any changes were made, rendering report took about 1.6 seconds.</p> <a href="/_app/immutable/assets/devtools-before.DMS7jLVl.png"><img alt="Browser devtools showing the Performance panel, which indicates that rendering the report took 1.67 seconds" loading="lazy" decoding="async" fetchpriority="low" src="/_app/immutable/assets/devtools-before.DMS7jLVl.png" width="2880" height="1580"/></a> <h2 id="the-fixes"><a href="#the-fixes">The fixes</a></h2> <p>With this knowledge in mind, it’s time to take action.</p> <h3 id="lazily-rendering-all-scatterplots"><a href="#lazily-rendering-all-scatterplots">Lazily rendering all ScatterPlots</a></h3> <p>Zooming in shows that there is a single function (<code>$e</code>) that takes up almost 700ms to render. It turns out it’s one of the scatter plots that we use to show Ruleset and Selector complexity.</p> <a href="/_app/immutable/assets/scatterplot-flamegraph.CfNhQC-X.png"><img alt="Browser Performance devtools highlighting the Scatterplot function duration of 692ms" loading="lazy" decoding="async" fetchpriority="low" src="/_app/immutable/assets/scatterplot-flamegraph.CfNhQC-X.png" width="2880" height="1028"/></a> <p>Here’s the offending scatterplot:</p> <a href="/_app/immutable/assets/scatterplot.DbCpW07K.png"><img loading="lazy" decoding="async" fetchpriority="low" alt="The scatter plot that caused a major render slowdown" src="/_app/immutable/assets/scatterplot.DbCpW07K.png" width="2344" height="640"/></a> <p>And this is just one. There are four of these in the report, so this is a good starting point to start optimizing.</p> <p>Rendering all ScatterPlots at once turns out to be quite slow and here’s the catch: they’re not even shown immediately, because you have to scroll down to see them. So what if we only render the ScatterPlots when they’re inside the viewport? Support for IntersectionObservers is quite good these days, so it shouldn’t be too hard to implement.</p> <p>There’s a pretty nice <a href="https://github.com/maciekgrzybek/svelte-inview" rel="nofollow">Svelte package to use IntersectionOberserver</a>, so I installed it and tweaked the <code>ScatterPlot.svelte</code> code a little (abbreviated example):</p> <h4 id="scatterplotsvelte-before"><a href="#scatterplotsvelte-before"><code>ScatterPlot.svelte</code> before</a></h4> <pre class="language-svelte"><!----><code class="language-svelte"><span class="token comment">&lt;!-- ScatterPlote.svelte (before) --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
	<span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> Pancake <span class="token keyword">from</span> <span class="token string">'@sveltejs/pancake'</span>

	<span class="token comment">/** @type number[]*/</span>
	<span class="token keyword">export</span> <span class="token keyword">let</span> points

	<span class="token comment">/** @type number */</span>
	<span class="token keyword">export</span> <span class="token keyword">let</span> max
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>chart<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Pancake.Chart</span> <span class="token attr-name">x1=</span><span class="token language-javascript"><span class="token punctuation">&#123;</span><span class="token number">0</span><span class="token punctuation">&#125;</span></span> <span class="token attr-name">x2=</span><span class="token language-javascript"><span class="token punctuation">&#123;</span>points<span class="token punctuation">.</span>length<span class="token punctuation">&#125;</span></span> <span class="token attr-name">y1=</span><span class="token language-javascript"><span class="token punctuation">&#123;</span><span class="token number">0</span><span class="token punctuation">&#125;</span></span> <span class="token attr-name">y2=</span><span class="token language-javascript"><span class="token punctuation">&#123;</span>max<span class="token punctuation">&#125;</span></span><span class="token punctuation">></span></span>
		<span class="token language-javascript"><span class="token punctuation">&#123;</span>#<span class="token keyword">if</span> points<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">&#125;</span></span>
			<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Pancake.Svg</span><span class="token punctuation">></span></span>
				<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Pancake.SvgScatterplot</span> <span class="token attr-name">data=</span><span class="token language-javascript"><span class="token punctuation">&#123;</span>points<span class="token punctuation">&#125;</span></span> <span class="token attr-name"><span class="token namespace">let:</span>d</span><span class="token punctuation">></span></span>
					<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>dot<span class="token punctuation">"</span></span> <span class="token language-javascript"><span class="token punctuation">&#123;</span>d<span class="token punctuation">&#125;</span></span> <span class="token punctuation">/></span></span>
				<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Pancake.SvgScatterplot</span><span class="token punctuation">></span></span>
			<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Pancake.Svg</span><span class="token punctuation">></span></span>
		<span class="token language-javascript"><span class="token punctuation">&#123;</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">&#125;</span></span>
	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Pancake.Chart</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
	<span class="token selector">.chart</span> <span class="token punctuation">&#123;</span>
		<span class="token property">height</span><span class="token punctuation">:</span> 200px<span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code><!----></pre> <h4 id="scatterplotsvelte-after"><a href="#scatterplotsvelte-after"><code>ScatterPlot.svelte</code> after</a></h4> <pre class="language-svelte"><!----><code class="language-svelte"><span class="token comment">&lt;!-- ScatterPlote.svelte (after) --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
	<span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> Pancake <span class="token keyword">from</span> <span class="token string">'@sveltejs/pancake'</span>
	<span class="token keyword">import</span> <span class="token punctuation">&#123;</span> inview <span class="token punctuation">&#125;</span> <span class="token keyword">from</span> <span class="token string">'svelte-inview'</span>

	<span class="token comment">/** @type number[]*/</span>
	<span class="token keyword">export</span> <span class="token keyword">let</span> points

	<span class="token comment">/** @type number */</span>
	<span class="token keyword">export</span> <span class="token keyword">let</span> max

	<span class="token keyword">let</span> is_visible <span class="token operator">=</span> <span class="token boolean">false</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span>
	<span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>chart<span class="token punctuation">"</span></span>
	<span class="token attr-name"><span class="token namespace">use:</span>inview=</span><span class="token language-javascript"><span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> unobserveOnEnter<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span></span>
	<span class="token attr-name"><span class="token namespace">on:</span>inview_enter=</span><span class="token language-javascript"><span class="token punctuation">&#123;</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>is_visible <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span></span>
<span class="token punctuation">></span></span>
	<span class="token language-javascript"><span class="token punctuation">&#123;</span>#<span class="token keyword">if</span> is_visible<span class="token punctuation">&#125;</span></span>
		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Pancake.Chart</span> <span class="token attr-name">x1=</span><span class="token language-javascript"><span class="token punctuation">&#123;</span><span class="token number">0</span><span class="token punctuation">&#125;</span></span> <span class="token attr-name">x2=</span><span class="token language-javascript"><span class="token punctuation">&#123;</span>points<span class="token punctuation">.</span>length<span class="token punctuation">&#125;</span></span> <span class="token attr-name">y1=</span><span class="token language-javascript"><span class="token punctuation">&#123;</span><span class="token number">0</span><span class="token punctuation">&#125;</span></span> <span class="token attr-name">y2=</span><span class="token language-javascript"><span class="token punctuation">&#123;</span>max<span class="token punctuation">&#125;</span></span><span class="token punctuation">></span></span>
			<span class="token language-javascript"><span class="token punctuation">&#123;</span>#<span class="token keyword">if</span> points<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">&#125;</span></span>
				<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Pancake.Svg</span><span class="token punctuation">></span></span>
					<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Pancake.SvgScatterplot</span> <span class="token attr-name">data=</span><span class="token language-javascript"><span class="token punctuation">&#123;</span>points<span class="token punctuation">&#125;</span></span> <span class="token attr-name"><span class="token namespace">let:</span>d</span><span class="token punctuation">></span></span>
						<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>dot<span class="token punctuation">"</span></span> <span class="token language-javascript"><span class="token punctuation">&#123;</span>d<span class="token punctuation">&#125;</span></span> <span class="token punctuation">/></span></span>
					<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Pancake.SvgScatterplot</span><span class="token punctuation">></span></span>
				<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Pancake.Svg</span><span class="token punctuation">></span></span>
			<span class="token language-javascript"><span class="token punctuation">&#123;</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">&#125;</span></span>
		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Pancake.Chart</span><span class="token punctuation">></span></span>
	<span class="token language-javascript"><span class="token punctuation">&#123;</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">&#125;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
	<span class="token selector">.chart</span> <span class="token punctuation">&#123;</span>
		<span class="token property">height</span><span class="token punctuation">:</span> 200px<span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code><!----></pre> <p>What happened here?</p> <ol><li>Add the svelte-inview package and create a single local variable called <code>is_visible</code></li> <li>Make sure the chart element is being observed, so that we get a message when it’s scolled into view: <code>use:inview={{ unobserveOnEnter: true }}</code></li> <li>When the element scrolls into view, <code>is_visible</code> is set to <code>true</code>: <code>on:inview_enter={() => (is_visible = true)}</code></li> <li>Only when <code>is_visible</code> is true, the <a href="https://pancake-charts.surge.sh/" rel="nofollow">Pancake chart</a> is rendered.</li> <li>There is no layout shift, because the <code>.chart</code> class makes sure the element is always <code>200px</code> tall.</li></ol> <p>That’s it! Only 5 lines of code added, for instant benefit.</p> <h3 id="lazily-rendering-colors"><a href="#lazily-rendering-colors">Lazily rendering Colors</a></h3> <p>Somewhere down the page there are some colors lists. They’re basically a small <code>&lt;canvas></code> where we render all known colors too. For websites with many colors this can be quite intensive, so I gave this the same treatment as the ScatterPlots: only when the Colors section of the report is scrolled into view, these canvases are rendered.</p> <a href="/_app/immutable/assets/color-bar.DOH0tfhQ.png"><img loading="lazy" decoding="async" fetchpriority="low" alt="Example ColorBar with 1200+ colors" src="/_app/immutable/assets/color-bar.DOH0tfhQ.png" width="2356" height="470"/></a> <h3 id="reducing-dom-nodes"><a href="#reducing-dom-nodes">Reducing DOM nodes</a></h3> <p>One thing that is generally true for most performance-related work is the amount of DOM nodes on a page. Whether that’s for React, Svelte or even <a href="https://nolanlawson.com/2023/01/17/my-talk-on-css-runtime-performance/" rel="nofollow">CSS selector performance</a>. Many nodes make your app slow. In our case, the page has over 7200 DOM nodes, so we should remove a whole lot of them to make our page render faster.</p> <h4 id="lazily-rendering-colorlist"><a href="#lazily-rendering-colorlist">Lazily rendering ColorList</a></h4> <p>Similarly to how we lazily render the scatter plots, we also lazily render the ColorList as well. For CNN it contains a whopping 504 DOM nodes for 126 unique colors (4 DOM nodes per color), so that’s a huge list of items for Svelte to skip initially.</p> <a href="/_app/immutable/assets/color-list.XosqTzlf.png"><img loading="lazy" decoding="async" fetchpriority="low" alt="Excerpt of the CSS Colors List for CNN.com" src="/_app/immutable/assets/color-list.XosqTzlf.png" width="2314" height="1142"/></a> <h4 id="reducing-font-size-nodes"><a href="#reducing-font-size-nodes">Reducing font-size nodes</a></h4> <p>CNN.com has 319 unique font-sizes which is a lot, but unfortunately a pretty common amount.</p> <a href="/_app/immutable/assets/font-sizes.BUaMieYs.png"><img loading="lazy" decoding="async" fetchpriority="low" alt="Example Font Sizes list of CNN.com" src="/_app/immutable/assets/font-sizes.BUaMieYs.png"/></a> <p>This is the (stripped down) HTML we used to generate that list (before):</p> <pre class="language-svelte"><!----><code class="language-svelte"><span class="token comment">&lt;!-- FontSizes.svelte --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span>
	<span class="token each"><span class="token punctuation">&#123;</span><span class="token keyword">#each</span> <span class="token language-javascript">sizes </span><span class="token keyword">as</span> <span class="token language-javascript"><span class="token punctuation">[</span>value<span class="token punctuation">]</span> </span><span class="token language-javascript"><span class="token punctuation">(</span>value<span class="token punctuation">)</span></span><span class="token punctuation">&#125;</span></span>
		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>
			<span class="token comment">&lt;!-- useless div! --></span>
			<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span>
				<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name"><span class="token namespace">style:</span>font-size=</span><span class="token language-javascript"><span class="token punctuation">&#123;</span>value<span class="token punctuation">&#125;</span></span><span class="token punctuation">></span></span>AaBb<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
				<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>code</span><span class="token punctuation">></span></span><span class="token language-javascript"><span class="token punctuation">&#123;</span>value<span class="token punctuation">&#125;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>code</span><span class="token punctuation">></span></span>
			<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>
	<span class="token each"><span class="token punctuation">&#123;</span><span class="token keyword">/each</span><span class="token punctuation">&#125;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span></code><!----></pre> <p>It wasn’t obvious immediately, because the actual implementation has more classnames and attributes, but there is a completely unnecessary <code>&lt;div></code> in there, so I took it out while maintaining the same layout, which saves us 319 DOM nodes!</p> <h4 id="reducing-dom-nodes-in-showmore-components"><a href="#reducing-dom-nodes-in-showmore-components">Reducing DOM nodes in ShowMore components</a></h4> <p>To keep the report somewhat easy to digest, I’ve made a component that only shows the top ~15 items when there’s a long list of things. The properties-list and media queries are a good example of this. When there are many properties, like the 220 unique properties on CNN.com, we don’t want to scroll for ages to get to the next section, unless you intentionally want to view them all.</p> <a href="/_app/immutable/assets/properties.D0_u9J8u.png"><img loading="lazy" decoding="async" fetchpriority="low" alt="Example ShowMore components that shows the list has 220 items, but it shows only the first 15 and overlays a Show More button, which reveals the remaining items when clicked" src="/_app/immutable/assets/properties.D0_u9J8u.png" width="2320" height="1088"/></a> <p>Now, before our performance upgrade, we’d render all 220 items (× 6 DOM nodes = 1320 nodes!) and hide everything after the initial <code>400px</code> with <code>overflow: hidden</code>. What a waste! Time to be a little smarter with our rendering.</p> <p>The trick here is to use <a href="https://svelte.dev/docs#template-syntax-slot-slot-key-value" rel="nofollow">Svelte slot props</a>:</p> <pre class="language-svelte"><!----><code class="language-svelte"><span class="token comment">&lt;!-- ShowMore.svelte --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
	<span class="token keyword">let</span> status <span class="token operator">=</span> <span class="token string">'closed'</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>show-more<span class="token punctuation">"</span></span> <span class="token attr-name">data-status=</span><span class="token language-javascript"><span class="token punctuation">&#123;</span>status<span class="token punctuation">&#125;</span></span><span class="token punctuation">></span></span>
	<span class="token language-javascript"><span class="token punctuation">&#123;</span>#<span class="token keyword">if</span> status <span class="token operator">==</span> <span class="token string">'closed'</span><span class="token punctuation">&#125;</span></span>
		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>button<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">on:</span>click</span><span class="token attr-value"><span class="token punctuation">=</span>()&#125;</span><span class="token punctuation">></span></span>Show more<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span>
	<span class="token language-javascript"><span class="token punctuation">&#123;</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">&#125;</span></span>
	<span class="token comment">&lt;!-- pass &#96;status&#96; to the component using &lt;ShowMore> --></span>
	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token language-javascript"><span class="token punctuation">&#123;</span>status<span class="token punctuation">&#125;</span></span> <span class="token punctuation">/></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
	<span class="token selector">.show-more[data-status="closed"]</span> <span class="token punctuation">&#123;</span>
		<span class="token property">max-height</span><span class="token punctuation">:</span> 400px<span class="token punctuation">;</span>
		<span class="token property">overflow</span><span class="token punctuation">:</span> hidden<span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code><!----></pre> <pre class="language-svelte"><!----><code class="language-svelte"><span class="token comment">&lt;!-- Properties.svelte --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
	<span class="token keyword">export</span> <span class="token keyword">let</span> properties <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ShowMore</span> <span class="token attr-name"><span class="token namespace">let:</span>status</span><span class="token punctuation">></span></span>
	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>table</span><span class="token punctuation">></span></span>
		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>thead</span><span class="token punctuation">></span></span><span class="token comment">&lt;!-- etc.  --></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>thead</span><span class="token punctuation">></span></span>
		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tbody</span><span class="token punctuation">></span></span>
			<span class="token each"><span class="token punctuation">&#123;</span><span class="token keyword">#each</span> <span class="token language-javascript">properties<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> status <span class="token operator">===</span> <span class="token string">'closed'</span> <span class="token operator">?</span> <span class="token number">15</span> <span class="token operator">:</span> <span class="token number">Infinity</span><span class="token punctuation">)</span> </span><span class="token keyword">as</span> <span class="token language-javascript">property<span class="token punctuation">&#125;</span></span></span>
				<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tr</span><span class="token punctuation">></span></span><span class="token comment">&lt;!-- etc.  --></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tr</span><span class="token punctuation">></span></span>
			<span class="token each"><span class="token punctuation">&#123;</span><span class="token keyword">/each</span><span class="token punctuation">&#125;</span></span>
		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tbody</span><span class="token punctuation">></span></span>
	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>table</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ShowMore</span><span class="token punctuation">></span></span></code><!----></pre> <p>In the above (simplified) components, we see that <code>ShowMore</code> exposes the <code>status</code> value back to the consuming component (<code>Properties.svelte</code>), so that it knows whether it’s open or closed. When closed, we slice the properties array to only render 15 rows (× 6 DOM nodes = 90 nodes, versus 1320 before). When open, we render the full list by setting the end of slice to <code>Infinity</code>.</p> <h2 id="the-result"><a href="#the-result">The result</a></h2> <p>After these changes the Performance panel shows that a mere 262ms is needed to render the whole report, which is <strong>more than 6 times faster</strong> than before! The initial DOM node count has gone down from a whopping 7200+ to 3770, which is almost a 50% reduction! 🤯</p> <a href="/_app/immutable/assets/devtools-after.Ba8yCn14.png"><img loading="lazy" decoding="async" fetchpriority="low" alt="Devtools performance panel showing a block of 262ms to render the report to page" src="/_app/immutable/assets/devtools-after.Ba8yCn14.png" width="2844" height="984"/></a> <p>The image also shows two big blocks of Recalculate Style and Layout, taking up 150ms and 50ms. I have yet to find out what they mean, but I think it’s the document trying to paint the generated report to screen. The tall block on the left is what would previously take up all space and look at it now: it’s tiny in comparison.</p> <hr/> <h2 id="thank-you"><a href="#thank-you">Thank you</a></h2> <p>A big shoutout to <a href="https://twitter.com/TimVereecke" rel="nofollow">Tim Vereecke</a> and <a href="https://twitter.com/burntcustard" rel="nofollow">John</a> for helping me understand what’s going on! Your feedback really helped me fix the issues and motivated me to write this post. John also found some interesting pointers to potentially speed up Pancake.svelte, but let’s leave that for another time. 😉</p><!--]-->]]></content:encoded>
          <pubDate>Sat, 01 Apr 2023 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/analyzing-instagram-css</guid>
          <link>https://www.projectwallace.com/blog/analyzing-instagram-css?utm_source=rss</link>
          <title><![CDATA[A deep dive into the CSS of Instagram.com]]></title>
          <description><![CDATA[Let's have a look at the interesting parts of analyzing Instagram.com.]]></description>
          <content:encoded><![CDATA[<!--[--><p>Instagram.com ranks pretty high according to the Alexa top visited websites. This means that all the CSS from Instagram.com gets downloaded to millions of devices every day. Let’s have a look at the interesting parts of their CSS analysis.</p> <h2 id="first-impressions"><a href="#first-impressions">First impressions</a></h2> <table><tbody><tr><td>Filesize</td><td>543 kB</td></tr><tr><td>Source Lines of Code</td><td>27,992</td></tr><tr><td>Rules</td><td>10,314</td></tr><tr><td>Selectors</td><td>10,449</td></tr><tr><td>Declarations</td><td>16,235</td></tr></tbody></table> <p>Coming in at 543 kB, the overall CSS size is pretty substantial. It’s not as bad as some other large companies, but I am almost certain it could be way less. With the amount of visitors they have, making their CSS smaller would be a nice bandwidth-saving as well.</p> <p>Almost 30,000 <a href="/blog/counting-lines-of-code-in-css">Source Lines of Code</a>, with declarations making up for roughly 50% of that (16,235). There are 10,449 selectors, which means that the remaining ~4000 lines of code are hidden in at-rules! That will be an interesting one to look at later on in this post.</p> <h2 id="css-rules"><a href="#css-rules">CSS Rules</a></h2> <img loading="lazy" width="1214" alt="Ruleset sizes of Instagram.com" src="/_app/immutable/assets/rules.BlygKje0.png"/> <p>One of the first things that jumps out is a massive rule size of 550! Zooming in on that we see a rule with 2 selectors 548 declarations! This is not uncommon since the rise of CSS custom properties because lots of sites declare most of their ‘variables’ on the <code>:root</code> selector. 548 custom properties is just… more than usual. In the graph above this ruleset is visible as the top left dot.</p> <pre class="language-css"><!----><code class="language-css"><span class="token selector">:root,
.__ig-light-mode</span> <span class="token punctuation">&#123;</span>
	<span class="token property">--fds-black</span><span class="token punctuation">:</span> #000000<span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-05</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.05<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-10</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.1<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-15</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.15<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-20</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.2<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-30</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.3<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-40</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.4<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-50</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.5<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-60</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.6<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-80</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.8<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-blue-05</span><span class="token punctuation">:</span> #ecf3ff<span class="token punctuation">;</span>
	<span class="token property">--fds-blue-30</span><span class="token punctuation">:</span> #aac9ff<span class="token punctuation">;</span>
	<span class="token property">--fds-blue-40</span><span class="token punctuation">:</span> #77a7ff<span class="token punctuation">;</span>
	<span class="token property">--fds-blue-60</span><span class="token punctuation">:</span> #1877f2<span class="token punctuation">;</span>
	<span class="token property">--fds-blue-70</span><span class="token punctuation">:</span> #2851a3<span class="token punctuation">;</span>
	<span class="token property">--fds-blue-80</span><span class="token punctuation">:</span> #1d3c78<span class="token punctuation">;</span>
	<span class="token property">--fds-button-text</span><span class="token punctuation">:</span> #444950<span class="token punctuation">;</span>
	<span class="token property">--fds-comment-background</span><span class="token punctuation">:</span> #f2f3f5<span class="token punctuation">;</span>
	<span class="token comment">/* hundreds more, etc. */</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>Right after that one, the dark theme:</p> <pre class="language-css"><!----><code class="language-css"><span class="token selector">.__ig-dark-mode</span> <span class="token punctuation">&#123;</span>
	<span class="token property">--fds-black</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-05</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.05<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-10</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.1<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-15</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.15<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-20</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.2<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-30</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.3<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-40</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.4<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-50</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.5<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-60</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.6<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-black-alpha-80</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.8<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">--fds-blue-05</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span>
	<span class="token property">--fds-blue-30</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span>
	<span class="token comment">/* hundreds more */</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <h2 id="selectors"><a href="#selectors">Selectors</a></h2> <img loading="lazy" decoding="async" alt="Instagram.com Selector analysis on projectwallace.com" src="/_app/immutable/assets/complexity.Be1X6iDu.png"/> <p>A surprisingly good score for Instagram here: 11,573 selectors with 96.16% being unique. Most websites with such a vast amount of selectors suffer from a lot of duplication.</p> <p>On the complexity side of things, it’s looking good as well: less than 200 selectors have a higher complexity than 3, which is pretty good.</p> <p>The most surprising part for me was the specificity: over 10,000 selectors have specificity of either <code>[0,1,0]</code> or <code>[0,2,0]</code>. And even the maximum specificity (<code>[1,3,0]</code>) is way below the maximum of what many sites of this size have. Good job!</p> <h2 id="at-rules"><a href="#at-rules">At-rules</a></h2> <h3 id="media"><a href="#media"><code>@media</code></a></h3> <p>The first thing that jumps out when checking the at-rules are the <code>@media</code> queries. It’s not just the total of 1,600+, but the fact that there are 135 unique ones. 135 unique media queries! Pretty much all of them use <code>px</code> as units for <code>min-width</code> or <code>max-width</code> and most of them are really, really specific numbers. I wonder if they have some kind of system for that, or if they just eyeball the page layout and apply media queries where they see fit.</p> <figure><table><thead><tr><th>Query</th><th>Count</th></tr></thead><tbody><tr><td><code>(max-width: 767px)</code></td><td>340</td></tr><tr><td><code>(min-width: 1025px)</code></td><td>321</td></tr><tr><td><code>(min-width: 768px) and (max-width: 1024px)</code></td><td>319</td></tr><tr><td><code>(max-width: 899px)</code></td><td>52</td></tr><tr><td><code>(min-width: 900px)</code></td><td>20</td></tr><tr><td><code>(max-width: 900px)</code></td><td>14</td></tr><tr><td><code>(max-width: 905px)</code></td><td>3</td></tr></tbody></table> <figcaption>Excerpt from instagram.com's <code>@media</code> at-rules. Note the almost-but-not-quite-identical breakpoints around 900px.</figcaption></figure> <h3 id="keyframes"><a href="#keyframes"><code>@keyframes</code></a></h3> <p>Another interesting metric is the 140 unique <code>@keyframes</code> that are in Instagram’s CSS, which is a lot. Usually, this can be explained by the fact that many of the atrules are straight copies because of vendor prefixes in the atrule, like this:</p> <pre class="language-undefined"><!----><code class="language-undefined">@keyframes thing &#123;
	from &#123;&#125;
	to &#123;&#125;
&#125;

@-webkit-keyframes thing &#123;
	from &#123;&#125;
	to &#123;&#125;
&#125;</code><!----></pre> <p>… but this is not the case on instagram.com. There are truly 140 unique <code>@keyframes</code> rules, most of which have a random string as a name, so I’m happy that I’m not the one having to go debug that part of their CSS.</p> <figure><pre>@keyframes x104yahw-B
@keyframes x1078a53-B
@keyframes x13f3g3z-B
@keyframes x148q4be-B
@keyframes x166kt1m-B</pre> <figcaption>Excerpt from Instagram's 140 unique <code>@keyframes</code> with randomly generated names.</figcaption></figure> <h2 id="declarations"><a href="#declarations">Declarations</a></h2> <p>Only one thing worth mentioning here: there are only 167 <code>!important</code>s, which is really low compared to the total of 17,356 declarations. Less than 1% <code>!important</code> usage, that’s something that not many websites at scale can pull off!</p> <h2 id="properties"><a href="#properties">Properties</a></h2> <p>I think Instagram must have broken some kind of record here for having 548 unique custom properties. That’s just bonkers. Other than that, the top usage ones are the usual suspects, with <code>width</code>, <code>height</code>, <code>background-color</code> and <code>display</code> ranking high, just like most other websites.</p> <img alt="CSS properties on instagram.com as analyzed on projectwallace.com" loading="lazy" decoding="async" src="/_app/immutable/assets/properties.DkhWlPHn.png"/> <p>One thing that’s cool about checking someone else’s CSS is that you can learn a lot by doing it. Today I learned about <a href="https://udn.realityripple.com/docs/Archive/Web/CSS/-ms-scroll-rails" rel="nofollow"><code>-ms-scroll-rails</code></a> and <a href="https://udn.realityripple.com/docs/Archive/Web/CSS/-ms-scroll-chaining" rel="nofollow"><code>-ms-scroll-chaining</code></a>, which are obsolete properties, but apparently still used on today’s instagram.com.</p> <h2 id="values"><a href="#values">Values</a></h2> <h3 id="colors"><a href="#colors">Colors</a></h3> <img width="1171" alt="Project Wallace report on CSS colors on instagram.com" src="/_app/immutable/assets/colors.DFlUDtQt.png"/> <p><strong>539 unique</strong> colors. That just can’t be right. We’ve seen the vast amount of custom properties above, so I’m surprised that there are still so many unique colors. I’m afraid that there must be a part of Instagram that’s old and tucked away and not updated with the same level of care as the rest of the site.</p> <img loading="lazy" decoding="async" width="1162" alt="Project Wallace report on CSS color format usage on instagram.com" src="/_app/immutable/assets/color-formats.C371llry.png"/> <p>An interesting note about Instagram’s color format usage: they seem to use more different formats than most sites I’ve seen so far. I’m surprised that there are so many named colors in there.</p> <h3 id="font-families"><a href="#font-families">Font-families</a></h3> <p>Again, a huge list: 78 unique font-families (most sites stop at about ~20). Scrolling through that actually reveals that there’s probably a good reason for this amount because some of them have names like <code>'Fix for Mac Chrome 80'</code> and I think that <code>font-family</code> alone is worth a blog post on itself.</p> <hr/> <p>That’s it for now! I won’t go into more details about other things because A) this post is long enough as it is and B) most things are what you would expect from a website as big as instagram.com.</p> <p>I highly encourage you to check out the report yourself and see what you can take away from these analytics. Let me know what you think of this type of post! Did you like it? Was it deep enough? Or still too superficial? Ping me at <a href="https://twitter.com/bartveneman" rel="nofollow">@bartveneman</a>.</p> <aside><p>This post is the first part of a series of posts focusing on dissecting the CSS of popular websites. The goal is to see how big companies structure their CSS, what mistakes they make and what we can learn from their CSS architecture.</p></aside><!--]-->]]></content:encoded>
          <pubDate>Wed, 01 Mar 2023 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/whats-new-jan-2023</guid>
          <link>https://www.projectwallace.com/blog/whats-new-jan-2023?utm_source=rss</link>
          <title><![CDATA[What's new in Project Wallace: January 2023]]></title>
          <description><![CDATA[It's been a pretty busy month with lots of fixes and new features.]]></description>
          <content:encoded><![CDATA[<!--[--><p>It’s been a pretty busy month with lots of fixes and new features.</p> <h3 id="specificity-analyzer-page"><a href="#specificity-analyzer-page">Specificity Analyzer page</a></h3> <p>Checking the specificity of a CSS selector is just one of those tasks that any CSS engineer does a lot. I’ve added a quick <a href="/specificity-calculator">specificity calculator</a> page, based on <a href="https://github.com/bramus/specificity" rel="nofollow">Bramus’ excellent specificity package</a>.</p> <img src="/_app/immutable/assets/specificity-calculator.Bbb0xV_P.png" alt="Specificity calculator page showing the specificity for a selector" width="714" height="461" loading="lazy" decoding="async"/> <p>The page is nowhere near as good or complete as <a href="https://polypane.app/css-specificity-calculator/" rel="nofollow">the one from Polypane</a>, but Wallace aims to be a place where you can audit all of your CSS, so it just makes sense to have our own specificity analyzer here.</p> <h3 id="css-units-game"><a href="#css-units-game">CSS Units game</a></h3> <p>A recent <a href="https://twitter.com/argyleink/status/1611394407928070146" rel="nofollow">Tweet</a> from Adam Argyle got me thinking that it’d be fun to create a CSS-based game. So I created it! It’s pretty much a copy of <a href="https://codepen.io/plfstr/full/zYqQeRw" rel="nofollow">HTML memory test</a> that seemed pretty popular a while ago. The game is to remember (or guess!) all possible CSS units. There are 64 of them and I tapped out after 10 units…</p> <img src="/_app/immutable/assets/css-units-game.DGMAtn_L.png" alt="CSS Units game in progress, showing  a progress bar with 10 units guessed of 64 total" width="714" height="468" loading="lazy" decoding="async"/> <h3 id="showing-actuals-in-code-quality-analysis"><a href="#showing-actuals-in-code-quality-analysis">Showing actuals in Code Quality analysis</a></h3> <p>A common complaint about the CSS Code Quality report was that it could use some more details. While the current analyzer doesn’t provide those (yet, because I developed it in a hurry), I added more information to most panels related to selector complexity. Now you can see the actual average line plotted in your complexity chart, for example. That should clear things up a bit.</p> <img src="/_app/immutable/assets/code-quality-threshold.DM4RKoMn.png" alt="a part of the CSS Code Quality page showing a scatter plot with selector complexity, with a clear red line drawn at the average selector complexity" width="714" height="522" loading="lazy" decoding="async"/> <h2 id="css-analyzer-uses-bramusspecificity"><a href="#css-analyzer-uses-bramusspecificity">css-analyzer uses @bramus/specificity</a></h2> <p>After a long time of doing our own specificity analysis, I finally switched over to using <a href="https://github.com/bramus/specificity" rel="nofollow">a package</a> to do that. Why? Well, analyzing specificity isn’t exactly difficult, but there’s a lot of gotchas and edge cases. More and more I found myself copying test cases and bits of code from other places. After I spoke to Bramus last summer at CSS Day 2022 I dabbled at bit with <a href="https://github.com/projectwallace/css-analyzer/pull/270/files" rel="nofollow">implementing his package</a>. At first I was afraid of performance implications, but actually there’s hardly any difference between his and my own implementation. They’re both built on CSSTree anyway and the code was 90% similar. And now, I can benefit from his (and <a href="https://github.com/bramus/specificity/commit/973abdc2906752163af4416380decf05a1fc3459" rel="nofollow">my</a> <a href="https://github.com/bramus/specificity/commit/fd29effade145c3e4f3273fc5577a5b10aa229b4" rel="nofollow">own</a>) bug fixes to make sure Wallace’s specificity calculations are correct!</p><!--]-->]]></content:encoded>
          <pubDate>Tue, 31 Jan 2023 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/2022-in-review</guid>
          <link>https://www.projectwallace.com/blog/2022-in-review?utm_source=rss</link>
          <title><![CDATA[2022 in review]]></title>
          <description><![CDATA[2022 was a good year in many ways for Project Wallace. New features, in-depth blog posts and a steady stream of new CSS enthusiasts.]]></description>
          <content:encoded><![CDATA[<!--[--><p>My <a href="/blog/2021-review-in-numbers">review of 2021</a> generated lots of encouraging comments. Here’s what happened with Project Wallace in 2022:</p> <h2 id="notable-releases"><a href="#notable-releases">Notable releases</a></h2> <ul><li><a href="/css-code-quality">CSS Code Quality</a> is the biggest release in terms of complexity and amount of usage. Read more about it in <a href="/blog/new-online-css-code-quality-analyzer">the introduction post</a>.</li> <li>The <a href="/prettify-css">Online CSS Prettifier</a> is a tool that already exists on a lot of other websites, but I don’t like having to google my way out of this every time, so <a href="/blog/prettify-css-online">I decided</a> Project Wallace should have it’s own prettifier.</li> <li>The core <a href="https://github.com/projectwallace/css-analyzer" rel="nofollow">CSS Analyzer</a> (please star it on GitHub!) has seen 10 releases this year in total: <ul><li>Support for <a href="https://github.com/projectwallace/css-analyzer/pull/215" rel="nofollow">analyzing Embedded Content</a> in CSS, like base64 encoded fonts or images;</li> <li>Lots of <a href="http://browserhacks.com/" rel="nofollow">browserhacks</a> <a href="https://github.com/projectwallace/css-analyzer/pull/258" rel="nofollow">are now analyzed</a>, ranging from at-rules and selectors to properties and values;</li> <li>The relatively new <code>@layer</code> at-rule <a href="https://github.com/projectwallace/css-analyzer/pull/221" rel="nofollow">is now analyzed</a></li> <li><a href="https://github.com/projectwallace/css-analyzer/pull/217" rel="nofollow">Total bundle size went down a lot</a> because CSSTree now supports tree shaking!</li> <li>Lots of performance improvements and less overall memory usage, despite adding more and more features.</li></ul></li></ul> <h2 id="traffic"><a href="#traffic">Traffic</a></h2> <p>Although the amount of visitors seems to have doubled, the amount of pageviews has only increased slightly. Part of this is due to an error on my end where analytics was broken on the Projects side, but still it’s a remarkable difference in visitors vs. pageviews ratio.</p> <table><thead><tr><th></th><th>2020</th><th>2021</th><th>2022</th></tr></thead><tbody><tr><td>Visitors</td><td>6,510</td><td>10,942</td><td>20,702</td></tr><tr><td>Page views</td><td>24,767</td><td>40,256</td><td>47,031</td></tr></tbody></table> <p>I’m starting to think that there’s less ‘quality’ traffic to the site, and more one-off visitors. According to Fathom the bounce rate is slightly higher than last year, so I think there are more people coming for a handful of pages and then moving along with their day. At first sight this frustrated me a little, but on second thought I think that’s a good thing: people come here to accomplish a task and then get on with the work they intended to do. Project Wallace is not a social media platform, we don’t need high engagement, we need high quality tools to do our work.</p> <h2 id="popular-pages"><a href="#popular-pages">Popular pages</a></h2> <ul><li>The <a href="/analyze-css">CSS Analyzer</a> is still on top in terms of page views, partially thanks to <a href="https://coliss.com/articles/build-websites/operation/css/css-analyzer-by-projectwallace.html" rel="nofollow">coliss.com</a>, but the gap is becoming smaller with…</li> <li>The <a href="/css-code-quality">CSS Code Quality page</a> is catching up in terms of popularity quickly. It has been mentioned of several websites like <a href="https://habr.com/ru/company/htmlacademy/blog/677318/" rel="nofollow">habr.com</a>, <a href="http://kachibito.net/useful-resource/css-code-quality" rel="nofollow">kachibito.net</a> and many others.</li> <li>By far the best blog post ever I’ve written: <a href="/blog/css-complexity">CSS Complexity: it’s complicated</a> peaks at 6,000+ page views at the time of writing and it’s still being visited every single day.</li></ul> <h2 id="website-activity"><a href="#website-activity">Website activity</a></h2> <table><thead><tr><th></th><th>2020</th><th>2021</th><th>2022</th></tr></thead><tbody><tr><td>Analyze CSS (url)</td><td>742</td><td>3,161</td><td>6,379</td></tr><tr><td>Analyze CSS (raw input)</td><td>8</td><td>503</td><td>2,345</td></tr><tr><td>CSS Code Quality (url)</td><td>-</td><td>-</td><td>4,788</td></tr><tr><td>CSS Code Quality (raw input)</td><td>-</td><td>-</td><td>790</td></tr><tr><td>Scrape CSS</td><td>138</td><td>264</td><td>248</td></tr><tr><td>Prettify CSS</td><td>-</td><td>-</td><td>40</td></tr></tbody></table> <p>It’s amazing to see that there’s such an interest in CSS Code Quality tooling. The funny part is that I started writing it to help someone explain how the plain CSS analytics could be translated to ‘quality’. Apparently there are more people struggling with the raw output of their CSS analytics. And I get it. Having the data is one, but understanding what it means is something different.
The CSS Code Quality package is still very crude, so I’m planning on some additions to help explain some of the concepts using the actual CSS that was analyzed. That should give everyone (myself included!) a better understanding on why some rankings are under-performing.</p> <p>It’s no surprise that the CSS Prettifier isn’t used very often. What <em>does</em> surprise me is that the CSS Scraper is so much less popular than the rest. I expected more people to be in need of a tool like this. Perhaps this is because the link is not in the site header. Ultimately, everyone analyzing CSS by URL uses this feature under the hood, so the actual total is more in the 11,000+ range, which is bonkers.</p> <h2 id="ideas-for-the-new-year"><a href="#ideas-for-the-new-year">Ideas for the new year</a></h2> <ul><li>Update the CSS Analyzer to use <a href="https://github.com/bramus/specificity" rel="nofollow">Bramus’ Specificity calculator</a>. It’s more correct and saves me a bunch of work in maintaining;</li> <li>Adding a page to the website to quickly calculate specificity. There’s already <a href="https://isellsoap.github.io/specificity-visualizer/" rel="nofollow">a bunch</a> <a href="https://polypane.app/css-specificity-calculator/" rel="nofollow">of them</a> <a href="https://specificity.keegan.st/" rel="nofollow">out there</a>, but like the prettifier, I don’t like context switching;</li> <li>Improving the <a href="/css-code-quality">CSS Code Quality</a> page to use the analyzed CSS to explain the bits that are sub-optimal;</li> <li>Implementing lots of new analysis features, like native CSS nesting, reporting on color formats used and the most important: <a href="https://github.com/projectwallace/css-analyzer/issues/218" rel="nofollow">calculating total CSS complexity</a>!</li></ul> <h2 id="closing-thoughts"><a href="#closing-thoughts">Closing thoughts</a></h2> <p>I’m pretty happy with these numbers, but even more happy with the feedback that comes from the CSS community in many forms. A part of that community was at <a href="https://cssday.nl/2022/schedule" rel="nofollow">CSS Day Conference</a> this year and I was lucky to meet some inspiring people there, like <a href="https://twitter.com/bramus" rel="nofollow">Bramus</a> and <a href="https://twitter.com/pepelsbey_dev" rel="nofollow">Vadim</a>. Also, <a href="https://twitter.com/rdvornov" rel="nofollow">Roman</a> just keeps pushing CSSTree to the next level, which is incredibly generous and exciting.</p> <p>Their enthusiasm and generosity really help driving Project Wallace forward and I can’t wait to see what 2023 will bring us.</p> <p>Keep an eye on this little website!</p><!--]-->]]></content:encoded>
          <pubDate>Sun, 01 Jan 2023 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/prettify-css-online</guid>
          <link>https://www.projectwallace.com/blog/prettify-css-online?utm_source=rss</link>
          <title><![CDATA[A new online CSS prettifier]]></title>
          <description><![CDATA[There's a lot of places on the web already where you can prettify your CSS already, but here's why Project Wallace now also has it's own prettifier.]]></description>
          <content:encoded><![CDATA[<!--[--><p>It’s one of those common tasks that you do when auditing CSS: making minified CSS readable, to easily navigate it. There are so many existing websites out there that do this, so it may come as a surprise that you now can <a href="/prettify-css">prettify CSS online</a> at Project Wallace too. But the reason for this is simple: ProjectWallace.com is a place where developers can inspect and learn about their CSS, so apart from all the metrics that we generate we also make a human-readable version of your CSS for you. This means you don’t have to jump to another site to do this exact task, meaning you can complete your task faster. And that’s what you came here for. To get a job done.</p> <p>For now, the prettifier is a standalone page, but perhaps we’ll eventually add ‘prettify CSS’ buttons to other pages as well, like the <a href="/analyze-css">Analyze CSS</a> or <a href="/css-code-quality">CSS Code Quality</a> pages.</p> <p>This online CSS prettifier is <a href="https://prettier.io/docs/en/api.html" rel="nofollow">powered by Prettier</a> and runs in your browser, so our servers never see that CSS.</p><!--]-->]]></content:encoded>
          <pubDate>Wed, 09 Nov 2022 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/css-complexity</guid>
          <link>https://www.projectwallace.com/blog/css-complexity?utm_source=rss</link>
          <title><![CDATA[CSS complexity: it's complicated]]></title>
          <description><![CDATA[There's lots of places in CSS to have complexity, but we tend to focus on selectors most of the time. Let's have a look at other places too.]]></description>
          <content:encoded><![CDATA[<!--[--><p>Years ago I read a <a href="https://csswizardry.com/2015/04/cyclomatic-complexity-logic-in-css/" rel="nofollow">blog post by Harry Roberts about Cyclomatic Complexity in CSS</a>. You know, one of those classic Computer Science metrics that traditionally only classically trained engineers get to use. And for years this has me thinking that <strong>with CSS we also need a way to express cyclomatic complexity</strong>.</p> <p>There are many occasions where someone will audit the codebase and will want to know the overall state of the code, detect weak spots and find outliers. The proof of that in the many visitors on this very website that check their <a href="/analyze-css">CSS analytics</a> or <a href="/css-code-quality">Code Quality</a>. And to my knowledge there doesn’t exist a tool that give you hints about the complexity of your CSS and I am convinced that we need this.</p> <p>And then came <a href="https://www.bram.us/2022/06/28/the-css-cascade-a-deep-dive-2022-06-09-css-day/" rel="nofollow">Bramus at CSS Day 2022</a>, who told us that most CSS architecture solutions like BEM and ITCSS are primarily aimed at <em>selectors</em> and the struggle with the cascade. This has been stuck in my head ever since. It makes so much sense: selectors are usually the frustrating part of the <em>authoring</em> experience (opposed to the <em>debugging</em> experience). Once you have a specific enough selector, you keep adding declarations until it looks good.</p> <h2 id="selector-complexity-beyond-specificity"><a href="#selector-complexity-beyond-specificity">Selector complexity beyond specificity</a></h2> <p>Some recent additions to CSS have made it easier to deal with specificity issues, like <code>:where()</code> and <code>:is()</code>. Both give us the opportunity to write more complex selectors at the cost of little specificity added (or none, in the case of <code>:where()</code>). But they can make selectors <strong>more complex</strong>. Let’s take a look at these two examples:</p> <pre class="language-css"><!----><code class="language-css"><span class="token comment">/* specificity: 0,1,2 */</span>
<span class="token selector">:is(header, main, footer) p:hover</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span>

<span class="token comment">/* specificity: 0,1,2 */</span>
<span class="token selector">header p:hover</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span></code><!----></pre> <p><a href="https://polypane.app/css-specificity-calculator/#selector=%3Ais(header%2C%20main%2C%20footer)%20p%3Ahover%2C%20header%20p%3Ahover" rel="nofollow">Specificity for both selectors is <code>0,1,2</code></a>, but I’d argue that <strong>the first one is more complex</strong> than the second, because of the arguments in <code>:is()</code>. For me there’s at least 6 parts to this selector, instead of the 3 (2 + 1) that the specificity would hint at. This is one example of specificity being not enough to explain the complexity of a selector.</p> <h2 id="more-than-just-selector-complexity"><a href="#more-than-just-selector-complexity">More than just selector complexity</a></h2> <p>Rarely do we talk about the ‘hidden’ complexity in CSS. The complexity that becomes visible when <em>debugging</em>. This is where the order of things comes into play, as well as things like RuleSets being nested inside at-rules, like <code>@media</code> or <code>@supports</code> or soon <code>@container</code>, but also vendor prefixes and <a href="http://browserhacks.com/" rel="nofollow">browser hacks</a> being used in selectors, properties, values, at-rule conditions. And you probably didn’t author some of these, but they were added by Autoprefixer, PostCSS, Sass or ParcelCSS and now there’s even more code to dig through! Even <code>@layer</code> will not save us from all the nasty stuff that’s tucked away in these rules, because it will only help with controlling the cascade, which in itself is another form of adding complexity.</p> <p>My goal is to work on a complete list and implement all these into Project Wallace’s analyzer, so we can all inspect the things we’ve made. <a href="https://github.com/projectwallace/css-analyzer/issues/218" rel="nofollow">Here’s a list of things</a> I’m considering to add to CSS complexity:</p> <ul><li>Atrules <ul><li><code>@import</code> can include media queries, supports conditions and a layer: <code>@import url(reset-mobile.css) supports(not (display: flex) and screen, (min-width: 1000px) layer(reset);</code></li> <li><code>@supports</code> can contain vendor prefixes: <code>@supports (-webkit-appearance: none) {}</code></li> <li><code>@keyframes</code> can be vendor prefixed: <code>@-webkit-keyframes {}</code></li> <li><code>@media</code> can be a browserhack: <code>@media \\0 screen {}</code>, <code>@media screen\9 {}</code></li></ul></li> <li>Selectors <ul><li>Attribute/value selectors: <code>[type]</code>, <code>[type=text]</code></li> <li>Vendor prefixes: <code>-moz-any(p)</code></li> <li>Combinators: <code>.item ~ .sibling</code></li> <li>Forms: <code>input[type="tel"]:invalid:not(:focus):not(:placeholder-shown) + label</code></li></ul></li> <li>Declarations: <ul><li>Usage of <code>!important</code></li></ul></li> <li>Properties: <ul><li>Vendor prefixes: <code>-webkit-appearance</code></li> <li>Browserhacks: <code>*zoom</code></li> <li>Custom properties: <code>--brand-surface</code> (I’m actually still undecided if this should count towards complexity, so I’d love to hear your thoughts. Again, <a href="https://twitter.com/bramus/status/1363842359918800898" rel="nofollow">Bramus shows</a> us there’s definitely a complexity cost to pay in some circumstances and <a href="https://www.youtube.com/watch?v=ZuZizqDF4q8" rel="nofollow">Lea Verou’s talk</a> also highlighted that custom properties can be really powerful, but that power comes with a complexity cost)</li></ul></li> <li>Values <ul><li>Browserhacks: <code>10px !ie</code>, <code>green \9</code></li> <li>Vendor prefixes: <code>-webkit-linear-gradient()</code></li></ul></li></ul> <p>See? There are so many things! At first I thought it would be a little far fetched to start looking into these, but when I started working on selector complexity metrics a long time ago I noticed that many websites have plenty enough of any of these complexity issues going on. So I’m really looking forward to experimenting with this and what insights it will bring to us.</p> <p>On a closing note: I think Project Wallace is the most powerful tool to explore your CSS, but I also think that browser makers should step up their game here. Google Chrome had a nice start with their <a href="/blog/css-analytics-in-chrome-devtools">CSS Overview</a>, but I think the topic is still largely overlooked and developers are looking for ways to learn more about their CSS, beyond looking at the different colors and fonts.</p> <p><strong>Special thanks to <a href="https://www.bram.us/" rel="nofollow">Bramus van Damme</a> for proof-reading this post!</strong></p><!--]-->]]></content:encoded>
          <pubDate>Fri, 08 Jul 2022 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/new-online-css-code-quality-analyzer</guid>
          <link>https://www.projectwallace.com/blog/new-online-css-code-quality-analyzer?utm_source=rss</link>
          <title><![CDATA[New: CSS Code Quality analyzer]]></title>
          <description><![CDATA[It's like Lighthouse, but for CSS specifically.]]></description>
          <content:encoded><![CDATA[<!--[--><p><em>TL;DR: See your CSS Code Quality in numbers in a single glance on <a href="https://www.projectwallace.com/css-code-quality" rel="nofollow">our Online CSS Code Quality Calculator</a></em></p> <p>Ever since I started Project Wallace years ago, I’ve been missing something. Because every time you open a project or do a one-off analysis it’s difficult to see in a quick glance how you’re doing. Is it good? Is it bad? Is it really bad? Well, let’s scroll for a couple of seconds and try to make sense of what is shown on the page. If you understand most metrics, that is.</p> <p>Well, at the start of this year, <a href="https://twitter.com/projectwallace/status/1477687128087830533" rel="nofollow">it finally clicked</a>:</p> <blockquote><p>Starting off the new year with an attempt to write a CSS code quality tool… :sweat_smile:<br/><br/>Points deducted for complexity, performance anti-patterns etc etc.<br/><br/>Think @____lighthouse, but for CSS specifically.</p></blockquote> <p>The responses were quite good. A couple dozen likes, retweets and folks showing interest. It doesn’t seem much, but for an account as small as Project Wallace that’s a lot. That was enough to get me started and after a couple of evenings, it started to look pretty good. I decided to not polish it too much at this stage but to put it online and gather feedback to improve it.</p> <p>The basic idea is that you give us your CSS and we will come up with a code quality score that’s made up of 3 separate topics: maintainability, complexity and performance. Each topic’s score is the result of several tests that we run against your CSS. We deduct points for each anti-pattern we encounter. The end score will be what’s left of an initial 100 points for each topic.</p> <p><strong>So here it is: Project Wallace’s Code Quality Analyzer.</strong></p> <img src="/_app/immutable/assets/screenshot.CbCnIIQ9.png" alt="Example CSS Code Quality output from https://www.projectwallace.com/css-code-quality" loading="lazy" width="904" height="1282"/> <p>The score list shows each test and whether you’re scoring poor (red), ok (orange) or good (green).</p> <p>I hope you’ll enjoy this feature. Please let me know what you think of it and have fun checking your own website(s) and those of your frenemies ;)</p><!--]-->]]></content:encoded>
          <pubDate>Sat, 29 Jan 2022 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/2021-review-in-numbers</guid>
          <link>https://www.projectwallace.com/blog/2021-review-in-numbers?utm_source=rss</link>
          <title><![CDATA[2021 review in numbers]]></title>
          <description><![CDATA[First day of a new year! Let's have a look at some numbers from projectwallace.com of 2021.]]></description>
          <content:encoded><![CDATA[<!--[--><p>First day of a new year! Let’s have a look at some numbers of this silly little website from 2021.</p> <h2 id="traffic"><a href="#traffic">Traffic</a></h2> <p>During 2021 we’ve had <strong>10.9K visitors with a total of 40.3K page views!</strong> No idea what that means compared to other sites, but this is a very niche site so the numbers look good to me.</p> <p>Apparently a lot of people have <em>some form of interest</em> in the CSS of their website. That’s a sign of hope for me. Project Wallace is a great educational tool, whether you’re a seasoned developer or a new kid on the block. And having lots of folks around here means that many people are getting better at understanding the CSS they wrote.</p> <h3 id="important-referrers"><a href="#important-referrers">Important referrers</a></h3> <p>Pretty much all traffic on this website comes from a handful of blog posts on the internet. Most of these posts are either on Smashing Magazine or CSS Tricks. Some big spikes in traffic this year because of some posts gaining traction or a popular newsletter that was just sent. Let’s list the top ones:</p> <ul><li><strong>Smashing Magazine</strong> (3.6K views): <ul><li><a href="https://www.smashingmagazine.com/2021/03/css-auditing-tools/#project-wallace" rel="nofollow">CSS Auditing Tools</a> by <a href="https://twitter.com/smash_it_on" rel="nofollow">Iris Lješnjanin</a>: 2.4K views</li> <li><a href="https://www.smashingmagazine.com/2021/07/refactoring-css-introduction-part1/" rel="nofollow">Refactoring CSS: Introduction</a> by <a href="https://twitter.com/AdrianBeceDev" rel="nofollow">Adrian Bece</a>: 915 views</li></ul></li> <li><strong>CSS Tricks</strong> (2.3K views): <ul><li><a href="https://css-tricks.com/tools-for-auditing-css/" rel="nofollow">Tools for Auditing CSS</a> by <a href="https://twitter.com/malimirkeccita" rel="nofollow">Silvestar Bistrović</a>: 1.2K views</li> <li><a href="https://css-tricks.com/design-v18/" rel="nofollow">Design v18</a> by <a href="https://twitter.com/chriscoyier" rel="nofollow">Chris Coyier</a>: 530 views</li> <li>Several more newsletter pages and older blog posts that gained less traction.</li></ul></li> <li><strong>Google</strong>: 2K views</li> <li><strong>Twitter</strong>: 1.2K views</li> <li><strong>GitHub</strong>: 844 views</li> <li><strong>LinkedIn</strong>: 734 views</li> <li>A shoutout to the several visitors that keep coming back from specific Jira URL’s. I see you and I applaud the people who thought it would be useful to create a ticket for themselves or someone else to have a look at Project Wallace.</li></ul> <p>I am so grateful for any of these authors for mentioning this website with positive remarks. It’s so inspiring to see other people being enthusiastic about this space. Especially Chris Coyier holds a special place for me because he let me write <a href="https://css-tricks.com/in-search-of-a-stack-that-monitors-the-quality-and-complexity-of-css/" rel="nofollow">a piece on CSS Tricks about monitoring CSS quality</a>. Thanks Chris, you’re such an amazing person!</p> <h2 id="activity"><a href="#activity">Activity</a></h2> <p>Looking at the raw database numbers for projectwallace.com, combined with data from <a href="https://usefathom.com" rel="nofollow">Fathom Analytics</a>:</p> <ul><li><strong>3.2K analyze CSS via URL.</strong> That means entering a URL, a headless browser spins up in a serverless function, it scrapes the CSS, and Wallace analyzes that CSS online. In just a couple of seconds.</li> <li><strong>503 analyze CSS via direct input.</strong> Copy your CSS, paste it in the <code>&lt;textarea></code> on <a href="https://www.projectwallace.com/analyze-css" rel="nofollow">projectwallace.com/analyze-css</a>, hit the button. 503 times. That’s a lot.</li> <li><strong>264 times Get CSS.</strong> Sometimes you just need the <a href="https://www.projectwallace.com/get-css" rel="nofollow">CSS for a URL</a>. This does just that. Less popular than analyzing CSS, but apparently still useful.</li> <li><strong>340 users created.</strong> That’s the amount of people whow thought it might be useful to keep track of their CSS over time, instead of only having a one-time look only. Amazing number, if you ask me.</li> <li><strong>23 users deleted their account.</strong> And deleted means deleted forever. I can only keep count of the amount of deletions, because I don’t keep any record of anyone cancelling their account.</li> <li><strong>349 projects created.</strong> Slightly more than the amount of people signing up.</li> <li><strong>59 projects deleted.</strong> Didn’t expect that many, but I think folks on the free plan need new projects sometimes, so they need to delete their existing one. Maybe you should consider upgrading so you can have more projects?</li> <li><strong>3621 imports created.</strong> 2910 <a href="https://www.projectwallace.com/docs/recipes/automatically-push-css-with-webhook" rel="nofollow">via a webhook</a>, 244 via pasting CSS in a <code>&lt;textarea></code>, 467 via entering a URL.</li> <li><strong>184 login failures.</strong> Apparently, logging in is hard sometimes. No worries, we’re all struggling from time to time.</li></ul> <h2 id="conclusion"><a href="#conclusion">Conclusion</a></h2> <p>Frankly, it baffles me that so many people take an interest in breaking down their CSS and it’s really motivating to keep this project running.</p> <p>Bart</p><!--]-->]]></content:encoded>
          <pubDate>Sat, 01 Jan 2022 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/css-analyzer-v5-released</guid>
          <link>https://www.projectwallace.com/blog/css-analyzer-v5-released?utm_source=rss</link>
          <title><![CDATA[CSS Analyzer v5 released]]></title>
          <description><![CDATA[The core of everything that powers Project Wallace just got a big upgrade. And it's pretty good!]]></description>
          <content:encoded><![CDATA[<!--[--><p>With a few hours left before the end of the year I am happy to announce that <code>@projectwallace/css-analyzer</code> is now on version 5! It is a complete rewrite of the previous version. The TL;DR:</p> <ul><li>More metrics added</li> <li>Existing metrics are more detailed</li> <li>Less dependencies (faster installation)</li> <li>Runs faster</li> <li>Browser compatible</li></ul> <p>If you’re in a hurry, go grab it <a href="https://github.com/projectwallace/css-analyzer" rel="nofollow">on GitHub</a> or run <code>npm install @projectwallace/css-analyzer</code>.</p> <h2 id="new-features-"><a href="#new-features-">New features 🥳</a></h2> <ul><li>The most exciting feature of all: <strong>see where colors are defined!</strong> It’s cool to see all the unique colors in a list, but it’s even better to see that <code>background</code> uses 3 different colors, whereas <code>color</code> uses 12!</li> <li>Metrics like <em>selectors per rule</em> now have a full list detailing the amount of selectors/declarations per rule. This means you can now graph out these metrics and that’s exactly what I’m doing on <a href="/analyze-css">the online analyzer</a>;</li> <li>For many metrics I’ve added the <strong>mean, median, mode, minimum, maximum and sum</strong> of that metric. That means that you can now see what the most common specificity of a selector is, or the highest complexity of a selector, or the maximum amount of selectors in a single rule;</li> <li>You can now see all the <strong>CSS units</strong> that are used in the CSS. Whether they are <code>rem</code>, <code>px</code> or even <code>vmin</code>: it’s all visible now. And you can even see if <code>font-size</code> is using more <code>px</code> than <code>em</code> or that <code>transition-duration</code> only uses <code>s</code> instead of <code>ms</code> ;)</li></ul> <h2 id="migration-from-v4-to-v5-"><a href="#migration-from-v4-to-v5-">Migration from v4 to v5 🪜</a></h2> <ul><li>Drop support for Node 8 and 10 (see <a href="https://nodejs.org/en/about/releases/" rel="nofollow">Node.js releases</a>)</li> <li>Rename all metrics ending on <code>.share</code> to <code>.ratio</code> <br/>This is only a better word for the exact metric;</li> <li>Rename <code>stylesheets</code> to <code>stylesheet</code> <br/>Because we're always analyzing exactly one stylesheet;</li> <li>Remove <code>stylesheets.simplicity</code> <br/>This is now `rules.declarations.mean`</li> <li>Remove <code>stylesheets.cohesion</code> <br/>This is now `rules.selectors.mean`</li> <li>Remove <code>stylesheets.browserhacks.*</code>, <code>atrules.supports.browserhacks.*</code>, <code>atrules.mediaqueries.browserhacks.*</code>, <code>selectors.browserhacks.*</code>, <code>values.browserhacks.*</code> <br/>These are hard to maintain because they relies on _a lot_ of complex regexes.</li> <li>Remove <code>atrules.documents.*</code> <br/>This metric added very little value.</li> <li>Drop <code>atrules.namespace.*</code> <br/>This metric added very little value.</li> <li>Drop <code>atrules.page.*</code> <br/>This metric added very little value.</li> <li>Remove <code>selectors.js.*</code> <br/>This metric added very little value.</li> <li>Remove <code>values.total</code> <br/>This is the exact same value as `declarations.total`, so use that one instead;</li> <li>Remove <code>values.colors.duplicate.*</code></li> <li>Add <code>rules.selectors</code> mean/median/mode/etc</li> <li>Add <code>rules.declarations</code> mean/median/mode</li> <li>Add <code>selectors.specificity.*</code></li> <li>Add <code>selectors.complexity.*</code></li> <li>Add <code>atrules.keyframes.prefixed.*</code></li></ul> <h2 id="dependencies-"><a href="#dependencies-">Dependencies 📦</a></h2> <p>An upcoming trend in Node land is to have a closer look at the <code>node_modules</code> of your project. The running gag is to compare <code>node_modules</code> to a black hole, but the undertone is more serious: it has gotten out of hand and many projects face insane install times with all the environmental impact that comes along with that. To turn this trend, projects like <a href="https://twitter.com/sitnikcode/status/1471791895332499456" rel="nofollow">PostCSS</a> and <a href="https://twitter.com/IAmTrySound/status/1475600522572877829" rel="nofollow">Vite</a> have gone the route of closely inspecting their dependencies and replacing big libraries with smaller alternatives.</p> <p>For this project it meant getting rid of a whole list of my own dependencies, as well as swapping existing ones with smaller and faster alternatives.
The biggest change in dependencies is that all existing dependencies have turned into a single dependency: <code>css-tree</code>. <a href="https://github.com/csstree/csstree" rel="nofollow">CSSTree is amazing</a> and I am a big fan of the work of CSSTree’s author <a href="https://twitter.com/rdvornov" rel="nofollow">Roman Dvornov</a>.</p> <p>For devDependencies, I’ve replaced Ava with Uvu (by <a href="https://twitter.com/lukeed05" rel="nofollow">Luke Edwards</a>). <a href="https://github.com/lukeed/uvu" rel="nofollow">Uvu is insanely fast</a> and I find it easier to use than Ava because it doesn’t rely on magical global variables.</p> <h2 id="conclusion"><a href="#conclusion">Conclusion</a></h2> <p>Let’s hope this new version can serve the community well the next couple of years. The <a href="/blog/css-analyzer-now-postcss">previous version that ran on PostCSS was released in 2017</a>, so I hope there’s not another big rewrite for the next couple of years. Enjoy!</p> <p>Bart Veneman</p><!--]-->]]></content:encoded>
          <pubDate>Thu, 30 Dec 2021 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/new-online-css-analyzer</guid>
          <link>https://www.projectwallace.com/blog/new-online-css-analyzer?utm_source=rss</link>
          <title><![CDATA[New Online CSS Analyzer]]></title>
          <description><![CDATA[Project Wallace's online CSS analyzer got a facelift!]]></description>
          <content:encoded><![CDATA[<!--[--><p>For more than a year I’ve been working behind the scenes on a complete rewrite of the CSS analyzer that powers all of Project Wallace. Although it’s not in a production-ready state, I decided to take it for a test run on <a href="https://www.projectwallace.com/analyze-css" rel="nofollow">projectwallace.com’s online CSS analyzer</a>. And the results were staggering. It’s so much more stable than the previous version. This was one of the (many) design goals for the new version, but to see it work out in real life brought absolute joy.</p> <p>Apart from implementing this new analyzer, I thought it would also be a good time to give the analyzer page a complete overhaul. The new design focuses more on <strong>high-level graphs</strong> and average/median/maximum/minimum numbers for a lot of metrics. And of course, the good-old existing details like the color bar, font-size chart are also still there.</p> <p>Another new thing is that the analyzer now <strong>runs on your machine</strong> instead of running on a serverless function. Less HTTP roundtrips means better stability and improved performance.
Even low-end devices benefit from running the analyzer locally, because the <strong>analyzer runs in a WebWorker</strong> and doesn’t block the main JS thread while doing heavy calculations.</p> <p>Here’s a list of what you can expect from the new online CSS analyzer:</p> <ul><li>Charts for getting a quick overview of stylesheet complexity</li> <li>A chart for selector complexity</li> <li>More concise lists for font-sizes, colors, etc.</li> <li>The ability to sort colors, font-sizes, z-indexes, etc.</li> <li>Big reduction in errors; You’ll only get an error now if your site can’t be reached for scraping the CSS.</li></ul> <p>I really hope you enjoy the new analyzer as much as I do and if you have ideas or suggestions to improve it, let me know <a href="https://twitter.com/projectwallace" rel="nofollow">on Twitter via @projectwallace</a>!</p><!--]-->]]></content:encoded>
          <pubDate>Tue, 12 Oct 2021 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/writing-docs</guid>
          <link>https://www.projectwallace.com/blog/writing-docs?utm_source=rss</link>
          <title><![CDATA[Writing docs]]></title>
          <description><![CDATA[Documentation for all Project Wallace's metrics is absent, and I'm currently working to bring them up to speed.]]></description>
          <content:encoded><![CDATA[<!--[--><p>With COVID-19 raging across our planet, sales for Project Wallace have pretty much dried up. Hardly any new paying customers are showing up. And I get that. These are incredibly challenging times. This is probably not the time to make (even a tiny) investment in a tool like Wallace, that you don’t strictly need anyway. <em>This is why I have decided to move my attention to serving the developer community with writing documentation for all Project Wallace’s CSS metrics.</em></p> <h2 id="on-writing-docs"><a href="#on-writing-docs">On writing docs</a></h2> <p>There are so many different metrics in Project Wallace and there’s no good explanation for any of them to be found. This needs to change. To do that, I have created a <code>/docs</code> folder and copied every existing CSS metric and created a Markdown file for it. From here I have started writing documentation. For at least 130 Markdown files.</p> <h2 id="writing-documentation-that-someone-might-actually-want-to-read"><a href="#writing-documentation-that-someone-might-actually-want-to-read">Writing documentation that someone might actually want to read</a></h2> <p>Truth be told, I think the progress is pretty slow. I’ve been looking at many websites to figure out what makes documentation pages right. Websites like <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/time" rel="nofollow">Mozilla Developer Network</a> and <a href="https://tailwindcss.com/docs/container" rel="nofollow">TailwindCSS</a> make it look easy, but look at the details that go into these pages. Here’s a small list of things that I think they do a great job of documenting:</p> <ul><li>Code and usage examples.</li> <li>Versioning. It’s so useful to have a browser compatibility table or a note about some thing being deprecated or removed.</li> <li>Short paragraphs of explanations. No-one wants to plough through a pile of unreadable text.</li> <li>Beautiful design. These docs are pleasant to the eye.</li> <li>Linkable sub-headings. Don’t underestimate the power of being able to link to a heading far below the fold of a page. I love it when a heading is clickable and gives me the url of that exact heading so I can easily share it.</li> <li>Links to related content. It can be useful sometimes to read more about a certain topic on the same or a different website.</li> <li>A link to edit the given page directly on GitHub. This is what makes open source great, I think. There is no easier way to get started in <abbr title="Open Source Software">OSS</abbr> than to write or change some documentation.</li></ul> <p>Certainly I’m going to take a couple of these goodies and apply them to Wallace’s documentation pages!</p> <h2 id="the-way-forward"><a href="#the-way-forward">The way forward</a></h2> <p>This is where I just need to put in the effort and write all the pages. It’s quite the task I’ve set out for myself, but it helps to have a big chunky checklist in Basecamp and seeing a little progress almost every day. I’ll get there. Eventually.</p><!--]-->]]></content:encoded>
          <pubDate>Thu, 07 May 2020 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/extracting-css-from-webpage</guid>
          <link>https://www.projectwallace.com/blog/extracting-css-from-webpage?utm_source=rss</link>
          <title><![CDATA[How Project Wallace extracts all CSS from any webpage]]></title>
          <description><![CDATA[Extracting all CSS from a webpage involves more work than you might expect. Here's how Project Wallace does it.]]></description>
          <content:encoded><![CDATA[<!--[--><p><strong>TLDR; Getting all CSS from a webpage requires a couple of different methods and some filtering. Go straight to <a href="#the-algorithm">the summary</a> or <a href="https://github.com/bartveneman/extract-css-core" rel="nofollow">the GitHub repository</a>.</strong></p> <p>Project Wallace would be nowhere without the prior art of CSS Stats. They came up with <a href="https://github.com/cssstats/cssstats/tree/master/packages/get-css" rel="nofollow">get-css</a> and this got me started in figuring out how to scrape CSS myself. Their algorithm is as simple as it is genius.</p> <ul><li>Take all <code>&lt;link></code> tags from the page, go to it’s <code>href</code> and take the CSS</li> <li>Follow any <code>@import</code> rule and take it’s CSS</li> <li>Take all <code>&lt;style></code> tags from the page and take it’s CSS</li> <li>Follow any <code>@import</code> rule and take it’s CSS</li> <li>Combine all these chunks of CSS into a single piece.</li></ul> <h3 id="when-this-approach-doesnt-work"><a href="#when-this-approach-doesnt-work">When this approach doesn’t work</a></h3> <p>At the moment, they haven’t included a way to scrape inline styles. I don’t know whether that’s intentional or not. For pages that utilize CSS-in-JS, the above method will not work. For that we need a browser that is able to evaluate styles at runtime, and looking at <code>&lt;link></code>s and <code>&lt;style></code>s is not enough. And with the rise of usage of CSS-in-JS, it’s time for an improved version.</p> <h2 id="the-in-depth-way"><a href="#the-in-depth-way">The in-depth way</a></h2> <p>The <em>complicated</em> way of getting all CSS involves a (optionally headless) browser and three key ingredients:</p> <ul><li>The <a href="https://developers.google.com/web/tools/chrome-devtools/coverage/" rel="nofollow">CSS Coverage</a> <a href="https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#class-coverage" rel="nofollow">API</a> (available in Puppeteer, so available for Firefox and Chromium-based browsers).</li> <li>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/styleSheets" rel="nofollow">HTML StyleSheets API</a></li> <li>A plain old <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll" rel="nofollow"><code>document.querySelectorAll()</code></a></li></ul> <h3 id="the-algorithm"><a href="#the-algorithm">The algorithm</a></h3> <p>For the complete algorithm, check out <a href="https://github.com/bartveneman/extract-css-core/blob/d365e7f699fe12c71640af8f6dfa572f86d2e14b/src/index.js" rel="nofollow">the source code GitHub</a>. The short and readable version is something like this:</p> <ul><li>Start the CSS Coverage reporter</li> <li>Go to the webpage</li> <li>Stop the CSS Coverage reporter</li> <li>Get all CSS-in-JS and <code>&lt;style></code> tags with <code>document.styleSheets</code></li> <li>Get all inline styles with <code>document.querySelectorAll('[style]')</code></li> <li>Combine all chunks of CSS in a single chunk.</li></ul> <h3 id="coverage-api"><a href="#coverage-api">Coverage API</a></h3> <p>The CSS Coverage API gives us all <code>&lt;link></code> tag CSS (and their <code>@import</code>s). It also finds a lot of <code>&lt;style></code> CSS, but not the ones that were created with JavaScript, so we’re ignoring those.</p> <pre class="language-js"><!----><code class="language-js"><span class="token keyword">await</span> page<span class="token punctuation">.</span>coverage<span class="token punctuation">.</span><span class="token function">startCSSCoverage</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">await</span> page<span class="token punctuation">.</span><span class="token function">goto</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> waitUntil <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> coverage <span class="token operator">=</span> <span class="token keyword">await</span> page<span class="token punctuation">.</span>coverage<span class="token punctuation">.</span><span class="token function">stopCSSCoverage</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> links <span class="token operator">=</span> coverage
	<span class="token comment">// Filter out the &lt;style> tags that were found in the coverage</span>
	<span class="token comment">// report since we've conducted our own search for them.</span>
	<span class="token comment">// A coverage CSS item with the same url as the url of the page</span>
	<span class="token comment">// we requested is an indication that this was a &lt;style> tag</span>
	<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">entry</span><span class="token punctuation">)</span> <span class="token operator">=></span> entry<span class="token punctuation">.</span>url <span class="token operator">!==</span> url<span class="token punctuation">)</span>
	<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">entry</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">&#123;</span>
		href<span class="token operator">:</span> entry<span class="token punctuation">.</span>url<span class="token punctuation">,</span>
		css<span class="token operator">:</span> entry<span class="token punctuation">.</span>text<span class="token punctuation">,</span>
		type<span class="token operator">:</span> <span class="token string">'link-or-import'</span>
	<span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code><!----></pre> <p><a href="https://github.com/bartveneman/extract-css-core/blob/d365e7f699fe12c71640af8f6dfa572f86d2e14b/src/index.js#L88-L98" rel="nofollow">Source on GitHub</a></p> <h3 id="documentstylesheets-api"><a href="#documentstylesheets-api"><code>document.styleSheets</code> API</a></h3> <p>With <code>document.styleSheets</code> we have access to all <code>&lt;style></code> tags that were server rendered, client-side rendered and all CSS that was generated with <code>StyleSheet.insertRule()</code>, as used in many CSS-in-JS frameworks.</p> <pre class="language-js"><!----><code class="language-js"><span class="token comment">// Get all CSS generated with the CSSStyleSheet API</span>
<span class="token comment">// This is primarily for CSS-in-JS solutions</span>
<span class="token comment">// See: https://developer.mozilla.org/en-US/docs/Web/API/CSSRule/cssText</span>
<span class="token keyword">const</span> styleSheetsApiCss <span class="token operator">=</span> <span class="token keyword">await</span> page<span class="token punctuation">.</span><span class="token function">evaluate</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>
	<span class="token keyword">return</span> <span class="token punctuation">(</span>
		<span class="token punctuation">[</span><span class="token operator">...</span>document<span class="token punctuation">.</span>styleSheets<span class="token punctuation">]</span>
			<span class="token comment">// Only take the stylesheets without href, because those with href are</span>
			<span class="token comment">// &lt;link> tags, and we already tackled those with the Coverage API</span>
			<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">stylesheet</span><span class="token punctuation">)</span> <span class="token operator">=></span> stylesheet<span class="token punctuation">.</span>href <span class="token operator">===</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
			<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">stylesheet</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>
				<span class="token keyword">return</span> <span class="token punctuation">&#123;</span>
					type<span class="token operator">:</span> stylesheet<span class="token punctuation">.</span>ownerNode<span class="token punctuation">.</span>tagName<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
					href<span class="token operator">:</span> stylesheet<span class="token punctuation">.</span>href <span class="token operator">||</span> document<span class="token punctuation">.</span>location<span class="token punctuation">.</span>href<span class="token punctuation">,</span>
					css<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token operator">...</span>stylesheet<span class="token punctuation">.</span>cssRules<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">&#123;</span> cssText <span class="token punctuation">&#125;</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> cssText<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'&#92;n'</span><span class="token punctuation">)</span>
				<span class="token punctuation">&#125;</span>
			<span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
	<span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">)</span></code><!----></pre> <p><a href="https://github.com/bartveneman/extract-css-core/blob/afadea662233a4d865bc728cc0327c38d8aa1c63/src/index.js#L50-L65" rel="nofollow">Source on GitHub</a></p> <h3 id="inline-styles"><a href="#inline-styles">Inline styles</a></h3> <p>Now, this part may be a bit controversial, but I think it’s worth to look at inline styles as well as all the rest. It’s often overlooked, but many WordPress Themes, Magento plugins and other <em>Big Web Players©</em> utilize inline styles for their themes and plugins. There’s one catch, though. A CSS Rule consists of one or more selectors and zero or more declarations. The declarations are the ones present in the <code>style=""</code>, but there is no selector. That’s why I decided to give each individual block of inline styles it’s own <code>[x-extract-css-inline-style]</code> selector. This way, it’s possible to <em>count</em> the amount of inline style attributes after they were extracted from the page.</p> <pre class="language-js"><!----><code class="language-js"><span class="token comment">// Get all inline styles: &lt;element style=""></span>
<span class="token comment">// This creates a new CSSRule for every inline style</span>
<span class="token comment">// attribute it encounters.</span>
<span class="token comment">//</span>
<span class="token comment">// Example:</span>
<span class="token comment">//</span>
<span class="token comment">// HTML:</span>
<span class="token comment">//    &lt;h1 style="color: red;">Text&lt;/h1></span>
<span class="token comment">//</span>
<span class="token comment">// CSSRule:</span>
<span class="token comment">//    [x-extract-css-inline-style] &#123; color: red; &#125;</span>
<span class="token comment">//</span>
<span class="token keyword">const</span> inlineCssRules <span class="token operator">=</span> <span class="token keyword">await</span> page<span class="token punctuation">.</span><span class="token function">evaluate</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>
	<span class="token keyword">return</span> <span class="token punctuation">(</span>
		<span class="token punctuation">[</span><span class="token operator">...</span>document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'[style]'</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
			<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">element</span><span class="token punctuation">)</span> <span class="token operator">=></span> element<span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'style'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
			<span class="token comment">// Filter out empty style="" attributes</span>
			<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>Boolean<span class="token punctuation">)</span>
	<span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> inlineCss <span class="token operator">=</span> inlineCssRules
	<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">rule</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token template-string"><span class="token template-punctuation string">&#96;</span><span class="token string">[x-extract-css-inline-style] &#123; </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>rule<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> &#125;</span><span class="token template-punctuation string">&#96;</span></span><span class="token punctuation">)</span>
	<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">css</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">&#123;</span> type<span class="token operator">:</span> <span class="token string">'inline'</span><span class="token punctuation">,</span> href<span class="token operator">:</span> url<span class="token punctuation">,</span> css <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code><!----></pre> <p><a href="https://github.com/bartveneman/extract-css-core/blob/afadea662233a4d865bc728cc0327c38d8aa1c63/src/index.js#L67-L87" rel="nofollow">Source on GitHub</a></p> <h3 id="bringing-it-all-together"><a href="#bringing-it-all-together">Bringing it all together</a></h3> <p>The final step is to take the CSS of every step and merge that into one giant chunk of CSS:</p> <pre class="language-js"><!----><code class="language-js"><span class="token keyword">const</span> css <span class="token operator">=</span> links
	<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>styleSheetsApiCss<span class="token punctuation">)</span>
	<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>inlineCss<span class="token punctuation">)</span>
	<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">&#123;</span> css <span class="token punctuation">&#125;</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> css<span class="token punctuation">)</span>
	<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'&#92;n'</span><span class="token punctuation">)</span></code><!----></pre> <p>That’s it! A lot of work to get some CSS off of a page, but so far it’s the most reliable way I’ve found to do it.</p><!--]-->]]></content:encoded>
          <pubDate>Sun, 15 Mar 2020 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/css-analytics-in-chrome-devtools</guid>
          <link>https://www.projectwallace.com/blog/css-analytics-in-chrome-devtools?utm_source=rss</link>
          <title><![CDATA[CSS Analytics in Chrome DevTools]]></title>
          <description><![CDATA[The people at Chrome DevTools are joining the CSS Analytics game!]]></description>
          <content:encoded><![CDATA[<!--[--><p>The people at Chrome DevTools are joining the CSS Analytics game! <a href="https://twitter.com/csswizardry/status/1231940099992170496" rel="nofollow">Harry Roberts posted a tweet</a> showing a screen in Chrome that lists colors on a page, complex CSS selectors, and more.</p> <img loading="eager" src="/_app/immutable/assets/screenshot.C_5JgU9P.png" alt="Chrome DevTools showing built-in CSS Analytics" width="1040" height="857"/> <p>This is awesome. We need developers and designers to be more aware of the branding and complexity they create in their websites. And more players in this field means that there is an interest. If more people are going to show interest in these metrics, that means that <a href="https://justinjackson.ca/build" rel="nofollow">more people will be inclined</a> to use Project Wallace. 😁</p> <p>I am curious to see where the Chrome folks will take this new addition to their (already huge) ecosystem of devtools. They have a huge benefit over Project Wallace, because they have way more context over all HTML and CSS, whereas Project Wallace only does static analysis of the CSS. <a href="https://squidfunk.github.io/talks/css-cognitive-complexity/#/32" rel="nofollow">Martin Donath (@squidfunk) already gave a nice presentation about this concept</a>, and I think he and the DevTools team are on the right track. Good stuff!</p> <p>Now, if I could only find out how to enable this Chrome experiment…</p> <ins>Update 2020-03-03: replaced image.</ins><!--]-->]]></content:encoded>
          <pubDate>Tue, 25 Feb 2020 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/sorting-css-time</guid>
          <link>https://www.projectwallace.com/blog/sorting-css-time?utm_source=rss</link>
          <title><![CDATA[Sorting CSS <time> values]]></title>
          <description><![CDATA[Support for analyzing CSS animations and transitions was added recently, but to display that nicely, animation-durations need to be sorted. Let's dive into sorting time.]]></description>
          <content:encoded><![CDATA[<!--[--><p>In the <a href="https://www.projectwallace.com/blog/analyzing-animations" rel="nofollow">previous blog post</a> I announced that Project Wallace now supports analysis of <code>animation-duration</code> and <code>transition-duration</code>. One of the neat things about Project Wallace is that it always shows all your values sorted in a sensible way. For example, font-sizes are sorted from small to large, colors are sorted by hue, etc. So it only makes sense to sort CSS <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/time" rel="nofollow"><code>&lt;time></code> values</a> from short to long. Enter <a href="https://github.com/bartveneman/css-time-sort" rel="nofollow">css-time-sort</a>.</p> <h2 id="the-functional-requirements"><a href="#the-functional-requirements">The functional requirements</a></h2> <ul><li>Sort durations from short to long</li> <li>If a <code>ms</code> value is the same as a <code>s</code> value, sort the <code>ms</code> value first</li> <li>Be able to sort an array of <code>['1s', '200ms']</code> with the native <code>.sort()</code> method</li></ul> <p>With this relatively simple piece of code it’s possible to sort an array of CSS durations like so:</p> <pre class="language-js"><!----><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">&#123;</span> sortFn <span class="token punctuation">&#125;</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'css-time-sort'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> sorted <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'1s'</span><span class="token punctuation">,</span> <span class="token string">'200ms'</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>sortFn<span class="token punctuation">)</span>

<span class="token comment">// RESULT:</span>
<span class="token comment">// => ['200ms', '1s']</span></code><!----></pre> <p>Check out the <a href="https://github.com/bartveneman/css-time-sort" rel="nofollow">source code on GitHub</a>.</p><!--]-->]]></content:encoded>
          <pubDate>Sat, 18 Jan 2020 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/analyzing-animations</guid>
          <link>https://www.projectwallace.com/blog/analyzing-animations?utm_source=rss</link>
          <title><![CDATA[Analyzing CSS animations]]></title>
          <description><![CDATA[Project Wallace now supports analysis of CSS animation and transition durations and timing functions.]]></description>
          <content:encoded><![CDATA[<!--[--><p>Animations and transitions are becoming part of many design systems. In the early days of style guides it used to be enough to keep track of font-sizes and colors. But these days design systems have evolved into full-blown user experience documents and animations are now first-class citizens. That’s why Wallace now also analyzes animation-related properties. Let’s take a look at <a href="https://www.projectwallace.com/~teamwallace/project-wallace/animations" rel="nofollow">the new features</a>.</p> <h2 id="animation-and-transition-durations"><a href="#animation-and-transition-durations">Animation and transition durations</a></h2> <p>Most design systems with animations have standardized their <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/animation-duration" rel="nofollow">animation durations</a>. The animation-duration is the time it takes from the start of an animation until it has finished.</p> <pre class="language-css"><!----><code class="language-css"><span class="token selector">.example</span> <span class="token punctuation">&#123;</span>
	<span class="token comment">/* in seconds */</span>
	<span class="token property">animation-duration</span><span class="token punctuation">:</span> 2s<span class="token punctuation">;</span>
	<span class="token comment">/* in miliseconds */</span>
	<span class="token property">animation-duration</span><span class="token punctuation">:</span> 200ms<span class="token punctuation">;</span>

	<span class="token comment">/* with shorthand notation */</span>
	<span class="token property">animation</span><span class="token punctuation">:</span> 2s animationName<span class="token punctuation">;</span>
	<span class="token comment">/* with shorthand AND animation-delay */</span>
	<span class="token property">animation</span><span class="token punctuation">:</span> 2s animationName 1s linear<span class="token punctuation">;</span>

	<span class="token comment">/* with transition shorthand */</span>
	<span class="token property">transition</span><span class="token punctuation">:</span> color 200ms ease-in<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>Analysis result:</p> <pre class="language-json"><!----><code class="language-json"><span class="token punctuation">&#123;</span>
	<span class="token property">"values.animations.durations.total"</span><span class="token operator">:</span> <span class="token number">5</span><span class="token punctuation">,</span>
	<span class="token property">"values.animations.durations.totalUnique"</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span>
	<span class="token property">"values.animations.durations.unique"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
		<span class="token punctuation">&#123;</span>
			<span class="token property">"value"</span><span class="token operator">:</span> <span class="token string">"200ms"</span><span class="token punctuation">,</span>
			<span class="token property">"count"</span><span class="token operator">:</span> <span class="token number">2</span>
		<span class="token punctuation">&#125;</span><span class="token punctuation">,</span>
		<span class="token punctuation">&#123;</span>
			<span class="token property">"value"</span><span class="token operator">:</span> <span class="token string">"2s"</span><span class="token punctuation">,</span>
			<span class="token property">"count"</span><span class="token operator">:</span> <span class="token number">3</span>
		<span class="token punctuation">&#125;</span>
	<span class="token punctuation">]</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>The Wallace CSS Analyzer finds an animation-duration of <code>2s</code> or <code>200ms</code> for all these declarations. Note that Wallace also analyzes <code>transition</code>-related declarations.</p> <h2 id="animation-and-transition-timing-functions"><a href="#animation-and-transition-timing-functions">Animation and transition timing functions</a></h2> <p>Similar to analyzing animation-durations, the Wallace CSS Analyzer looks at animation and transition <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timing-function" rel="nofollow">timing functions</a>. Timing functions can be keywords like <code>linear</code>, or <code>ease-in-out</code>, but also <code>cubic-bezier(0, 1, 1, 0)</code> or even <code>steps(4, step-end)</code>. These timing functions add liveliness to your animations and a lot of studies have been done to optimize timing functions. So once you have found the perfect timing for your animation, it’s good to stick to it.</p> <pre class="language-css"><!----><code class="language-css"><span class="token selector">.example</span> <span class="token punctuation">&#123;</span>
	<span class="token property">transition</span><span class="token punctuation">:</span> background 0.2s ease-in-out<span class="token punctuation">;</span>
	<span class="token property">animation</span><span class="token punctuation">:</span> 2s animationName <span class="token function">steps</span><span class="token punctuation">(</span>4<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token property">animation-timing-function</span><span class="token punctuation">:</span> <span class="token function">cubic-bezier</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 1<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 1<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>Result:</p> <pre class="language-json"><!----><code class="language-json"><span class="token punctuation">&#123;</span>
	<span class="token property">"values.animations.timingFunctions.total"</span><span class="token operator">:</span> <span class="token number">5</span><span class="token punctuation">,</span>
	<span class="token property">"values.animations.timingFunctions.totalUnique"</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span>
	<span class="token property">"values.animations.timingFunctions.unique"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
		<span class="token punctuation">&#123;</span>
			<span class="token property">"value"</span><span class="token operator">:</span> <span class="token string">"cubic-bezier(0, 1, 0, 1)"</span><span class="token punctuation">,</span>
			<span class="token property">"count"</span><span class="token operator">:</span> <span class="token number">1</span>
		<span class="token punctuation">&#125;</span><span class="token punctuation">,</span>
		<span class="token punctuation">&#123;</span>
			<span class="token property">"value"</span><span class="token operator">:</span> <span class="token string">"ease-in-out"</span><span class="token punctuation">,</span>
			<span class="token property">"count"</span><span class="token operator">:</span> <span class="token number">1</span>
		<span class="token punctuation">&#125;</span><span class="token punctuation">,</span>
		<span class="token punctuation">&#123;</span>
			<span class="token property">"value"</span><span class="token operator">:</span> <span class="token string">"steps(4)"</span><span class="token punctuation">,</span>
			<span class="token property">"count"</span><span class="token operator">:</span> <span class="token number">1</span>
		<span class="token punctuation">&#125;</span>
	<span class="token punctuation">]</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <h2 id="next-steps"><a href="#next-steps">Next steps</a></h2> <p>These reports are also available in <a href="https://www.projectwallace.com" rel="nofollow">projectwallace.com</a>, <a href="https://github.com/bartveneman/wallace-cli" rel="nofollow">Wallace CLI</a> and <a href="https://github.com/bartveneman/constyble" rel="nofollow">Constyble</a>. What are you going to do with these reports? What scary values did you find? Let me know <a href="https://twitter.com/bartveneman" rel="nofollow">on Twitter</a>!</p><!--]-->]]></content:encoded>
          <pubDate>Mon, 23 Dec 2019 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/counting-lines-of-code-in-css</guid>
          <link>https://www.projectwallace.com/blog/counting-lines-of-code-in-css?utm_source=rss</link>
          <title><![CDATA[Counting Lines of Code in CSS]]></title>
          <description><![CDATA[Project Wallace introduces Lines Of Code for CSS. Compare projects or files based on the amount of lines of code, instead of file size or guesswork.]]></description>
          <content:encoded><![CDATA[<!--[--><p>After using Project Wallace for quite some time, I found it hard sometimes to compare projects. For example, Facebook.com’s CSS is way larger than that of Twitter.com, but that’s because it has some base64-encoded fonts inlined in their <code>@font-face</code> declarations and parts of either website aren’t minified. So what is the best option to compare project sizes at a glance? Other programming languages have figured this out before: the Lines Of Code metric.</p> <h2 id="what-is-lines-of-code"><a href="#what-is-lines-of-code">What is Lines Of Code</a></h2> <p><a href="https://en.wikipedia.org/wiki/Source_lines_of_code" rel="nofollow">From Wikipedia</a>:</p> <blockquote><p>Source lines of code (SLOC), also known as lines of code (LOC), is a software metric used to measure the size of a computer program by counting the number of lines in the text of the program’s source code.</p></blockquote> <p>So we use SLOC to determine the size of bunch of code. This works for CSS too! Let’s discuss LOC and SLOC in relation to CSS.</p> <h2 id="lines-of-code-for-css"><a href="#lines-of-code-for-css">Lines Of Code for CSS</a></h2> <p>Lines of Code in relation to CSS is basically all your CSS, split by newlines. It actually just is that for Project Wallace. Checkout <a href="https://github.com/projectwallace/css-analyzer/blob/f7e913594f041d07c0a6c4913f8fca614c2e5d23/src/analyzer/stylesheets/lines-of-code.js#L4" rel="nofollow">the source code</a> for that:</p> <pre class="language-js"><!----><code class="language-js"><span class="token keyword">const</span> splitLines <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'split-lines'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> totalLinesOfCode <span class="token operator">=</span> <span class="token function">splitLines</span><span class="token punctuation">(</span>rawCss<span class="token punctuation">)</span><span class="token punctuation">.</span>length</code><!----></pre> <p>Note that <a href="https://github.com/sindresorhus/split-lines" rel="nofollow"><code>splitLines()</code></a> is basically <code>string.split(/\r?\n/)</code>.</p> <h2 id="source-lines-of-code-for-css"><a href="#source-lines-of-code-for-css">Source Lines of Code for CSS</a></h2> <p>There are three things in CSS that are important to keep track of when counting lines:</p> <ul><li>At-Rules (@media queries, @font-face rules, @keyframes rules, etc.)</li> <li>Selectors (<code>#header</code>, <code>.some-list</code>, <code>h1 > span</code>)</li> <li>Declarations (<code>color: blue</code>)</li></ul> <p>You might wonder why rules aren’t counted here, but rules only consist of selectors and declarations.
The <a href="https://github.com/projectwallace/css-analyzer/blob/f7e913594f041d07c0a6c4913f8fca614c2e5d23/src/analyzer/stylesheets/lines-of-code.js#L5-L6" rel="nofollow">source code for counting Source Lines Of Code in CSS</a> is seemingly simple as well:</p> <pre class="language-js"><!----><code class="language-js"><span class="token keyword">const</span> totalSourceLinesOfCode <span class="token operator">=</span> atRules<span class="token punctuation">.</span>length <span class="token operator">+</span> selectors<span class="token punctuation">.</span>length <span class="token operator">+</span> declarations<span class="token punctuation">.</span>length</code><!----></pre> <p>Here is a practical example of counting Lines Of Code in CSS:</p> <pre class="language-css"><!----><code class="language-css"><span class="token comment">/* 2 Source Lines Of Code */</span>
<span class="token selector">.selector</span> <span class="token punctuation">&#123;</span>
	<span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span>

<span class="token comment">/* 4 Source Lines Of Code */</span>
<span class="token selector">.selectorA,
#selectorB</span> <span class="token punctuation">&#123;</span>
	<span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span>
	<span class="token property">border-radius</span><span class="token punctuation">:</span> 3px<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span>

<span class="token comment">/* 5 Source Lines of Code */</span>
<span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 400px<span class="token punctuation">)</span></span> <span class="token punctuation">&#123;</span>
	<span class="token selector">.selector,
	.another-selector</span> <span class="token punctuation">&#123;</span>
		<span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span>
		<span class="token property">background</span><span class="token punctuation">:</span> yellow<span class="token punctuation">;</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span>

<span class="token comment">/* 4 Source Lines of Code */</span>
<span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 400px<span class="token punctuation">)</span> <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">max-width</span><span class="token punctuation">:</span> 800px<span class="token punctuation">)</span></span> <span class="token punctuation">&#123;</span>
	<span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">)</span></span> <span class="token punctuation">&#123;</span>
		<span class="token selector">.deep-selector</span> <span class="token punctuation">&#123;</span>
			<span class="token property">color</span><span class="token punctuation">:</span> tomato<span class="token punctuation">;</span>
		<span class="token punctuation">&#125;</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <h2 id="closing-thoughts"><a href="#closing-thoughts">Closing thoughts</a></h2> <p>There is still a lot more counting to do, but with regards to Lines Of Code, this is pretty much it. The next step for <a href="https://github.com/projectwallace/css-analyzer" rel="nofollow">@projectwallace/css-analyzer</a> is to calculate the cyclomatic complexity for CSS. But that’s a whole different can of worms.</p><!--]-->]]></content:encoded>
          <pubDate>Tue, 20 Aug 2019 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/integrating-constyble-into-ci-workflow</guid>
          <link>https://www.projectwallace.com/blog/integrating-constyble-into-ci-workflow?utm_source=rss</link>
          <title><![CDATA[Integrating Constyble into your build process]]></title>
          <description><![CDATA[Integrating Constyble into your build process will help you automate complexity testing for CSS. This post explains how to do that.]]></description>
          <content:encoded><![CDATA[<!--[--><p>Integrating <a href="https://github.com/bartveneman/constyble" rel="nofollow">Constyble</a> into your build process will help you automate complexity testing for CSS. Depending on how you actually integrate, it will fail your build if some unexpected complexity is detected. And with the <a href="https://github.com/bartveneman/constyble/releases/tag/v1.1.0" rel="nofollow">latest release</a> error output is even more clear, so fixing those issues should be a lot easier. Let’s dive in.</p> <h2 id="constyble-configuration"><a href="#constyble-configuration">Constyble configuration</a></h2> <p>For Constyble to do its job, we need a config file and some CSS to run our test against. The CSS part should be easy. You probably already do some kind of processing your CSS into a final <code>.css</code> file. We use that CSS file to run our tests against. Constyble by itself does not know what to check, so we need to provide a configuration with thresholds. Here is a small piece of a configuration I use for Project Wallace itself:</p> <pre class="language-json"><!----><code class="language-json"><span class="token punctuation">&#123;</span>
	<span class="token property">"selectors.identifiers.average"</span><span class="token operator">:</span> <span class="token number">1.45</span><span class="token punctuation">,</span>
	<span class="token property">"values.colors.totalUnique"</span><span class="token operator">:</span> <span class="token number">25</span><span class="token punctuation">,</span>
	<span class="token property">"values.colors.unique"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
		<span class="token string">"hsl(0, 70%, 42%)"</span><span class="token punctuation">,</span>
		<span class="token string">"hsl(42, 100%, 55%)"</span><span class="token punctuation">,</span>
		<span class="token comment">//... [truncated] ...</span>
		<span class="token string">"hsl(0, 0%, 0%)"</span><span class="token punctuation">,</span>
		<span class="token string">"rgba(0, 0, 0, 0)"</span>
	<span class="token punctuation">]</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>In short, I expect the average selector to not contain more than 1.45 identifiers. And no more than 25 unique colors and no other colors than the ones I provided. <a href="https://github.com/bartveneman/constyble#usage" rel="nofollow">Check the Constyble readme</a> if you want to know more about setting up Constyble.</p> <h2 id="ci-configuration"><a href="#ci-configuration">CI configuration</a></h2> <p>For Constyble to become most effective, we need to run it after we’ve created a Pull Request. If someone makes a change to our CSS that increases the complexity, we need to prevent that change from being added to our codebase, or update our Constyle config accordingly.</p> <p>The ‘trick’ is to run Constyble in your NPM <code>test</code> script. It will look something like this <code>package.json</code>:</p> <pre class="language-json"><!----><code class="language-json"><span class="token punctuation">&#123;</span>
	<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"my-package-name"</span><span class="token punctuation">,</span>
	<span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>
		<span class="token property">"test"</span><span class="token operator">:</span> <span class="token string">"constyble styles/main.css"</span>
	<span class="token punctuation">&#125;</span><span class="token punctuation">,</span>
	<span class="token property">"devDependencies"</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>
		<span class="token property">"constyble"</span><span class="token operator">:</span> <span class="token string">"^1.1.0"</span>
	<span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>Here is <a href="https://github.com/projectwallace/constyble-continuous-integration-example" rel="nofollow">an example repository</a> that includes the example above. This example repo has an integration with <a href="https://travis-ci.com" rel="nofollow">Travis</a>, so that every push and Pull Request will run the <code>npm test</code> script to validate that CSS changes do not exceed the thresholds set in <code>.constyblerc</code>.</p> <p>That’s all there is to it. Do you find this a useful addition to your tool chain? Let me know <a href="https://twitter.com/bartveneman" rel="nofollow">via Twitter</a>!</p><!--]-->]]></content:encoded>
          <pubDate>Fri, 22 Mar 2019 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/privacy-by-default</guid>
          <link>https://www.projectwallace.com/blog/privacy-by-default?utm_source=rss</link>
          <title><![CDATA[Privacy by default]]></title>
          <description><![CDATA[A recent REWORK podcast episode triggered me thinking about user privacy and this post explains how we deal with privacy.]]></description>
          <content:encoded><![CDATA[<!--[--><p>TLDR; there’s no tracking on Project Wallace.</p> <p>A <a href="https://rework.fm/100-facebook-free/" rel="nofollow">recent episode</a> of the REWORK podcast triggered me to question the only client-side script that Project Wallace contains: Google Analytics. The folks at Basecamp had decided to become a <a href="https://m.signalvnoise.com/Become-A-Facebook-Free-Business/" rel="nofollow">100% Facebook-free business</a>. One of the reasons being that Facebook is more and more known for privacy-related scandals. It got me thinking about the privacy of my users.
I decided that there is no good reason to include a library like Google Analytics (GA) on Project Wallace.</p> <h2 id="reasons-to-remove-the-google-analytics-snippet"><a href="#reasons-to-remove-the-google-analytics-snippet">Reasons to remove the Google Analytics snippet</a></h2> <ul><li>I have no idea what Google does with the data it gathers, and it just doesn’t feel right that I cannot control how much data it gathers and shares.</li> <li>I’m no GA expert, and I don’t even know how to setup goals or advanced tracking, so all I do is including the default snippet and hoping that some day something smart can be seen.</li> <li>The snippet is only loaded if the browser did not have <a href="https://www.eff.org/issues/do-not-track" rel="nofollow">‘Do Not Track’ (DNT)</a> enabled, because I tried to be thoughtful of users’ privacy preferences. This leads to incomplete tracking results and might give misleading insights.</li> <li>The snippet isn’t loaded if you have any kind of tracking blocker. Again, leading to misleading results.</li> <li>Loading client-side JS is essentially making the site slower. The impact of a default GA snippet is pretty minimal, but still, it’s going to make the site load slower.</li></ul> <p>The GA snippet was <a href="https://twitter.com/projectwallace/status/1089994374350540800" rel="nofollow">removed on 28 January 2019</a>, making Project Wallace completely tracking-free. As a user of many other sites, I always hope that other site owners think really hard about the trackers they include in their pages. Is it really necessary? Can your business live without it? Are there other, less-invasive ways of gathering the data points you need to answer your questions?</p> <h2 id="default-privacy-on-project-wallace"><a href="#default-privacy-on-project-wallace">Default privacy on Project Wallace</a></h2> <ul><li>There are no tracking scripts. Actually, there are no client-side scripts <em>at all</em>, except on the pricing/payment page, because that’s <a href="https://stripe.com/docs/checkout" rel="nofollow">powered by Stripe</a>.</li> <li>Project Wallace only asks for a minimal amount of user data, like email and name. There’s nothing to steal or leak if you cannot give us any more data. So we won’t let you, except if you want to fill in your bio or relevant links in your profile.</li> <li>The only tracking that I can do is reading directly from the data storage and a <em>tiny</em> bit of logging around malicious requests, but that’s all. And it’s all I need.</li> <li>No data is shared with, or sold to anyone else. There never has been.</li></ul> <p>Anyway, I have decided that the data I need to improve Project Wallace will have to come from talking to the users of the platform. Luckily the <a href="https://twitter.com/projectwallace" rel="nofollow">@projectwallace</a> Twitter account is picking up steam quickly and conversations are starting to take place. That’ll do. Stay safe everyone!</p><!--]-->]]></content:encoded>
          <pubDate>Fri, 01 Mar 2019 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/the-css-tricks-redesign-in-numbers</guid>
          <link>https://www.projectwallace.com/blog/the-css-tricks-redesign-in-numbers?utm_source=rss</link>
          <title><![CDATA[The CSS Tricks 2019 redesign in numbers]]></title>
          <description><![CDATA[CSS Tricks got a fresh coat of paint and boy, does it look good! But what exactly changes in CSS statistics after such a big redesign?]]></description>
          <content:encoded><![CDATA[<!--[--><p>On the 1<sup>st</sup> of January <a href="https://css-tricks.com" rel="nofollow">CSS-Tricks.com</a> got a fresh coat of paint, and what a looker she is! Chris Coyier and his team have done an excellent job at delivering a stunning looking website. A redesign at such scale (CSS Tricks is no tiny website) is interesting to look at from a CSS analysis perspective, so let’s do that.</p> <p>(If you just want to take a look at the raw numbers: <a href="https://www.projectwallace.com/teamwallace/css-tricks/imports/20190101202756143" rel="nofollow">Compare CSS Tricks 30 Dec 2018 21:18:23 to 1 Jan 2019 20:27:56</a>)</p> <h2 id="a-quick-overview-of-numbers"><a href="#a-quick-overview-of-numbers">A quick overview of numbers</a></h2> <p>Here are some numbers that’ll tell us just how big this redesign has been. These are some stylesheet-wide numbers that tell something about the CSS in general.</p> <table><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Difference</th></tr></thead><tbody><tr><td>Filesize</td><td>296 kB</td><td>176 kB</td><td>-40.48%</td></tr><tr><td>Rules</td><td>1592</td><td>1494</td><td>-6.16%</td></tr><tr><td>Selectors</td><td>2951</td><td>2380</td><td>-19.35%</td></tr><tr><td>Declarations</td><td>4090</td><td>4146</td><td>+1.37%</td></tr></tbody></table> <p>The drop in filesize is one of the most significant changes. A reduction of more than 40%! That is a massive achievement and I suspect the team have thrown away a large chunk of the previous codebase that became obsolete.</p> <h2 id="changes-in-design"><a href="#changes-in-design">Changes in design</a></h2> <p>The most notable change in a redesign is the visual aspect of things, so let’s find out how that reflects on the brand-related metrics in the statistics.</p> <table><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Difference</th></tr></thead><tbody><tr><td>Colors</td><td>175</td><td>137</td><td>-21.71%</td></tr><tr><td>Font-sizes</td><td>175</td><td>137</td><td>-30.88%</td></tr><tr><td>Font-families</td><td>7</td><td>8</td><td>+14.29%</td></tr></tbody></table> <p>The amount of unique colors and font-sizes have decreased quite a lot. I suspect that the team focused on building a more consistent looking UI. Fun fact from the <a href="https://www.projectwallace.com/teamwallace/css-tricks/imports/20190101202756143#values.fontfamilies.unique" rel="nofollow">font-family department</a>: no more icon fonts!</p> <h2 id="changes-in-complexity"><a href="#changes-in-complexity">Changes in complexity</a></h2> <p>Complexity in CSS is hard to define based on a couple of numbers, but here is a small list of numbers that I use to audit a codebase that I don’t own on complexity.</p> <table><thead><tr><th>Metric</th><th>Before</th><th>After</th><th>Difference</th></tr></thead><tbody><tr><td>ID selectors</td><td>369</td><td>298</td><td>-19.24%</td></tr><tr><td>Universal selectors</td><td>11</td><td>12</td><td>+9.09%</td></tr><tr><td>Average identifiers per selector</td><td>3.39</td><td>2.81</td><td>-17.10%</td></tr><tr><td>Maximum identifiers per selector</td><td>10</td><td>10</td><td>0.00%</td></tr><tr><td>Property browserhacks</td><td>24</td><td>0</td><td>-100.00%</td></tr><tr><td>!importants</td><td>82</td><td>60</td><td>-26.83%</td></tr><tr><td>!importants (%)</td><td>0.02</td><td>0.01</td><td>-27.82%</td></tr></tbody></table> <p>One metric in particular draws my attention here: <strong>average identifiers per selector</strong>. This metric is a tough one to improve and seeing that it went down with a whopping 17% just blows my mind. That means that <em>a lot of selectors</em> have gotten <em>a lot less nesting</em>. In terms of complexity that means that the CSS is a lot less tied to the HTML, which is usually a good thing.
Also, having a lot less ID selectors means that there’s a whole lot less of specificity to deal with.</p> <h2 id="closing-thoughts"><a href="#closing-thoughts">Closing thoughts</a></h2> <p>I think the CSS-Tricks Team have done a great job at this redesign. The design aspect is excellent and it feels so much fun to browse around the site. While having ’good’ stats was certainly not something that the team had in mind when working on the redesign, I think they did an excellent job on decreasing complexity and increasing cohesion. Bravo!</p> <p>I would love to read a blog post from the engineering/design team that created this new design to see what they think about these numbers.</p><!--]-->]]></content:encoded>
          <pubDate>Sun, 06 Jan 2019 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/detecting-color-aliases</guid>
          <link>https://www.projectwallace.com/blog/detecting-color-aliases?utm_source=rss</link>
          <title><![CDATA[Detecting color aliases]]></title>
          <description><![CDATA[Color aliases accidentally slip into your codebase and now you have multiple notations for the same color. Great, now what?]]></description>
          <content:encoded><![CDATA[<!--[--><p>I love looking at the color-metric pages of projects and I found a pattern while browsing through a bunch of projects. Many of them have multiple notations for the same color, like <code>#000</code> and <code>rgb(0, 0, 0)</code> that result in the same black color. I’m calling them aliases. I’m pretty sure that these aliases are not intentional, so keeping track of them may help us understand why they appear and how to fix them.</p> <h2 id="examples"><a href="#examples">Examples</a></h2> <p>First, some examples to show you what I mean by aliases:</p> <ul><li><a href="https://www.projectwallace.com/teamwallace/google-cloud/colors#alias" rel="nofollow">cloud.google.com</a></li> <li><a href="https://www.projectwallace.com/teamwallace/facebookcom/colors#alias" rel="nofollow">facebook.com</a></li> <li><a href="https://www.projectwallace.com/teamwallace/stackoverflowcom/colors#alias" rel="nofollow">stackoverflow.com</a></li></ul> <h2 id="what-does-it-mean-having-aliases-in-my-css"><a href="#what-does-it-mean-having-aliases-in-my-css">What does it mean having aliases in my CSS?</a></h2> <p>To my experience, this usually means one of the following things:</p> <ul><li><strong>Colors are not standardized in your codebase.</strong><br/> Projects like <a href="https://github.com/tailwindcss/tailwindcss/blob/7a5bc9700842b19e6e6e52bf9d5567d9a7782d4e/defaultConfig.stub.js#L30" rel="nofollow">Tailwind</a> and <a href="https://github.com/twbs/bootstrap/blob/928ebd89254300aee284fc78b84c8a57de188d71/scss/_variables.scss#L7" rel="nofollow">Bootstrap</a> define all their colors in variables and use those variables in the rest of their codebase. As long as you use a variable for any color, it means that you’ll always use one of the predefined colors.</li> <li><strong>Code isn’t minified at all or temporarily broken.</strong><br/> I’ve seen a couple of cases where websites started serving un-minified CSS, just by looking at their CSS colors. Where they just had <code>#000</code> initially, they suddenly added <code>#000000</code> too and the same for a lot more colors.</li> <li><strong>A piece of CSS that you don’t control is loaded.</strong><br/> Very often a piece of CSS is loaded that belongs to a third party plugin or some advertiser. You probably can’t control the CSS that is in their source, so if they decide to use <code>#f00</code> and you use <code>red</code> instead, there’s not a lot you can do.</li> <li><strong>Colors are created dynamically.</strong><br/> Some developers have a list of base colors, like in the above example of Tailwind. To have a list of shades for every color they apply a transformation to the colors to darken, mix or saturate them. Most preprocessors like Sass, Styles or Less allow you to do something like <code>mix(#fff, #000, 50%)</code>. The resulting color might be a color that is already in your definitions, but in a different notation, depending on what the preprocessor decides is best.</li></ul> <h2 id="are-aliases-bad"><a href="#are-aliases-bad">Are aliases bad?</a></h2> <p>No, they are not. As long as a browser can understand the different color notations and paint them on a screen, I don’t see why this could be harmful.</p> <h2 id="whats-the-benefit-of-fixing-aliases"><a href="#whats-the-benefit-of-fixing-aliases">What’s the benefit of fixing aliases?</a></h2> <p>The most obvious benefit is that you can inspect your codebase outdated or unused pieces of CSS. A certain color notation could be used in only an old part of your application that could be removed entirely.
Secondly, there is the <a href="https://csswizardry.com/2016/02/mixins-better-for-performance/" rel="nofollow">tiniest benefit in compression</a> of the CSS file if there are more common substrings. This benefit is negligible, so it should not trigger you to refactor your codebase to remove all aliases.</p> <h2 id="how-can-i-fix-aliases"><a href="#how-can-i-fix-aliases">How can I fix aliases?</a></h2> <p>A good CSS minifier should be able to fix most or all aliases. A minifier like <a href="https://github.com/cssnano/cssnano/tree/master/packages/postcss-colormin" rel="nofollow">CSSNano</a> parses all colors and tries to use shortest possible notation for each of them.
The second option is to go to your project and manually <kbd>ctrl/cmd + f</kbd> your way through your CSS, but that’s probably not such a good idea. It will take a lot of time and the benefits will only be small.</p><!--]-->]]></content:encoded>
          <pubDate>Tue, 18 Sep 2018 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/sorting-colors-in-css</guid>
          <link>https://www.projectwallace.com/blog/sorting-colors-in-css?utm_source=rss</link>
          <title><![CDATA[Sorting colors in CSS]]></title>
          <description><![CDATA[Sorting colors in CSS is hard. I've found a method to make it look pretty decent]]></description>
          <content:encoded><![CDATA[<!--[--><p>TLDR; Sorting colors in CSS is hard. I’ve found a method to make it look pretty decent (highly opinionated): <a href="https://github.com/projectwallace/color-sorter" rel="nofollow">Color Sorter repo on Github</a></p> <p>One of the goals that I’ve had since the inception of Project Wallace is to show which colors are present in your CSS. Ever since the beginning the most feedback has been around the color analysis. People seem to enjoy a list of colors ¯_(ツ)_/¯. One thing that bothered me however was that it was hard to visually grep how colors were related so I decided to start sorting them.</p> <h2 id="the-solution"><a href="#the-solution">The solution</a></h2> <p>My knowledge of color theory is limited to the basics of the color wheel, so I’m using this as my starting point. I also already know that I want to make a clear difference between black-grey-white values and saturated colors. Thanks to <a href="http://bgrins.github.io/TinyColor/" rel="nofollow">TinyColor</a> every color can be converted to an HSL value, so I can use the hue, saturation and lightness properties of each color. So, here we go:</p> <ol><li>Sort the colors by hue (red/yellow/green/blue/purple (0-360) degrees on the color wheel)</li> <li>If the hue of two colors is the same, order them by saturation</li> <li>Take every color in the grey spectrum (meaning that the saturation equals 0), and move that to the end of our color list</li> <li>Order the grey-ish colors by their alpha channel</li> <li>If the alpha channel is the same for both colors, sort them by lightness</li></ol> <p>Take a look at <a href="https://github.com/projectwallace/color-sorter#examples" rel="nofollow">the examples</a> in the repo to see the results for several well-known sites.</p> <h2 id="the-bad-parts"><a href="#the-bad-parts">The bad parts</a></h2> <ul><li>It is pretty much impossible to properly handle the differences in the alpha-channel (opacity). A color that is 90% transparent will look totally different than that same color with 0% transparency.</li> <li>This sorting is based on my own taste and has no solid base in color theory. It just needs to look good for me.</li></ul><!--]-->]]></content:encoded>
          <pubDate>Sun, 02 Sep 2018 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/introducing-gromit</guid>
          <link>https://www.projectwallace.com/blog/introducing-gromit?utm_source=rss</link>
          <title><![CDATA[Introducing Gromit]]></title>
          <description><![CDATA[Gromit is a tool that runs in your builds and checks if the stats do not exceed any tresholds that you have set.]]></description>
          <content:encoded><![CDATA[<!--[--><p>Woof! 🐶 <a href="https://github.com/projectwallace/constyble" rel="nofollow">Introducing Gromit</a>, a guard dog for your CSS. It is built on top of <a href="https://github.com/projectwallace/css-analyzer#readme" rel="nofollow">Project Wallace’s CSS Analyzer</a> and checks if your final output CSS does not exceed any tresholds that you’ve set.</p> <h2 id="a-quick-look"><a href="#a-quick-look">A quick look</a></h2> <p>Gromit is at his best when you let him play in your build pipeline. You feed him your CSS and a configuration with tresholds, and he will entertain himself.</p> <pre class="language-shell"><!----><code class="language-shell"><span class="token function">npm</span> <span class="token builtin class-name">test</span>
<span class="token operator">></span> <span class="token function">cat</span> my-concatenated-styles.css <span class="token operator">|</span> gromit <span class="token parameter variable">--config</span><span class="token operator">=</span>.gromitrc

TAP version <span class="token number">13</span>

<span class="token comment"># Subtest: atrules.fontfaces.totalUnique</span>
    ok <span class="token number">1</span> - atrules.fontfaces.totalUnique should not belarger than <span class="token number">1</span> <span class="token punctuation">(</span>actual: <span class="token number">1</span><span class="token punctuation">)</span>
    <span class="token number">1</span><span class="token punctuation">..</span><span class="token number">1</span>
ok <span class="token number">1</span> - atrules.fontfaces.totalUnique <span class="token comment"># time=2.298ms</span>

<span class="token comment"># Subtest: atrules.mediaqueries.totalUnique</span>
    ok <span class="token number">1</span> - atrules.mediaqueries.totalUnique should notbe larger than <span class="token number">11</span> <span class="token punctuation">(</span>actual: <span class="token number">11</span><span class="token punctuation">)</span>
    <span class="token number">1</span><span class="token punctuation">..</span><span class="token number">1</span>
ok <span class="token number">2</span> - atrules.mediaqueries.totalUnique <span class="token comment"># time=1.169ms</span>

<span class="token comment"># Subtest: selectors.id.total</span>
    ok <span class="token number">1</span> - selectors.id.total should not be larger than <span class="token number">0</span> <span class="token punctuation">(</span>actual: <span class="token number">0</span><span class="token punctuation">)</span>
    <span class="token number">1</span><span class="token punctuation">..</span><span class="token number">1</span>
ok <span class="token number">3</span> - selectors.id.total <span class="token comment"># time=0.877ms</span>

<span class="token comment"># Subtest: selectors.js.total</span>
    ok <span class="token number">1</span> - selectors.js.total should not be larger than <span class="token number">0</span> <span class="token punctuation">(</span>actual: <span class="token number">0</span><span class="token punctuation">)</span>
    <span class="token number">1</span><span class="token punctuation">..</span><span class="token number">1</span>
ok <span class="token number">4</span> - selectors.js.total <span class="token comment"># time=1.455ms</span>

<span class="token comment"># Subtest: values.colors.totalUnique</span>
    ok <span class="token number">1</span> - values.colors.totalUnique should not be larger than <span class="token number">28</span> <span class="token punctuation">(</span>actual: <span class="token number">28</span><span class="token punctuation">)</span>
    <span class="token number">1</span><span class="token punctuation">..</span><span class="token number">1</span>
ok <span class="token number">5</span> - values.colors.totalUnique <span class="token comment"># time=1.041ms</span>

<span class="token number">1</span><span class="token punctuation">..</span><span class="token number">5</span>
<span class="token comment"># time=40.526ms</span>

 ✔ <span class="token string">"Well done, lad! Very well done..."</span>
✨  Done <span class="token keyword">in</span> <span class="token number">2</span>.66s.</code><!----></pre> <h2 id="the-configuration"><a href="#the-configuration">The configuration</a></h2> <p>The configuration is based on all the metrics that Wallace’s CSS Analyzer can measure. All metrics are optional by default. Here’s an example from the <a href="https://github.com/projectwallace/constyble#config-file" rel="nofollow">Gromit repo readme</a>:</p> <h3 id="gromitrc-config-file-example"><a href="#gromitrc-config-file-example">.gromitrc config file example</a></h3> <pre class="language-jsonc"><!----><code class="language-jsonc">&#123;
	// Do not exceed 4095, otherwise IE9
	// will drop any subsequent rules
	&quot;selectors.total&quot;: 4095,
	&quot;selectors.id.total&quot;: 0,
	&quot;values.colors.totalUnique&quot;: 2,
	&quot;values.colors.unique&quot;: [&quot;#fff&quot;, &quot;#000&quot;]
&#125;</code><!----></pre> <p>This example configuration disallows having more than 4095 selectors in total, disallows any ID selectors, and allows only the colors <code>#fff</code> and <code>#000</code> to be present in the CSS.</p> <h2 id="why-not-use-css-linters"><a href="#why-not-use-css-linters">Why not use CSS linters?</a></h2> <p>Who said you can’t use both? Linters like Stylelint, EsLint and many more have proven to be very helpful in many ways, but we usually configure them to look at our to-be-transformed CSS, like Less or Stylus. That means that we don’t often look at the CSS that these preprcessors generate. Using a preprocessor sometimes makes it hard to guess what CSS eventually comes out. Gromit looks at the actual CSS and warns you if some Sass mixin accidentally created a color you don’t want to exist.</p> <h3 id="example-of-preprocessor-creating-undesired-values"><a href="#example-of-preprocessor-creating-undesired-values">Example of preprocessor creating undesired values</a></h3> <pre class="language-scss"><!----><code class="language-scss"><span class="token comment">// This will produce a color based on your $grey variable,</span>
<span class="token comment">// but darker and less opaque</span>
<span class="token selector">.box </span><span class="token punctuation">&#123;</span>
	<span class="token property">box-shadow</span><span class="token punctuation">:</span> 1em 1em 1em <span class="token function">opacify</span><span class="token punctuation">(</span><span class="token function">darken</span><span class="token punctuation">(</span><span class="token variable">$grey</span><span class="token punctuation">,</span> 10%<span class="token punctuation">)</span><span class="token punctuation">,</span> 0.5<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>Setting up Gromit or any linter is probably equally hard or easy for both (which isn’t very hard).</p><!--]-->]]></content:encoded>
          <pubDate>Tue, 21 Aug 2018 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/new-feature-empty-rulesets-analysis</guid>
          <link>https://www.projectwallace.com/blog/new-feature-empty-rulesets-analysis?utm_source=rss</link>
          <title><![CDATA[New feature: empty rulesets analysis]]></title>
          <description><![CDATA[It is a tiny new feature, but starting now you can analyze how many empty rulesets your CSS contains.]]></description>
          <content:encoded><![CDATA[<!--[--><p>When I was scrolling to some of my competitors’ websites, I noticed that <a href="https://www.npmjs.com/package/analyze-css" rel="nofollow">analyze-css</a> by <a href="https://github.com/macbre" rel="nofollow">Maciej Brencz</a> was counting the amount of empty rules in your CSS. It seemed to me that this was easy to add to Wallace, so I did it in a short amount of time. Here is the explanation that will eventually end up in the docs in some form:</p> <blockquote><p>A rule (or ruleset) is considered empty when it contains no declarations. Whitespace is ignored when determining whether a rule is empty or not.</p></blockquote> <h2 id="examples"><a href="#examples">Examples</a></h2> <pre class="language-css"><!----><code class="language-css"><span class="token comment">/* This is an empty rule */</span>
<span class="token selector">.header</span> <span class="token punctuation">&#123;</span>
<span class="token punctuation">&#125;</span>

<span class="token comment">/* This is also an empty rule */</span>
<span class="token selector">.header</span> <span class="token punctuation">&#123;</span>
<span class="token punctuation">&#125;</span>

<span class="token comment">/* This is not an empty rule */</span>
<span class="token selector">.header</span> <span class="token punctuation">&#123;</span>
	<span class="token property">margin</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>Please note that at-rules are not part of this analysis, so an empty media query rule is not counted here. That might be a future improvement.</p> <p>Finally, a <a href="https://github.com/projectwallace/css-analyzer/commit/effc01cc4777d1d0e0a91bd0125e1b0013c618af" rel="nofollow">link to the commit</a> that contains this change.</p><!--]-->]]></content:encoded>
          <pubDate>Mon, 23 Jul 2018 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/css-analyzer-now-on-npm</guid>
          <link>https://www.projectwallace.com/blog/css-analyzer-now-on-npm?utm_source=rss</link>
          <title><![CDATA[css-analyzer is now on NPM!]]></title>
          <description><![CDATA[You can install css-analyzer via NPM now!]]></description>
          <content:encoded><![CDATA[<!--[--><p>As of today it is possible to install <a href="https://www.npmjs.com/package/@projectwallace/css-analyzer" rel="nofollow">css-analyzer</a> via NPM! I released it as 1.0.0, so it means that as from now I am committed to adhering to SemVer and keeping the API stable until a next major version. I hope to find more users and contributers by publishing it to NPM.</p> <pre class="language-shell"><!----><code class="language-shell"><span class="token function">npm</span> i @projectwallace/css-analyzer</code><!----></pre><!--]-->]]></content:encoded>
          <pubDate>Sun, 22 Jul 2018 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/font-face-descriptors-not-properties</guid>
          <link>https://www.projectwallace.com/blog/font-face-descriptors-not-properties?utm_source=rss</link>
          <title><![CDATA[@font-face descriptors are not properties]]></title>
          <description><![CDATA[Sometimes there are less properties reported than you are expecting. Here is why.]]></description>
          <content:encoded><![CDATA[<!--[--><p>Here is some piece of CSS you’ve probably seen before:</p> <pre class="language-css"><!----><code class="language-css"><span class="token atrule"><span class="token rule">@font-face</span></span> <span class="token punctuation">&#123;</span>
	<span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token string">'MyCustomFont'</span><span class="token punctuation">;</span>
	<span class="token property">src</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'/path/to-font.woff2'</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span>

<span class="token selector">.my-component</span> <span class="token punctuation">&#123;</span>
	<span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token string">'MyCustomFont'</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span></code><!----></pre> <p>When it comes to analyzing CSS it is tempting to count that <code>font-family</code> and <code>src</code> inside the <code>@font-face</code> as regular properties, but technically they are not. They are descriptors.</p> <h2 id="the-theory"><a href="#the-theory">The theory</a></h2> <blockquote><p>A CSS descriptor defines the characteristics of an at-rule. At-rules may have one or multiple descriptors. Each descriptor has:</p> <ul><li>A name</li> <li>A value, which holds the component values</li> <li>An “!important” flag, which in its default state is unset</li></ul> <p><a href="https://developer.mozilla.org/en-US/docs/Glossary/descriptor_(CSS)" rel="nofollow">CSS Descriptor, MDN web docs</a></p></blockquote> <p>In the case of our example, the <code>font-family</code> and the <code>src</code> are the descriptors of the <code>@font-face</code> at-rule.</p> <h2 id="common-css-parser-implementations"><a href="#common-css-parser-implementations">Common CSS parser implementations</a></h2> <p>Most CSS parsers, like PostCSS and ReworkCSS take those lines as declarations because it fits better in the Abstract Syntax Tree that they are constructing. And that’s fine, because these parsers are aimed at performing operations on the CSS to transform it into something else. But when it comes to analyzing the tree, we must take note that the ‘properties’ inside <code>@font-face</code>s are actually descriptors.</p> <h2 id="consequenses-for-css-statistics"><a href="#consequenses-for-css-statistics">Consequenses for CSS statistics</a></h2> <ul><li>Less properties are counted than you might expect</li> <li>Specifically: less font-related properties, which usually means there are less font-families to show in your projects’ <a href="https://www.projectwallace.com/teamwallace/project-wallace/fontfamilies" rel="nofollow">font-family details pages</a></li></ul> <h2 id="more-information"><a href="#more-information">More information</a></h2> <p>Read more about CSS descriptors:</p> <ul><li><a href="https://www.w3.org/TR/css-fonts-3/#font-face-rule" rel="nofollow">The W3C Spec</a> If you’re into the fine details of the <code>@font-face</code> descriptors</li> <li><a href="https://www.quackit.com/css/at-rules/descriptors/css_font-family_descriptor.cfm" rel="nofollow">CSS font-family Descriptor</a> A readable explanation, and more great docs about CSS</li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face" rel="nofollow"><code>@font-face</code> at MDN Docs</a></li></ul><!--]-->]]></content:encoded>
          <pubDate>Tue, 15 May 2018 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/css-analyzer-now-postcss</guid>
          <link>https://www.projectwallace.com/blog/css-analyzer-now-postcss?utm_source=rss</link>
          <title><![CDATA[Project Wallace CSS Analyzer is now on PostCSS]]></title>
          <description><![CDATA[We've switched to PostCSS for generating the AST for our analysis, and did some other fixes under the hood too.]]></description>
          <content:encoded><![CDATA[<!--[--><p>So you start making a brand new <a href="https://github.com/projectwallace/css-analyzer" rel="nofollow">CSS analyzer</a> and you’re all fired up to get to work on it. You find a CSS parser, transform the <a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree" rel="nofollow">AST</a> into some kind of statistics and there you go: your own pretty little analyzer! But… in this case I made a mistake very soon in the process: choosing <a href="https://github.com/reworkcss/css" rel="nofollow">Reworkcss</a> over <a href="http://postcss.org/" rel="nofollow">PostCSS</a>, because PostCSS seemed so much harder to work with. Of course I was wrong.</p> <h2 id="the-old-way"><a href="#the-old-way">The old way</a></h2> <ol><li><strong>Let Reworkcss transform the <a href="https://github.com/reworkcss/css#ast" rel="nofollow">CSS into an AST</a>;</strong></li> <li><strong>Analyze the AST, including making the AST simpler in some cases;</strong> For example, Reworkcss creates an AST that reflects the actual tree structure of the CSS file, so nested Media Queries could appear, nested rules and many more complex structures. This is ok if you want to do some in-depth analysis of things, but for a quick analysis it’s easier to have a flat structure, like PostCSS does. In the case of using Reworkcss we have to do <a href="https://github.com/projectwallace/css-analyzer/commit/e5504e1226b607e6af0ab0668fcd96511f1553c6#diff-dc17ef0e9b58ca9c3ecde125925b6d41L8" rel="nofollow">some recursion</a> at a couple of places to make sure that we don’t miss out on some (edge) cases.</li> <li><strong>Unit tests for analysis;</strong> Only test the output of the whole application, without intermediate steps for the parser for example. If something in the parser fails, it’ll fail in the eventual output. That’s not the right way to approach it.</li> <li><strong>Done.</strong></li></ol> <h2 id="the-new-way"><a href="#the-new-way">The new way</a></h2> <ol><li><p><strong>Let PostCSS create a <a href="http://api.postcss.org/Root.html" rel="nofollow">flat AST</a>;</strong> And flat means that there’s no need to worry about nesting or other complex structures. When asking for all Media Queries, it returns all Media Queries, regardless of the document structure. That’s exactly what we need.</p></li> <li><p><strong>Transform the AST a tiny bit into something a little more suitable for analyzing;</strong> By creating an intermediate layer for the AST, we’re able to write tests for it and we’re making it easier to switch between parsers, because they will all be turned into simple arrays of strings of tiny objects.</p></li> <li><p><strong>Run tests for the analyzable AST;</strong></p></li> <li><p><strong>Analyze the CSS;</strong> Run the analysis on the simple arrays and objects created in step 2.</p></li> <li><p><strong>Run tests for the analysis;</strong></p></li> <li><p><strong>Done!</strong></p></li></ol> <p>PostCSS is actually easier to work with than Reworkcss because of the ease of which you can get <em>all</em> rules, instead of having to go through the whole document yourself. It pays off to split even a relatively small application like this one into multiple layers to increase testability and maintainability. Have fun using <a href="https://github.com/projectwallace/css-analyzer" rel="nofollow">Project Wallace CSS Analyzer</a>!</p><!--]-->]]></content:encoded>
          <pubDate>Tue, 08 Aug 2017 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/php-library</guid>
          <link>https://www.projectwallace.com/blog/php-library?utm_source=rss</link>
          <title><![CDATA[The PHP CSS project that was released. And abandoned right away.]]></title>
          <description><![CDATA[Today marks the point where a new PHP CSS parsing/analyzing library is released publicly. And abandoned immediately.]]></description>
          <content:encoded><![CDATA[<!--[--><p>Today marks the point where a PHP library accepting incoming HTTP requests, parsing CSS into an Abstract Syntaxt Tree (AST) and analysing it into a hash of statistics was <a href="https://github.com/projectwallace/php-css-parser-analyser" rel="nofollow">released on GitHub</a>. And abandoned immediately.</p> <p>The reason was explained in the <a href="/blog/new-libraries-released">previous blog post</a>: it is too slow to process a lot of stylesheets, so work on a NodeJS based system has already started and expected to be ready for use somewhere late this summer. The now published and abandoned PHP library served as a great starter for doing the first stages of this rather large project. It was easy to setup, easy to test and a pain to deploy.</p> <p>The architecture of the PHP library is a mess, because the single project serves three different purposes:</p> <ul><li>A REST API that accepts incoming CSS for analysis and serves historical data for those analysis</li> <li>A CSS Parser (based on <a href="https://github.com/katiefenn/parker" rel="nofollow">Parker</a>), because it was hard to find a PHP library that parsed CSS into a AST</li> <li>A CSS analyser that created an analysis based on the CSS AST</li></ul> <p>That alone already made the project poorly constructed, but also the less than ideal construction of using an out-of-date version of <a href="https://www.slimframework.com/" rel="nofollow">Slim</a> and just not enough knowledge of and care for the PHP syntax made it a recipe for disaster. Everything worked quite well, a lot of tests have been written, but it just didn’t cut it. Stylesheets from sites like <a href="https://css-tricks.com/" rel="nofollow">CSS-Tricks</a> and <a href="https://www.schiphol.nl/en/" rel="nofollow">Schiphol Airport</a> caused the API to time out every time and increasing the timeout to 30+ seconds just didn’t make sense. Also, smarter developers will probably say that Dependency Injection, Domain Driven Design, and all that fancy stuff is missing or implemented incorrectly.</p> <p>So there you have it: a new library that will not be worked on. Hope you enjoy the new one soon!</p><!--]-->]]></content:encoded>
          <pubDate>Mon, 26 Jun 2017 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/new-libraries-released</guid>
          <link>https://www.projectwallace.com/blog/new-libraries-released?utm_source=rss</link>
          <title><![CDATA[New libraries released!]]></title>
          <description><![CDATA[Two new libraries were released that will contribute to a better and faster API to power the Project Wallace website.]]></description>
          <content:encoded><![CDATA[<!--[--><p>Today I released two libraries on GitHub that will ultimately power <a href="https://www.projectwallace.com" rel="nofollow">projectwallace.com</a>! Both libraries are brand new and take part in replacing a slow PHP API that times out on a lot of CSS files. Because that’s such a big and important part of what Project Wallace is, I decided to make the open source. That way our fantastic community will get a chance to contribute to the Project.</p> <h2 id="css-collection"><a href="#css-collection">css-collection</a></h2> <p>This project can be used to get a little more powers with arrays. I often found myself writing functions for sorting, making unique lists and getting the first and last item from a list. This repository takes care of those issues.</p> <p>See the repository on GitHub: <strong><a href="https://github.com/projectwallace/css-collection" rel="nofollow">css-collection</a></strong></p> <h2 id="css-analyzer"><a href="#css-analyzer">css-analyzer</a></h2> <p>Here’s the real powerhouse of Project Wallace. This repository contains all the stuff that analyses CSS, from rules to values and from at-rules to stylesheet-wide stats. It currently is pretty basic and could definitely benefit from some more async operations, but because of it’s simplicity it’s pretty easy to maintain. The <a href="https://github.com/projectwallace/css-collection" rel="nofollow">css-collection</a> library is used quite a lot in this repository because of all the collections of selectors, values etc. that are in it.</p> <p>See the repository on GitHub: <strong><a href="https://github.com/projectwallace/css-analyzer" rel="nofollow">css-analyzer</a></strong></p> <h2 id="next-steps"><a href="#next-steps">Next steps</a></h2> <p>The last step in replacing the slow PHP API is to write a new API, probably also in Node, since it can use the other projects as dependencies. 💖</p> <p>In the meantime, keep an eye
on the <a href="https://github.com/projectwallace" rel="nofollow">Project Wallace GitHub profile</a> for updates that
will most definitely come.</p><!--]-->]]></content:encoded>
          <pubDate>Fri, 19 May 2017 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
        <item>
          <guid>https://www.projectwallace.com//blog/is-this-thing-on</guid>
          <link>https://www.projectwallace.com/blog/is-this-thing-on?utm_source=rss</link>
          <title><![CDATA[*Tap, tap.* Is this thing on?]]></title>
          <description><![CDATA[The project is coming to a stage where everyone can test it, but a fair warning: it is still unstable!]]></description>
          <content:encoded><![CDATA[<!--[--><p>Hi. How are you? Just wanted to let you know that we’re going to do it! Wallace is going to be open for anyone to use this thing! It will be a hell of a journey, but let’s get started.</p> <p>Before we start though, a small word of warning. <strong>This project will change and it will change a lot.</strong> Here’s a small breakdown of it:</p> <ul><li>URI’s are going to change. No doubt about it. The current way they are used are too verbose and it would be nice to have cleaner, shorter URI’s but for now these have to do.</li> <li>The design will undergo some transformations here and there. Although the dark purple looks cool and has some character, the color scheme needs fixing and so does the general look and feel.</li> <li>Page layouts will very likely change to something more useful. The <a href="https://www.projectwallace.com/bartveneman/project-wallace" rel="nofollow">project overview page</a> is now pretty cluttered with all kinds of graphs and the detail pages also could use some improvements here and there.</li></ul> <p>One thing that will not change a lot is the actual analysis of the CSS itself. All that is largely done and tested and will remain stable, apart from the occasional bug fix here and there.</p> <p>So here we go: <a href="https://www.projectwallace.com" rel="nofollow">Project Wallace is live 🎉</a></p><!--]-->]]></content:encoded>
          <pubDate>Tue, 28 Feb 2017 00:00:00 GMT</pubDate>
          <author>Bart Veneman</author>
        </item>
      
    </channel>
  </rss>