<?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>Anthony Fu</title>
        <link>https://antfu.me/</link>
        <description>Anthony Fu' Blog</description>
        <lastBuildDate>Mon, 22 Dec 2025 01:06:45 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Anthony Fu</title>
            <url>https://antfu.me/avatar.png</url>
            <link>https://antfu.me/</link>
        </image>
        <copyright>CC BY-NC-SA 4.0 2021 © Anthony Fu</copyright>
        <atom:link href="https://antfu.me/feed.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Categorize Your Dependencies]]></title>
            <link>https://antfu.me/posts/categorize-deps</link>
            <guid isPermaLink="true">https://antfu.me/posts/categorize-deps</guid>
            <pubDate>Mon, 28 Apr 2025 14:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>When building a project, it's very likely that we will install third-party packages from npm to offload some tasks. On that topic, we know there are two major types of dependencies: <code>dependencies</code> (prod) and <code>devDependencies</code> (dev). In our <code>package.json</code>, it might look something like this:</p>
<pre><code class="language-json">{
  &quot;name&quot;: &quot;my-cool-vue-components&quot;,
  &quot;dependencies&quot;: {
    &quot;vue&quot;: &quot;^3.5.15&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;eslint&quot;: &quot;^9.15.0&quot;
  }
}
</code></pre>
<p>The main difference is that <a href="https://github.com/npm/npm/blob/2e3776bf5676bc24fec6239a3420f377fe98acde/doc/files/package.json.md#devdependencies"><code>devDependencies</code></a> are only needed during the build or development phase, while <a href="https://github.com/npm/npm/blob/2e3776bf5676bc24fec6239a3420f377fe98acde/doc/files/package.json.md#dependencies"><code>dependencies</code></a> are required for the project to run. For example, <code>eslint</code> in the case above only lints our source code; it's no longer needed when we publish the project or deploy it to production.</p>
<p>The concept of <code>dependencies</code> and <code>devDependencies</code> was originally introduced for <strong>authoring Node.js libraries</strong> (those published to npm). When you install a package like <code>vite</code>, npm automatically installs its <code>dependencies</code> but not its <code>devDependencies</code>. This is because you are consuming <code>vite</code> as a dependency and don't need its development tools. So, even if <code>vite</code> uses <code>prettier</code> during its development, you won't be forced to install <code>prettier</code> when you only need <code>vite</code> in your project.</p>
<p>As the ecosystem has evolved, we can now build much more complex projects than ever before. We have meta-frameworks for building full-stack websites, bundlers for transpiling and bundling code and dependencies, and so on. Node.js became a lot more than just running JavaScript code and packages on the server side.</p>
<p>I'd roughly categorize projects into three types:</p>
<ol>
<li><strong>Apps</strong>: Websites, Electron apps, mobile apps, etc. Here, <code>package.json</code> primarily keeps track of dependency information, and the app itself is never published to npm.</li>
<li><strong>Libraries</strong>: Packages designed to be published to npm, then installed and consumed by other projects.</li>
<li><strong>Internal</strong>: Packages used within monorepos that are never published.</li>
</ol>
<p>Fundamentally, the distinction between <code>dependencies</code> and <code>devDependencies</code> <strong>only</strong> truly makes sense for libraries intended for publication on npm. However, due to different scenarios and usage patterns, their meaning has extended far beyond the original purpose.</p>
<p>Tools often <strong>overload</strong> the meaning of <code>dependencies</code> and <code>devDependencies</code> to fit various scenarios, aiming for sensible defaults and better Developer Experience.</p>
<p>For example, <a href="https://vite.dev/"><code>Vite</code></a> treats <code>dependencies</code> as &quot;client-side packages&quot; and automatically runs pre-optimization on them. Build tools like <a href="https://github.com/egoist/tsup"><code>tsup</code></a>, <a href="https://github.com/unjs/unbuild"><code>unbuild</code></a>, and <a href="https://github.com/rolldown/tsdown"><code>tsdown</code></a> treat <code>dependencies</code> as packages to be externalized during bundling, automatically inlining (bundling) anything not listed in <code>dependencies</code>.</p>
<p>While these conventions certainly simplify things in most cases, they also force <code>dependencies</code> and <code>devDependencies</code> to wear multiple hats, making it harder to grasp the purpose of each package.</p>
<p>If we see <code>vue</code> listed in <code>devDependencies</code>, it could mean several things:</p>
<ul>
<li>We are inlining/bundling it.</li>
<li>We are only referencing its types.</li>
<li>We use it solely for testing.</li>
<li>We have it to enable IDE IntelliSense.</li>
<li>Or something else entirely.</li>
</ul>
<p>Simply classifying packages as <code>dependencies</code> or <code>devDependencies</code> doesn't provide the full picture of that package's purpose without external documentation (also note that <code>package.json</code> doesn't support comments).</p>
<h3>Categorize Your Dependencies</h3>
<p>Let's forget about <code>dependencies</code> and <code>devDependencies</code> for a moment, how might we categorize our dependencies? Here are some rough ideas I could come up with:</p>
<ul>
<li><code important-text-lime>test</code>: Packages used for testing (e.g., <code>vitest</code>, <code>playwright</code>, <code>msw</code>).</li>
<li><code important-text-purple>lint</code>: Packages for linting/formatting (e.g., <code>eslint</code>, <code>knip</code>).</li>
<li><code important-text-cyan>build</code>: Packages used for building the project (e.g., <code>vite</code>, <code>rolldown</code>).</li>
<li><code important-text-orange>script</code>: Packages used for scripting tasks (e.g., <code>tsx</code>, <code>tinyglobby</code>, <code>cpx</code>).</li>
<li><code important-text-green>frontend</code>: Packages for frontend development (e.g., <code>vue</code>, <code>pinia</code>).</li>
<li><code important-text-yellow>backend</code>: Packages for the backend server.</li>
<li><code important-text-blue>types</code>: Packages for type checking and definitions.</li>
<li><code important-text-amber>inlined</code>: Packages that are included directly in the final bundle.</li>
<li><code important-text-red>prod</code>: Runtime production dependencies.</li>
<li>...</li>
</ul>
<p>Categorization might differ between projects. But that point is that <code>dependencies</code> and <code>devDependencies</code> lack the flexibility to capture this level of detail.</p>
<p>This thing had been bothering me for a while, though it didn't feel like a critical problem needing immediate resolution. Only until pnpm introduced <a href="https://pnpm.io/catalogs">catalogs</a>, opening up possibilities for dependency categorization we never had before.</p>
<h3>PNPM Catalogs</h3>
<p><a href="https://pnpm.io/catalogs">PNPM Catalogs</a> is a feature allowing monorepo workspaces to share dependency versions across different packages via a centralized management location.</p>
<p>Basically, you add <code>catalog</code> or <code>catalogs</code> fields to your <code>pnpm-workspace.yaml</code> file and reference them using <code>catalog:&lt;name&gt;</code> in your <code>package.json</code>.</p>
<pre><code class="language-yaml"># pnpm-workspace.yaml
catalog:
  vue: ^3.5.15
  pinia: ^2.2.6
  cac: ^6.7.14
</code></pre>
<pre><code class="language-json">// package.json
{
  &quot;dependencies&quot;: {
    &quot;vue&quot;: &quot;catalog:&quot;,
    &quot;pinia&quot;: &quot;catalog:&quot;,
    &quot;cac&quot;: &quot;catalog:&quot;
  }
}
</code></pre>
<p>Or with <a href="https://pnpm.io/catalogs#named-catalogs"><strong>named catalogs</strong></a>:</p>
<pre><code class="language-yaml"># pnpm-workspace.yaml
catalogs:
  frontend:
    vue: ^3.5.15
    # We locked the version for some reason, etc.
    pinia: 2.2.6
  prod:
    cac: ^6.7.14
</code></pre>
<pre><code class="language-json">// package.json
{
  &quot;dependencies&quot;: {
    &quot;vue&quot;: &quot;catalog:frontend&quot;,
    &quot;pinia&quot;: &quot;catalog:frontend&quot;,
    &quot;cac&quot;: &quot;catalog:prod&quot;
  }
}
</code></pre>
<p>During installation and publishing, pnpm automatically resolves dependencies to the versions specified in the catalogs. While it's originally designed for managing version consistency across monorepos, I found <a href="https://pnpm.io/catalogs#named-catalogs">Named Catalogs</a> are also a great way to also categorize dependencies. As shown above, we can categorize <code>vue</code> and <code>cac</code> into different catalogs even though they both presented in <code>dependencies</code>. This information makes version upgrade easier and would help on reviewing dependency changes.</p>
<blockquote>
<p>A nice bonus: you can use comments in <code>pnpm-workspace.yaml</code> to share additional context with your team.</p>
</blockquote>
<h3>Tooling Support</h3>
<p>Given that catalogs are still quite new, this shift requires better tooling support. A significant pain point for me on this was losing the ability to see a dependency's version at a glance in <code>package.json</code> when using <code>catalog:&lt;name&gt;</code>.</p>
<p>To address this, I created a VS Code extension, <a href="https://marketplace.visualstudio.com/items?itemName=antfu.pnpm-catalog-lens">PNPM Catalog Lens</a>, which displays the resolved version inline within <code>package.json</code>.</p>
<p><img src="https://antfu.me/images/pnpm-catalogs-vscode.png" alt="Screenshot of the extension PNPM Catalog Lens"></p>
<p>It also adds distinct colors to each named category for easier identification. This gives us the categorization and centralized version control without significantly impacting DX.</p>
<p>Since versions move to <code>pnpm-workspace.yaml</code>, CLI tools would need to make some integrations to support this. So far, we've adapted the following tools:</p>
<ul>
<li><a href="https://github.com/antfu/taze"><code>taze</code></a>: Checks and bumps dependency versions, now supporting reading and updating versions from catalogs.</li>
<li><a href="https://github.com/antfu/pnpm-workspace-utils/tree/main/packages/eslint-plugin-pnpm"><code>eslint-plugin-pnpm</code></a>: Enforces using catalogs for all dependencies in <code>package.json</code>, with auto-fixes.
<ul>
<li>If you use <a href="https://github.com/antfu/eslint-config"><code>@antfu/eslint-config</code></a>, enable this by setting <code>pnpm: true</code>.</li>
</ul>
</li>
<li><a href="https://github.com/antfu/pnpm-workspace-utils/tree/main/packages/pnpm-workspace-yaml"><code>pnpm-workspace-yaml</code></a>: A utility library for reading and writing <code>pnpm-workspace.yaml</code> while preserving comments and formatting.</li>
<li><a href="https://github.com/antfu/node-modules-inspector"><code>node-modules-inspector</code></a>: Visualizes your <code>node_modules</code>, now labeling dependencies with their catalog name for a better overview of their origin.</li>
<li><a href="https://github.com/antfu/nip"><code>nip</code></a>: Interactive CLI to install packages to catalogs</li>
</ul>
<h3>Looking into the Future</h3>
<p>Currently, I see the value of categorize dependencies is mainly for better communication and easier version upgrade reviews. However, as this convention gains wider adoption and tooling support improves, we could integrate this information more deeply with our tools.</p>
<p>For example, in Vite, we could gain more explicit control over dependency optimization, decoupling it from the <code>dependencies</code> and <code>devDependencies</code> fields:</p>
<pre><code class="language-ts">// vite.config.ts
import { readWorkspaceYaml } from 'pnpm-workspace-yaml'
import { defineConfig } from 'vite'

const yaml = await readWorkspaceYaml('pnpm-workspace.yaml') // pseudo-API

export default defineConfig({
  optimizeDeps: {
    include: Object.keys(yaml.catalogs.frontend)
  }
})
</code></pre>
<p>Similarly, for <a href="https://github.com/unjs/unbuild"><code>unbuild</code></a>, we could explicitly control externalization and inlining without manually maintaining lists in multiple places:</p>
<pre><code class="language-ts">// build.config.ts
import { readWorkspaceYaml } from 'pnpm-workspace-yaml'
import { defineBuildConfig } from 'unbuild'

const yaml = await readWorkspaceYaml('pnpm-workspace.yaml')

export default defineBuildConfig({
  externals: Object.keys(yaml.catalogs.prod),
  rollup: {
    inlineDependencies: Object.keys(yaml.catalogs.inlined)
  }
})
</code></pre>
<p>For linting or bundling, we could enforce rules based on catalogs, such as throwing errors when attempting to import backend packages into frontend code, preventing accidental bundling mistakes.</p>
<p>This categorization could also provide valuable context for vulnerability reports. Vulnerabilities in build tools might be less severe than those in dependencies shipped to production.</p>
<p>...and so on.</p>
<p>I've already started migrating many of my projects to use named catalogs(<a href="https://github.com/antfu/node-modules-inspector"><code>node-modules-inspector</code></a> for example). Even outside monorepos, the ability to categorize dependencies is a compelling reason to adopt to pnpm catalogs. I consider this an exploratory phase where we're still discovering best practices and improving tooling support.</p>
<p>So, that's why I'm writing this post: to invite you to consider this approach and try it out. We'd love to hear your thoughts and how you would utilize it. I look forward to seeing more patterns like this emerge, helping us build more maintainable projects with a better DX. Thanks for reading!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Hello Tokyo!]]></title>
            <link>https://antfu.me/posts/hello-tokyo</link>
            <guid isPermaLink="true">https://antfu.me/posts/hello-tokyo</guid>
            <pubDate>Tue, 08 Apr 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><a href="/posts/ohayo-tokyo"><span i-ph-arrow-right-bold inline-block/> 日本語</a></p>
<p>Seven years ago, my first visit to Japan - a trip to <ruby lang="ja">大阪<rp>(</rp><rt>Osaka</rt><rp>)</rp></ruby>, <ruby lang="ja">京都<rp>(</rp><rt>Kyoto</rt><rp>)</rp></ruby>, and <ruby lang="ja">北海道<rp>(</rp><rt>Hokkaido</rt><rp>)</rp></ruby> - planted the seed of wanting to live in Japan one day. It was an unforgettable experience that changed the way I see the world. The meticulous attention to detail, the dedication to craftsmanship, and the seamless blend of tradition and modernity continue to inspire me.</p>
<p>Here are some photos I took back in 2018 on that trip:</p>
<PhotoHelloTokyo1 />
<p>The language also intrigued me. Japanese has a unique rhythm and musicality that I find fascinating. As an anime enthusiast, I've been passively learning Japanese through <a href="/media">watching countless shows</a>, but I've never had the opportunity to study it formally to have it useful enough.</p>
<p>Living in Japan has been one of my life goals, but life took some unexpected turns along the way. My work in programming suddenly gained recognition at the last year of my university, igniting my passion and leading me into the exciting world of Open Source, which has been filled with incredible opportunities and experiences.</p>
<p>After graduating from university, I was incredibly fortunate to join {@NuxtLabs}, where I got to collaborate with amazing people and work in Open Source. This opportunity led me to <a href="/posts/bonjour-paris">move to Paris</a> two years ago. Paris is a magnificent city, and life in Europe opened my eyes to so many new experiences - attending conferences and meetups, and meeting wonderful friends along the way. Although Europe wasn't part of my initial plan, I'm deeply grateful for the chance to live there, as it has completely transformed my life.</p>
<h2>The Move</h2>
<p>It would never be enough for me to explore and discover the profound history and culture of Europe. However, down to the heart, there is also a part of me that wanted to experience more different lifestyles and see more of the world while I am still young. Thanksfully, the flexibility of my work allows me to work remotely and be in different timezones. In addition, the conferences held all over the world also made it easier for me to travel and go back to catch up with friends.</p>
<p>After careful consideration, I decided to take the leap and give Japan a try - checking off another item on my life's bucket list.</p>
<p>So here I am.</p>
<p>Right on the second day I arrived, <ruby lang="ja">東京<rp>(</rp><rt>Tokyo</rt><rp>)</rp></ruby> gave me an amazing welcoming surprise - a rare heavy snowfall in March!</p>
<PhotoHelloTokyo2 />
<p>Shortly after, the sakura season began. One of the things I love most about Tokyo is how cherry blossoms can be found almost everywhere, painting the spring with so much of romantic vibes.</p>
<PhotoHelloTokyo3 />
<p>It's now my third week here, and the time has been incredibly fulfilling. Beyond setting up my apartment and getting settled, I got to see many old friends and meet quite some new ones. I attended the <a href="https://vuejs-meetup.connpass.com/">v-tokyo</a> and <a href="https://www.meetup.com/phpxtky/">PHP x Tokyo</a> meetups in person, and enjoyed several <ruby lang="ja">飲み会<rp>(</rp><rt>drinking party</rt><rp>)</rp></ruby> with many nice and welcoming friends. While I've visited Japan for travel several times before, living here is a completely different experience with its own unique challenges.</p>
<p>About the language, while I am still struggling on it, the people here are so friendly and patient, surprisingly, I managed to get over many communication barriers <span op75>(and knowing <ruby lang="ja">漢字<rp>(</rp><rt>kanji</rt><rp>)</rp></ruby> from my Chinese background definitely helps a lot)</span>. I am taking a language school starting from this week, it's interesting to back as a part-time student after so many years.</p>
<PhotoHelloTokyo4 />
<p>It was indeed quite overwhelming at the beginning to deal with so many things at the same time. I'm grateful for the tremendous help from many friends who made it possible for me to settle in quickly and establish a routine in this new environment.</p>
<h2>Thanks</h2>
<p>I am incredibly grateful to {@Atinux} and {@NuxtLabs} for their unwavering support during my move, making this transition possible. Thanks to {@kazupon} {@ubugeeei} {@nozomuikuta} {@448jp} and the entire {@vuejs-jp} community for their warm welcome (<ruby>I might keep bothering for a while<rp>(</rp><rt>これからもよろしくお願いします！</rt><rp>)</rp></ruby>). Big thanks to {@privatenumber} for assisting with the logistics, {@sxzz} for traveling all the way from Hangzhou to help me settle in, and れい<span text-xs>さん</span> for helping me found this perfect apartment and managing the entire process.</p>
<p>Lastly, a special thanks to {@hannoeru}, who supported me in almost every aspect of preparing for and during this move. Without the help from him, I might still be wandering the streets right now 🤣.</p>
<h2>Please Reach Out</h2>
<p>I'll usually be in between <ruby lang="ja">新宿<rp>(</rp><rt>Shinjuku</rt><rp>)</rp></ruby> and <ruby lang="ja">秋葉原<rp>(</rp><rt>Akihabara</rt><rp>)</rp></ruby> during weekdays. If you live in Tokyo or come by to visit, please don't hesitate to reach out! I'd love to meet and chat! I'm eager to learn more about Japan and the people here (or if you're visiting, I'd be happy to show you around!).</p>
<p>You can message me on SNS, or send me an email at <code>ohayo@antfu.me</code>, looking forward to that! 🌸</p>
<p>Thanks for reading!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Photos Page]]></title>
            <link>https://antfu.me/posts/photos-page</link>
            <guid isPermaLink="true">https://antfu.me/posts/photos-page</guid>
            <pubDate>Wed, 12 Mar 2025 12:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>As you might have noticed, I added a <a href="/photos">photos page</a> on this site recently. It's something I wanted to do for a very long time but always procrastinated on.</p>
<p>During certain periods of my life, photography was my greatest passion, just as much as I do today with Open Source and programming. Instagram was once a delightful, minimalist platform for photo sharing that I frequently used - until Meta's acquisition changed everything. While I could even tolerate the algorithms, ads, and short videos, the recent <a href="https://www.standard.co.uk/news/tech/instagram-update-how-adjust-profile-grid-what-changes-coming-b1205890.html">change of profile photo grids' aspect ratio from square to 4:5</a> was the final straw - an arrogant decision that impacts every user's content without providing proper solutions.</p>
<p>I am not sure if anger or disappointment are the right words to describe my feelings. But I'm certainly lucky to be a frontend developer - so I can leverage my skills to build my own website and host my photos here.</p>
<p>I <a href="https://accountscenter.instagram.com/info_and_permissions/dyi/">requested to download all my data from Instagram</a> (it took roughly a day to process in my case), and imported them to the website. Thankfully, the downloaded data was relatively easy to process, with photos dating back to 2015. I use <a href="https://github.com/lovell/sharp"><code>sharp</code></a> to process the images and compress them with <a href="https://github.com/antfu/antfu.me/blob/main/scripts/photos-manage.ts">this script</a>. This automation helps me manage the photos without worrying about image sizes for hosting.</p>
<p>Looking through these old photos brings back so many memories. While some may not meet my current standards - and I admittedly feel a bit embarrassed sharing them - they hold too many precious memories to leave behind. So I decided to keep most of them - hope you won't look too closely at them :P</p>
<p>Here are some of <a href="/photos">my recent photos</a>:</p>
<div mb-8>
  <PhotoGalleryAll :limit="12" class="gap-1!" />
</div>
<p>It's a shame that the image quality from Instagram isn't great, since they compress photos heavily upon posting. I might replace some of them with higher quality originals in the future, but for now, I think it's a good start.</p>
<p>That said, I hope to get back into regularly sharing photos (as I keep saying 😅), especially now that I have my own platform for it.</p>
<p>Thanks for reading! And I hope you find <a href="/photos">my photos</a> interesting. Cheese 🧀!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Async, Sync, in Between]]></title>
            <link>https://antfu.me/posts/async-sync-in-between</link>
            <guid isPermaLink="true">https://antfu.me/posts/async-sync-in-between</guid>
            <pubDate>Mon, 03 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[The coloring problem in modern programming, and a proposal of a new approach]]></description>
            <content:encoded><![CDATA[<h2>The Coloring Problem</h2>
<p>In modern programming, the function coloring problem isn't new. Based on how functions execute: <code important-text-blue>synchronous</code> (blocking) and <code important-text-rose>asynchronous</code> (non-blocking), we often classify them into two &quot;colors&quot; for better distinction. The problem arises because you generally cannot mix and match these colors freely.</p>
<p>For instance, in JavaScript:</p>
<ul>
<li>An <span text-rose>async</span> function can call both <span text-blue>sync</span> and other <span text-rose>async</span> functions.</li>
<li>A <span text-blue>sync</span> function, however, cannot directly call an <span text-rose>async</span> function without changing its own color to async.</li>
</ul>
<p>This restriction forces developers to propagate the &quot;color&quot; throughout their codebase. If a function deep in your logic needs to become async, it forces every caller up the chain to also become async, leading to a cascading effect (or &quot;async inflection&quot;). This makes refactoring harder, increases complexity, and sometimes leads to awkward workarounds like blocking async calls with await inside sync contexts, or vice versa.</p>
<figure>
  <QuansyncGraph1 />
  <figcaption text-center>
If `loadFile()` needs to be async, then all its callers upstream need to change to async too.
  </figcaption>
</figure>
<p>We often discuss the async inflection problem, where a common solution is to make everything async since async functions can call both sync and async functions, while the reverse is not true. However, the coloring problem actually goes both ways, which seems to be less frequently discussed:</p>
<p>While an async function requires all the <strong>callers</strong> to be async, a sync function also requires all the <strong>dependencies</strong> to be sync.</p>
<figure>
  <QuansyncGraph2 />
  <figcaption text-center>
If `parse()` needs to be sync, then all its dependencies down the road need to be sync too.
  </figcaption>
</figure>
<p>At its core, it's the same problem with different perspectives. It depends on which part of the code you're focusing on and how difficult it is to change its &quot;color.&quot; If the function you're working on <strong>must be async</strong>, the burden shifts to the callers. Conversely, if it <strong>must be sync</strong>, you'll need all your dependencies to be sync or provide a synchronous entry point.</p>
<h3>Libraries in Practice</h3>
<p>For example, the widely used library <a href="https://github.com/sindresorhus/find-up"><code>find-up</code></a> provides two main APIs, <code important-text-rose>findUp</code> and <code important-text-blue>findUpSync</code>, to avoid dependents being trapped by the coloring problem. If you look into the code, you'll find that the package essentially <a href="https://github.com/sindresorhus/find-up/blob/b733bb70d3aa21b22fa011be8089110d467c317f/index.js#L51">duplicates the logic twice</a> to provide the two APIs. Going down, you see its dependency <a href="https://github.com/sindresorhus/locate-path"><code>locate-path</code></a> also <a href="https://github.com/sindresorhus/locate-path/blob/355a681456d79a8506de11120d56b6e34a0389b5/index.js#L49">duplicates the <code important-text-rose>locatePath</code> and <code important-text-blue>locatePathSync</code> logic</a>.</p>
<p>Say you want to build another library that uses <code>findUp</code>, like <code>readNearestPkg</code>, you would also have to write the logic twice, using <code important-text-rose>findUp</code> and <code important-text-blue>findUpSync</code> separately, to support both async and sync usage.</p>
<p>In these cases, even if our main logic does not come with its own &quot;colors,&quot; the whole dependency pipeline is forced to branch into two colors due to an optional async operation down the road (e.g., <code important-text-rose>fs.promises.stat</code> and <code important-text-blue>fs.statSync</code>).</p>
<figure>
  <QuansyncGraph3 />
  <figcaption text-center>
Basically, we would maintain two branches of code to support both sync and async, with only a few sync utils that can be shared.
  </figcaption>
</figure>
<h3>Async Plugins</h3>
<p>Another case demonstrating the coloring problem is a plugin system with async hooks. For example, imagine we are building a Markdown-to-HTML compiler with plugin support. Say the parser and compiler logic are synchronous; we could expose a sync API like:</p>
<pre><code class="language-ts">export function markdownToHtml(markdown) {
  const ast = parse(markdown)
  // ...
  return render(ast)
}
</code></pre>
<p>To make our library extensible, we might allow plugins to register hooks at multiple stages thoughout the process, for example:</p>
<pre><code class="language-ts">export interface Plugin {
  preprocess: (markdown: string) =&gt; string
  transform: (ast: AST) =&gt; AST
  postprocess: (html: string) =&gt; string
}

export function markdownToHtml(markdown, plugins) {
  for (const plugin of plugins) {
    markdown = plugin.preprocess(markdown) // [!code hl]
  }
  let ast = parse(markdown)
  for (const plugin of plugins) {
    ast = plugin.transform(ast) // [!code hl]
  }
  let html = render(ast)
  for (const plugin of plugins) {
    html = plugin.postprocess(html) // [!code hl]
  }
  return html
}
</code></pre>
<p>Great, now we have a plugin system. However, having <code>markdownToHtml</code> as a synchronous function essentially limits all plugin hooks to be synchronous as well. This limitation can be quite restrictive. For instance, consider a plugin for syntax highlighting. In many cases, the best results for syntax highlighting might require asynchronous operations, such as fetching additional resources or performing complex computations that are better suited for non-blocking execution.</p>
<p>To accommodate such scenarios, we need to allow <span text-rose>async hooks</span> in our plugin system. This means that our main function, <code>markdownToHtml</code>, as the caller of the plugin hooks must also be async. We could implement it like this:</p>
<pre><code class="language-ts">// [!code word:Promise]
// [!code word:async]
// [!code word:await]
export interface Plugin {
  preprocess: (markdown: string) =&gt; string | Promise&lt;string&gt;
  transform: (ast: AST) =&gt; AST | Promise&lt;AST&gt;
  postprocess: (html: string) =&gt; string | Promise&lt;string&gt;
}

export async function markdownToHtml(markdown, plugins) { // [!code hl]
  for (const plugin of plugins) {
    markdown = await plugin.preprocess(markdown) // [!code hl]
  }
  let ast = parse(markdown)
  for (const plugin of plugins) {
    ast = await plugin.transform(ast) // [!code hl]
  }
  let html = render(ast)
  for (const plugin of plugins) {
    html = await plugin.postprocess(html) // [!code hl]
  }
  return html
}
</code></pre>
<p>While this maximized the flexibility of the plugin system, this approach also <strong>forces</strong> all users to handle the process <span text-rose>asynchronously</span>, even in the cases where all plugins are synchronous. This is the cost of accommodating the possibility that some operations &quot;<b important-text-purple>might be asynchronous</b>&quot;. To manage this, we often end up duplicating the logic to offer both sync and async APIs, and restrict async plugins to the async version only.</p>
<p>Such duplications lead to increased maintenance efforts, potential inconsistencies, and larger bundle sizes, which are not ideal for maintainers or users.</p>
<p>Is there a better way to handle this?</p>
<h2>Introducing Quansync</h2>
<p>What if we could make our logic decoupled from the coloring problem and let the caller decide the color?</p>
<p>Trying to make the situation a bit better, {@sxzz} and I took inspiration from <a href="https://github.com/loganfsmyth/gensync"><code>gensync</code></a> by {@loganfsmyth} and made a package called <a href="https://github.com/antfu-collective/quansync"><code important-text-purple>quansync</code></a>. Taking it even further, we are dreaming of leveraging this to create a paradigm shift in the way we write libraries in the JavaScript ecosystem.</p>
<p>The name <code important-text-purple>Quansync</code> is borrowed from <a href="https://en.wikipedia.org/wiki/Quantum_mechanics">Quantum Mechanics</a>, where particles can exist in multiple states simultaneously, known as <em>superposition</em>, and only settle into a single state when observed <span op50>(try hovering over the atom below)</span>.</p>
<AsyncSyncQuantum />
<p>You can think of <code important-text-purple>quansync</code> as a new type of function that can be used as both <code important-text-blue>sync</code> and <code important-text-rose>async</code> depending on the context. In many cases, our logic can escape the async inflection problem, especially when designing shared logic with optional async hooks.</p>
<figure>
  <QuansyncGraph4 />
  <figcaption text-center>
Try hovering over either the async or sync side.<br>
Quansync functions are in purple, which can adapt to either sync or async.
  </figcaption>
</figure>
<h3>Usage Examples</h3>
<p>Quansync provides a single API with two overloads.</p>
<h4>Wrapper API</h4>
<p>Wrapper allows you to create a quansync function by providing a sync and an async implementation. For example:</p>
<pre><code class="language-ts">import fs from 'find-up'
import { quansync } from 'quansync'

export const readFile = quansync({
  sync: filepath =&gt; fs.readFileSync(filepath),
  async: filepath =&gt; fs.promises.readFile(filepath),
})
</code></pre>
<pre><code class="language-ts">const content1 = readFile.sync('package.json')
const content2 = await readFile.async('package.json')

// The quansync function itself can behave like a normal async function
const content3 = await readFile('package.json')
</code></pre>
<h4>Generator API</h4>
<p>Generator is where the magic happens. It allows you to create a <code>quansync</code> function by using other <code>quansync</code> functions. For example:</p>
<pre><code class="language-ts">import { quansync } from 'quansync'

export const readFile = quansync({
  sync: filepath =&gt; fs.readFileSync(filepath),
  async: filepath =&gt; fs.promises.readFile(filepath),
})

// Create a quansync with `function*` and `yield*`
// [!code word:function*:1]
export const readJSON = quansync(function* (filepath) {
  // Call the quansync function directly
  // and use `yield*` to get the result.
  // Upon usage, it will auto select the implementation
  // [!code word:yield*:1]
  const content = yield* readFile(filepath)
  return JSON.parse(content)
})
</code></pre>
<pre><code class="language-ts">// fs.readFileSync will be used under the hood
const pkg1 = readJSON.sync('package.json')
// fs.promises.readFile will be used under the hood
const pkg2 = await readJSON.async('package.json')
</code></pre>
<h3>Build-time Macros</h3>
<p>If the <code>function*</code> and <code>yield*</code> syntax scares you a bit, {@sxzz} also made a build-time macro <a href="https://github.com/quansync-dev/unplugin-quansync"><code>unplugin-quansync</code></a> allowing you to write normal <code>async</code>/<code>await</code> syntax, and it will be transformed to the corresponding <code>yield*</code> syntax at build time.</p>
<pre><code class="language-ts">// [!code word:quansync/macro]
import { quansync } from 'quansync/macro'

// Use async/await syntax
// They will be transformed to `function*` and `yield*` at build time
export const readJSON = quansync(async (filepath) =&gt; {
  const content = await readFile(filepath)
  return JSON.parse(content)
})

// Expose the classical sync API
export const readJSONSync = readJSON.sync
</code></pre>
<p>Thanks to <a href="https://github.com/unjs/unplugin"><code>unplugin</code></a>, it can work in almost any build tool, like compiling with <code>unbuild</code> or testing with <code>vitest</code>. Please refer to <a href="https://github.com/quansync-dev/unplugin-quansync">the docs</a> for more detailed setup.</p>
<h2>How does it Work?</h2>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator">Generators</a> in JavaScript are a powerful yet often underutilized feature. To define a generator function, you use the <code>function*</code> syntax (note that arrow functions do not support generators). Inside a generator function, you can use the <code>yield</code> keyword to pause execution and return a value. This effectively splits your logic into multiple &quot;chunks,&quot; allowing the caller to control when to execute the next chunk.</p>
<p>By leveraging this behavior, we can pause execution at each <code>yield</code> point. In an asynchronous context, we can wait for the async operation to complete before resuming execution. In a synchronous context, the next chunk runs immediately. This approach offloads the coloring problem to the caller, allowing them to decide whether the function should run synchronously or asynchronously.</p>
<p>In fact, during the early days of JavaScript, before the <code>async</code> and <code>await</code> keywords were widely adopted, Babel used generators and <code>yield</code> to polyfill async behavior. While this technique isn't new, we believe it has significant potential to improve how we handle the coloring problem, especially in library design.</p>
<h2>When not to Use?</h2>
<p>Frankly, I wish most of time you don't even need to think about it. High-level tools should support async entry points for most cases, where choice <code>sync</code> and <code>async</code> is not a problem. However, there are still many cases where in the context, it's required to be colored. In such cases, <code>quansync</code> could be a good fit for progressive and gradual adoption.</p>
<p>Promise in JavaScript naturally a <a href="https://javascript.info/event-loop">microtask</a> that delays a tick. <code>yield</code> also introduce certain overhead (<a href="https://github.com/quansync-dev/quansync#benchmark">around <code>~120ns</code> on M1 Max</a>). In performance-sensitive scenarios, you might also want to avoid using either <code>async</code>or <code>quansync</code>.</p>
<h2>Coloring Problem Revisited</h2>
<p>While <code>quansync</code> doesn't completely solve the coloring problem, it provides a new perspective that simplifies managing synchronous and asynchronous code. Quansync introduces a new <span text-purple>&quot;purple&quot;</span> color, blending the red and blue. Quansync functions still face the coloring problem, as wrapping a function to support both sync and async requires it to be a quansync function (or generator). However, the key advantage is that a quansync function can be <a href="https://en.wikipedia.org/wiki/Wave_function_collapse">&quot;collapsed&quot;</a> to either sync or async as needed. This allows your &quot;colorless&quot; logic to avoid the red and blue color inflection caused by some operations that might have a color.</p>
<h2>Conclusion</h2>
<p>This is a new approach to tackling the coloring problem we are still exploring. We will slowly roll out <code>quansync</code> in our libraries and see how it improves our experience and the ecosystem. We are also<br>
looking for feedback and contributions, so feel free to join us in the <a href="https://chat.antfu.me">Discord</a> or <a href="https://github.com/quansync-dev/quansync/discussions">GitHub Discussions</a> to share your thoughts.</p>
<ul>
<li>
<GitHubLink repo="quansync-dev/quansync" />
</li>
<li>
<GitHubLink repo="quansync-dev/unplugin-quansync" />
</li>
</ul>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Move on to ESM-only]]></title>
            <link>https://antfu.me/posts/move-on-to-esm-only</link>
            <guid isPermaLink="true">https://antfu.me/posts/move-on-to-esm-only</guid>
            <pubDate>Wed, 05 Feb 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Let's move on to ESM-only]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<p>Three years ago, I wrote a post about <a href="/posts/publish-esm-and-cjs">shipping ESM &amp; CJS in a single package</a>, advocating for dual CJS/ESM formats to ease user migration and trying to make the best of both worlds. Back then, I didn't fully agree with <a href="https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c">aggressively shipping ESM-only</a>, as I considered the ecosystem wasn't ready, especially since the push was mostly from low-level libraries. Over time, as tools and the ecosystem have evolved, my perspective has gradually shifted towards more and more on adopting ESM-only.</p>
<p>As of 2025, a decade has passed since ESM was first introduced in 2015. Modern tools and libraries have increasingly adopted ESM as the primary module format. According to {@wooorm}'s <a href="https://github.com/wooorm/npm-esm-vs-cjs">script</a>, the packages that ships ESM on npm in 2021 was <strong>7.8%</strong>, and by the end of 2024, it had reached <a href="https://github.com/wooorm/npm-esm-vs-cjs"><strong>25.8%</strong></a>. Although a significant portion of packages still use CJS, the trend clearly shows a good shift towards ESM.</p>
<figure>
  <img src="https://antfu.me/images/npm-esm-vs-cjs-2024.svg" dark:filter-invert />
  <figcaption text-center>ESM adoption over time, generated by the <code>npm-esm-vs-cjs</code> script. Last updated at 2024-11-27</figcaption>
</figure>
<p>Here in this post, I'd like to share my thoughts on the current state of the ecosystem and why I believe it's time to move on to ESM-only.</p>
<h2>The Toolings are Ready</h2>
<h3>Modern Tools</h3>
<p>With the rise of <a href="https://vite.dev">Vite</a> as a popular modern frontend build tool, many meta-frameworks like <a href="https://nuxtjs.org">Nuxt</a>, <a href="https://kit.svelte.dev">SvelteKit</a>, <a href="https://astro.build">Astro</a>, <a href="https://solidstart.dev">SolidStart</a>, <a href="https://remix.run">Remix</a>, <a href="https://storybook.js.org">Storybook</a>, <a href="https://redwoodjs.com">Redwood</a>, and many others are all built on top of Vite nowadays, that <strong>treating ESM as a first-class citizen</strong>.</p>
<p>As a complement, we have also testing library <a href="https://vitest.dev">Vitest</a>, which was designed for ESM from the day one with powerful module mocking capability and efficient fine-grain caching support.</p>
<p>CLI tools like <a href="https://github.com/privatenumber/tsx"><code>tsx</code></a> and <a href="https://github.com/unjs/jiti"><code>jiti</code></a> offer a seamless experience for running TypeScript and ESM code without requiring additional configuration. This simplifies the development process and reduces the overhead associated with setting up a project to use ESM.</p>
<p>Other tools, for example, <a href="https://eslint.org">ESLint</a>, in the recent v9.0, introduced a new flat config system that enables native ESM support with <code>eslint.config.mjs</code>, even in CJS projects.</p>
<h3>Top-Down &amp; Bottom-Up</h3>
<p>Back in 2021, when {@sindresorhus} first started migrating all his packages to ESM-only, for example, <code>find-up</code> and <code>execa</code>, it was a bold move. I consider this move as a <strong>bottom-up</strong> approach, as the packages that rather low-level and many their dependents are not ready for ESM yet. I was worried that this would force those dependents to stay on the old version of the packages, which might result in the ecosystem being fragmented. (As of today, I actually appreciate that move bringing us quite a lot of high-quality ESM packages, regardless that the process wasn't super smooth).</p>
<p>It's way easier for an ESM or Dual formats package to depend on CJS packages, but not the other way around. In terms of smooth adoption, I believe the <strong>top-down</strong> approach is more effective in pushing the ecosystem forward. With the support of high-level frameworks and tools from top-down, it's no longer a significant obstacle to use ESM-only packages. The remaining challenges in terms of ESM adoption primarily lie with package authors needing to migrate and ship their code in ESM format.</p>
<h3>Requiring ESM in Node.js</h3>
<p>The <a href="https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/">capability to <code>require()</code> ESM modules</a> in Node.js, <a href="https://github.com/nodejs/node/pull/51977">initiated</a> by {@joyeecheung}, marks an <strong>incredible milestone</strong>. This feature allows packages to be published as ESM-only while still being consumable by CJS codebases with minimal modifications. It helps avoid the <a href="/posts/async-sync-in-between">async infection</a> (also known as <a href="https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/">Red Functions</a>) introduced by dynamic <code>import()</code> ESM, which can be pretty hard, if not impossible in some cases, to migrate and adapt.</p>
<p>This feature was recently <a href="https://github.com/nodejs/node/pull/55085">unflagged</a> and <a href="https://github.com/nodejs/node/pull/55217">backported to Node.js v22</a> (<a href="https://github.com/nodejs/node/pull/56927">and soon v20</a>), which means it should be available to many developers already. Consider the <a href="#top-down--bottom-up">top-down or bottom-up</a> metaphor, this feature actually makes it possible to start ESM migration also from <strong>middle-out</strong>, as it allows import chains like <code>ESM → CJS → ESM → CJS</code> to work seamlessly.</p>
<p>To solve the interop issue between CJS and ESM in this case, <a href="https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require">Node.js also introduced</a> a new <code>export { Foo as 'module.exports' }</code> syntax in ESM to export CJS-compatible exports (by <a href="https://github.com/nodejs/node/pull/54563">this PR</a>). This allows package authors to publish ESM-only packages while still supporting CJS consumers, without even introducing breaking changes (expcet for changing the required Node.js version).</p>
<p>For more details on the progress and discussions around this feature, keep track on <a href="https://github.com/nodejs/node/issues/52697">this issue</a>.</p>
<h2>The Troubles with Dual Formats</h2>
<p>While dual CJS/ESM packages have been a quite helpful transition mechanism, they come with their own set of challenges. Maintaining two separate formats can be cumbersome and error-prone, especially when dealing with complex codebases. Here are some of the issues that arise when maintaining dual formats:</p>
<h3>Interop Issues</h3>
<p>Fundamentally, CJS and ESM are different module systems with distinct design philosophies. Although Node.js has made it possible to import CJS modules in ESM, dynamically import ESM in CJS, and even <code>require()</code> ESM modules, there are still many tricky cases that can lead to interop issues.</p>
<p>One key difference is that CJS typically uses a single <code>module.exports</code> object, while ESM supports both default and named exports. When authoring code in ESM and transpiling to CJS, handling exports can be particularly challenging, especially when the exported value is a non-object, such as a function or a class. Additionally, to make the types correct, we also need to introduce further complications with <code>.d.mts</code> and <code>.d.cts</code> declaration files. And so on...</p>
<p>As I am trying to explain this problem deeper, I found that I actually wish you didn't even need to be bothered with this problem at all. It's frankly too complicated and frustrating. If you are just a user of packages, let alone the package authors to worry about that. This is one of the reasons I advocate for the entire ecosystem to transition to ESM, to leave these problems behind and spare everyone from this unnecessary hassle.</p>
<h3>Dependency Resolution</h3>
<p>When a package has both CJS and ESM formats, the resolution of dependencies can become convoluted. For example, if a package depends on another package that only ships ESM, the consumer must ensure that the ESM version is used. This can lead to version conflicts and dependency resolution issues, especially when dealing with transitive dependencies.</p>
<p>Also for packages that are designed to used with singleton pattern, this might introduce multiple copies of the same package and cause unexpected behaviors.</p>
<h3>Package Size</h3>
<p>Shipping dual formats essentially doubles the package size, as both CJS and ESM bundles need to be included. While a few extra kilobytes might not seem significant for a single package, the overhead can quickly add up in projects with hundreds of dependencies, leading to the infamous node_modules bloat. Therefore, package authors should keep an eye on their package size. Moving to ESM-only is a way to optimize it, especially if the package doesn't have strong requirements on CJS.</p>
<h2>When Should We Move to ESM-only?</h2>
<p>This post does not intend to diminish the value of dual-format publishing. Instead, I want to encourage evaluating the current state of the ecosystem and the potential benefits of transitioning to ESM-only.</p>
<p>There are several factors to consider when deciding whether to move to ESM-only:</p>
<h3>New Packages</h3>
<p>I strongly recommend that <strong>all new packages</strong> be released as ESM-only, as there are no legacy dependencies to consider. New adopters are likely already using a modern, ESM-ready stack, there being ESM-only should not affect the adoption. Additionally, maintaining a single module system simplifies development, reduces maintenance overhead, and ensures that your package benefits from future ecosystem advancements.</p>
<h3>Browser-targeted Packages</h3>
<p>If a package is primarily targeted for the browser, it makes total sense to ship ESM-only. In most cases, browser packages go through a bundler, where ESM provides significant advantages in static analysis and tree-shaking. This leads to smaller and more optimized bundles, which would also improve loading performance and reduce bandwidth consumption for end users.</p>
<h3>Standalone CLI</h3>
<p>For a standalone CLI tool, it's no difference to end users whether it's ESM or CJS. However, using ESM would enable your dependencies to also be ESM, facilitating the ecosystem's transition to ESM from a <a href="#top-down--bottom-up">top-down approach</a>.</p>
<h3>Node.js Support</h3>
<p>If a package is targeting the evergreen Node.js versions, it's a good time to consider ESM-only, especially with the recent <a href="#requiring-esm-in-nodejs"><code>require(ESM)</code> support</a>.</p>
<h3>Know Your Consumers</h3>
<p>If a package already has certain users, it's essential to understand the dependents' status and requirements. For example, for an ESLint plugin/utils that requires ESLint v9, while ESLint v9's new config system supports ESM natively even in CJS projects, there is no blocker for it to be ESM-only.</p>
<p>Definitely, there are different factors to consider for different projects. But in general, I believe the ecosystem is ready for more packages to move to ESM-only, and it's a good time to evaluate the benefits and potential challenges of transitioning.</p>
<h2>How Far We Are?</h2>
<p>The transition to ESM is a gradual process that requires collaboration and effort from the entire ecosystem. Which I believe we are on a good track moving forward.</p>
<p>To improve the transparency and visibility of the ESM adoption, I recently built a visualized tool called <a href="https://github.com/antfu/node-modules-inspector">Node Modules Inspector</a> for analyzing your packages's dependencies. It provides insights into the ESM adoption status of your dependencies and helps identify potential issues when migrating to ESM.</p>
<p>Here are some screenshots of the tool to give you a quick impression:</p>
<figure>
  <img src="/images/node-modules-inspector-1.png" scale-110 />
  <figcaption text-center>Node Modules Inspector - Overview</figcaption>
</figure>
<figure>
  <img src="/images/node-modules-inspector-2.png" scale-110 />
  <figcaption text-center>Node Modules Inspector - Dependency Graph</figcaption>
</figure>
<figure>
  <img src="/images/node-modules-inspector-3.png" scale-110 />
  <figcaption text-center>Node Modules Inspector - Reports like ESM Adoptions and Duplicated Packages</figcaption>
</figure>
<p>This tool is still in its early stages, but I hope it will be a valuable resource for package authors and maintainers to track the ESM adoption progress of their dependencies and make informed decisions about transitioning to ESM-only.</p>
<p>To learn more about how to use it and inspect your projects, check the repository <GitHubLink repo="antfu/node-modules-inspector" />.</p>
<h2>Moving Forward</h2>
<p>I am planning to gradually transition the packages I maintain to ESM-only and take a closer look at the dependencies we rely on. We also have plenty of exciting ideas for the Node Modules Inspector, aiming to provide more useful insights and help find the best path forward.</p>
<p>I look forward to a more portable, resilient, and optimized JavaScript/TypeScript ecosystem.</p>
<p>I hope this post has shed some light on the benefits of moving to ESM-only and the current state of the ecosystem. If you have any thoughts or questions, feel free to reach out using the links below. Thank you for reading!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Epoch Semantic Versioning]]></title>
            <link>https://antfu.me/posts/epoch-semver</link>
            <guid isPermaLink="true">https://antfu.me/posts/epoch-semver</guid>
            <pubDate>Tue, 07 Jan 2025 12:00:00 GMT</pubDate>
            <description><![CDATA[Proposal for an extended Semantic Versioning called Epoch SemVer to provide more granular versioning information to users.]]></description>
            <content:encoded><![CDATA[<p>If you've been following my work in open source, you might have noticed that I have a tendency to stick with zero-major versioning, like <code>v0.x.x</code>. For instance, as of writing this post, the latest version of UnoCSS is <a href="https://github.com/unocss/unocss/releases/tag/v0.65.3"><code>v0.65.3</code></a>, Slidev is <a href="https://github.com/slidevjs/slidev/releases/tag/v0.50.0"><code>v0.50.0</code></a>, and <code>unplugin-vue-components</code> is <a href="https://github.com/unplugin/unplugin-vue-components/releases/tag/v0.28.0"><code>v0.28.0</code></a>. Other projects, such as React Native is on <a href="https://github.com/facebook/react-native/releases/tag/v0.76.5"><code>v0.76.5</code></a>, and sharp is on <a href="https://github.com/lovell/sharp/releases/tag/v0.33.5"><code>v0.33.5</code></a>, also follow this pattern.</p>
<p>People often assume that a zero-major version indicates that the software is not ready for production. However, all of the projects mentioned here are quite stable and production-ready, used by millions of projects.</p>
<p><strong>Why?</strong> - I bet that's your question reading this.</p>
<h2>Versioning</h2>
<p>Version numbers act as snapshots of our codebase, helping us communicate changes effectively. For instance, we can say &quot;it works in v1.3.2, but not in v1.3.3, there might be a regression.&quot; This makes it easier for maintainers to locate bugs by comparing the differences between these versions. A version is essentially a marker, a seal of the codebase at a specific point in time.</p>
<p>However, code is complex, and every change involves trade-offs. Describing how a change affects the code can be tricky even with natural language. A version number alone can't capture all the nuances of a release. That's why we have changelogs, release notes, and commit messages to provide more context.</p>
<p>I see versioning as a way to communicate changes to users — a <strong>contract</strong> between the library maintainers and the users to ensure compatibility and stability during upgrades. As a user, you can't always tell what's changed between <code>v2.3.4</code> and <code>v2.3.5</code> without checking the changelog. But by looking at the numbers, you can infer that it's a patch release meant to fix bugs, which <strong>should</strong> be safe to upgrade. This ability to understand changes just by looking at the version number is possible because both the library maintainer and the users agree on the versioning scheme.</p>
<p>Since versioning is only a contract, and could be interpreted differently to each specific project, you shouldn't blindly trust it. It serves as an indication to help you decide when to take a closer look at the changelog and be cautious about upgrading. But it's not a guarantee that everything will work as expected, as every change might introduce behavior changes, whether it's intended or not.</p>
<h2>Semantic Versioning</h2>
<p>In the JavaScript ecosystem, especially for packages published on npm, we follow a convention known as <a href="https://semver.org/">Semantic Versioning</a>, or SemVer for short. A SemVer version number consists of three parts: <code>MAJOR.MINOR.PATCH</code>. The rules are straightforward:</p>
<ul>
<li><span font-bold font-mono text-amber>MAJOR</span>: Increment when you make incompatible API changes.</li>
<li><span font-bold font-mono text-lime>MINOR</span>: Increment when you add functionality in a backwards-compatible manner.</li>
<li><span font-bold font-mono text-blue>PATCH</span>: Increment when you make backwards-compatible bug fixes.</li>
</ul>
<p>Package managers we use, like <code>npm</code>, <code>pnpm</code>, and <code>yarn</code>, all operate under the assumption that every package on npm adheres to SemVer. When you or a package specifies a dependency with a version range, such as <code>^1.2.3</code>, it indicates that you are comfortable with upgrading to any version that shares the same major version (<code>1.x.x</code>). In these scenarios, package managers will automatically determine the best version to install based on what is most suitable for your specific project.</p>
<p>This convention works well technically. If a package releases a new major version <code>v2.0.0</code>, your package manager won't install it if your specified range is <code>^1.2.3</code>. This prevents unexpected breaking changes from affecting your project until you manually update the version range.</p>
<p>However, humans perceive numbers on a logarithmic scale. We tend to see <code>v2.0</code> to <code>v3.0</code> as a huge, groundbreaking change, while <code>v125.0</code> to <code>v126.0</code> seems a lot more trivial, even though both indicate incompatible API changes in SemVer. This perception can make maintainers hesitant to bump the major version for minor breaking changes, leading to the accumulation of many breaking changes in a single major release, making upgrades harder for users. Conversely, with something like <code>v125.0</code>, it becomes difficult to convey the significance of a major change, as the jump to <code>v126.0</code> appears minor.</p>
<blockquote>
<p>{@TkDodo|Dominik Dorfmeister} had <a href="https://tkdodo.eu/blog/react-query-api-design-lessons-learned">a great talk about API Design</a>, which mentions an interesting inequality that descripting this: <a href="https://tkdodo.eu/blog/react-query-api-design-lessons-learned?page=30">&quot;Breaking Changes !== Marketing Event&quot;</a></p>
</blockquote>
<h2>Progressive</h2>
<p>I am a strong believer in the principle of progressiveness. Rather than making a giant leap to a significantly higher stage all at once, progressiveness allows users to adopt changes gradually at their own pace. It provides opportunities to pause and assess, making it easier to understand the impact of each change.</p>
<figure text-center>
  <img src="https://antfu.me/images/epoch-semver-progressive-1.png" alt="Progressive as Stairs" border="~ base rounded-xl">
  <figcaption>Progressive as Stairs - a screenshot of my talk <a italic font-serif href="/talks#the-progressive-path" target="_blank">The Progressive Path</a></figcaption>
</figure>
<p>I believe we should apply the same principle to versioning. Instead of treating a major version as a massive overhaul, we can break it down into smaller, more manageable updates. For example, rather than releasing <code>v2.0.0</code> with 10 breaking changes from <code>v1.x</code>, we could distribute these changes across several smaller major releases. This way, we might release <code>v2.0</code> with 2 breaking changes, followed by <code>v3.0</code> with 1 breaking change, and so on. This approach makes it easier for users to adopt changes gradually and reduces the risk of overwhelming them with too many changes at once.</p>
<figure text-center>
  <img src="/images/epoch-semver-progressive-2.png" alt="Progressive on Breaking Changes" border="~ base rounded-xl">
  <figcaption>Progressive on Breaking Changes - a screenshot of my talk <a italic font-serif href="/talks#the-progressive-path" target="_blank">The Progressive Path</a></figcaption>
</figure>
<h2>Leading Zero Major Versioning</h2>
<p>The reason I've stuck with <code>v0.x.x</code> is my own unconventional approach to versioning. I prefer to introduce necessary and minor breaking changes early on, making upgrades easier, without causing alarm that typically comes with major version jumps like <code>v2</code> to <code>v3</code>. Some changes might be &quot;technically&quot; breaking but don't impact 99.9% of users in practice. (Breaking changes are relative. Even a bug fix can be breaking for those relying on the previous behavior, but that's another topic for discussion :P).</p>
<p>There's a special rule in SemVer that states <strong>when the leading major version is <code>0</code>, every minor version bump is considered breaking</strong>. I am kind of <strong>abusing</strong> that rule to workaround the limitation of SemVer. With zero-major versioning, we are effectively abandoning the first number, and merge <code>MINOR</code> and <code>PATCH</code> into a single number (thanks to <a href="https://x.com/ssalbdivad">David Blass</a> for pointing <a href="https://x.com/ssalbdivad/status/1876614090623431116">this</a> out):</p>
<div py4>
  <code important="text-xl text-gray"><span line-through>ZERO</span>.<span font-bold text-amber>MAJOR</span>.{<span font-bold text-lime>MINOR</span> + <span font-bold text-blue>PATCH</span>}</code>
</div>
<blockquote>
<p>Of course, zero-major versioning is not the only solution to be progressive. We can see that tools like <a href="https://nodejs.org/en">Node.js</a>, <a href="https://vite.dev/">Vite</a>, <a href="https://vitest.dev/">Vitest</a> are rolling out major versions in consistent intervals, with a minimal set of breaking changes in each release that are easy to adopt. It would require a lot of effort and extra attentions. Kudos to them!</p>
</blockquote>
<p>I have to admit that sticking to <strong>zero-major versioning isn't the best practice.</strong> While I aimed for more granular versioning to improve communication, using zero-major versioning has actually limited the ability to convey changes effectively. In reality, I've been wasting a valuable part of the versioning scheme due to my peculiar insistence.</p>
<p>Thus, here, I am proposing to change.</p>
<h2>Epoch Semantic Versioning</h2>
<p><a href="https://x.com/antfu7/status/1679184417930059777">In an ideal world, I would wish SemVer to have 4 numbers: <code>EPOCH.MAJOR.MINOR.PATCH</code></a>. The <code>EPOCH</code> version is for those big announcements, while <code>MAJOR</code> is for technical incompatible API changes that might not be significant. This way, we can have a more granular way to communicate changes. Similarly, we also have <a href="https://github.com/romversioning/romver">Romantic Versioning that propose <code>HUMAN.MAJOR.MINOR</code></a>. The creator of SemVer, <a href="https://tom.preston-werner.com/">Tom Preston-Werner</a> also <a href="https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred">mentioned similar concerns and solutions in this blog post</a>. (thanks to <a href="https://x.com/sebastienlorber">Sébastien Lorber</a> for pointing <a href="https://x.com/sebastienlorber/status/1879127128530460856">this</a> out).</p>
<p>But, of course, it's too late for the entire ecosystem to adopt a new versioning scheme.</p>
<p>If we can't change SemVer, maybe we can at least extend it. I am proposing a new versioning scheme called <strong>🗿 Epoch Semantic Versioning</strong>, or Epoch SemVer for short. It's built on top of the structure of <code>MAJOR.MINOR.PATCH</code>, extend the first number to be the combination of <code>EPOCH</code> and <code>MAJOR</code>. To put a difference between them, we use a fourth digit to represent <code>EPOCH</code>, which gives <code>MAJOR</code> a range from 0 to 999. This way, it follows the exact same rules as SemVer <strong>without requiring any existing tools to change, but provides more granular information to users</strong>.</p>
<blockquote>
<p>The name &quot;Epoch&quot; is inspired by <a href="https://manpages.debian.org/stretch/dpkg-dev/deb-version.5.en.html">Debian's versioning scheme</a>.</p>
</blockquote>
<p>The format is as follows:</p>
<div py4>
  <code important="text-xl text-gray">{<span font-bold text-violet>EPOCH</span> * 1000 + <span font-bold text-amber>MAJOR</span>}.<span font-bold text-lime>MINOR</span>.<span font-bold text-blue>PATCH</span></code>
</div>
<ul>
<li><span font-bold font-mono text-violet>EPOCH</span>: Increment when you make significant or groundbreaking changes.</li>
<li><span font-bold font-mono text-amber>MAJOR</span>: Increment when you make minor incompatible API changes.</li>
<li><span font-bold font-mono text-lime>MINOR</span>: Increment when you add functionality in a backwards-compatible manner.</li>
<li><span font-bold font-mono text-blue>PATCH</span>: Increment when you make backwards-compatible bug fixes.</li>
</ul>
<blockquote>
<p>I previously proposed to have the EPOCH multiplier to be <code>100</code>, but according to the community feedback, it seems that <code>1000</code> is a more popular choices as it give more room for the <code>MAJOR</code> version and a bit more distinguision between the numbers. The multiplier is not a strict rule, feel free to adjust it based on your needs.</p>
</blockquote>
<p>For example, UnoCSS would transition from <code>v0.65.3</code> to <code>v65.3.0</code> (in the case <code>EPOCH</code> is <code>0</code>). Following SemVer, a patch release would become <code>v65.3.1</code>, and a feature release would be <code>v65.4.0</code>. If we introduced some minor incompatible changes affecting an edge case, we could bump it to <code>v66.0.0</code> to alert users of potential impacts. In the event of a significant overhaul to the core, we could jump directly to <code>v1000.0.0</code> to signal a new era and make a big announcement. I'd suggest assigning a code name to each non-zero <code>EPOCH</code> to make it more memorable and easier to refer to. This approach provides maintainers with more flexibility to communicate the scale of changes to users effectively.</p>
<blockquote>
<p>[!TIP]<br>
We shouldn't need to bump <code>EPOCH</code> often. It's mostly useful for high-level, end-user-facing libraries or frameworks. For low-level libraries, they might <strong>never</strong> need to bump <code>EPOCH</code> at all (<code>ZERO-EPOCH</code> is essentially the same as SemVer).</p>
</blockquote>
<p>Of course, I'm not suggesting that everyone should adopt this approach. It's simply an idea to work around the existing system, and only for those packages with this need. It will be interesting to see how it performs in practice.</p>
<h2>Moving Forward</h2>
<p>I plan to adopt Epoch Semantic Versioning in my projects, including UnoCSS, Slidev, and all the plugins I maintain, and ultimately abandon zero-major versioning for stable packages. I hope this new versioning approach will help communicate changes more effectively and provide users with better context when upgrading.</p>
<p>I'd love to hear your thoughts and feedback on this idea. Feel free to share your comments using the links below!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[CPU Profiling Nuxt]]></title>
            <link>https://antfu.me/posts/nuxt-cpuprofile</link>
            <guid isPermaLink="true">https://antfu.me/posts/nuxt-cpuprofile</guid>
            <pubDate>Tue, 31 Dec 2024 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>If you want to debug the bundling performance of your Nuxt app to generate CPU profiles.</p>
<p>Node.js provides a built-in <a href="https://nodejs.org/api/cli.html#--cpu-prof"><code>--cpu-prof</code></a> flag that allows you to generate CPU profiles. However you can't directly pass it in your <code>nuxi</code> command, you have to use it with <code>node</code> directly.</p>
<p>Instead of running <code>nuxi dev</code>, you can run <code>node</code> with the direct path to the CLI in <code>node_modules</code>:</p>
<pre><code class="language-bash"># nuxi dev
node --cpu-prof ./node_modules/nuxi/bin/nuxi.mjs dev --fork=false
</code></pre>
<p>Note that <code>--fork=false</code> is important as by <a href="https://github.com/nuxt/cli/blob/a433fbcebda8cb87d7c0c8199137877b669e1c31/src/commands/dev.ts#L69-L75">default <code>nuxi</code> will start the Nuxt process in a forked process</a> which will make the CPU profile not working.</p>
<blockquote>
<p>The simliar technique can be applied to other CLI tools that are not directly using <code>node</code> to start the process.</p>
</blockquote>
<p>After killing your Nuxt process, you will find two <code>CPU.***.cpuprofile</code> files generated in your working directory. I recommend using <a href="https://discoveryjs.github.io/cpupro/">CPUpro</a> to visualize the profile. If you are using VS Code, I also created <a href="https://marketplace.visualstudio.com/items?itemName=antfu.cpupro">an extension</a> for you to directly open the <code>.cpuprofile</code> file in VS Code easily.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Introducing Nuxt Icon v1]]></title>
            <link>https://antfu.me/posts/nuxt-icon-v1</link>
            <guid isPermaLink="true">https://antfu.me/posts/nuxt-icon-v1</guid>
            <pubDate>Mon, 25 Nov 2024 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><a href="https://nuxt.com/blog/nuxt-icon-v1-0">Go to nuxt.com and read the full post</a>.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Initiative on Sponsorship Forwarding]]></title>
            <link>https://antfu.me/posts/sponsorship-forwarding</link>
            <guid isPermaLink="true">https://antfu.me/posts/sponsorship-forwarding</guid>
            <pubDate>Sat, 20 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[An initiative to support open-source ecosystem by Anthony Fu.]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<p>It's not a secret that open-source projects are now a critical part of almost every software project. While most open-source projects are maintained by volunteers, the sustainability of these projects becomes a big concern. The recent <a href="https://robmensching.com/blog/posts/2024/03/30/a-microcosm-of-the-interactions-in-open-source-projects/">xz/liblzma vulnerability</a> accident is a great example that shows the importance of open-source projects and how severe the problem could be.</p>
<p>There are <a href="https://robmensching.com/blog/posts/2024/03/31/what-could-be-done-to-support-open-source-maintainers/">multiple ways to support open-source projects</a> <span op75 italic>(another excellent article by Rob Mensching, highly recommended)</span>. Funding is indeed one of its essential aspects. I believe most open-source maintainers are not doing it for money. However, maintainers still need to pay their bills to make a living and spend time maintaining the projects. Unrewarded free work is not sustainable in the long run.</p>
<p>We are glad to see that more and more companies and individuals started to see the importance of open-source sustainability and have started to sponsor open-source projects. We also see that some companies have started to hire or support maintainers to work on open-sources projects full-time <span op75>(for example, thanks {NuxtLabs} for having the Nuxt core team and me 💚. {Astro} {Stackblitz} {Netlify} {Vercel} and other companies are also doing it)</span>. We, as the maintainers, genuinely appreciate all that support.</p>
<p>However, today, it's still extremely hard for maintainers to figure out ways to get the minimal funds to work on open-source projects full-time, not even asking for returns that match the value they are providing. There are many aspects of open-source funding that need to be improved. In this post, I'd like to bring up some problems we have observed and propose solutions to improve the situation.</p>
<h2>Unbalanced Funding</h2>
<p>In open-source, there are different types of projects.</p>
<p>Some projects are more &quot;front-facing&quot; and are directly interacted with by developers and users on a daily basis. These projects often have short and loud names, well-designed logos, and a large community discussing them or even holding events around them.</p>
<p>On the other hand, there are also &quot;underlying&quot; dependencies that are used extensively but may not be as visible. The majority of the users may not even be aware that they are indirectly depending on them.</p>
<p>Whether a project is &quot;front-facing&quot; or &quot;underlying&quot;, both types of projects are crucial to the ecosystem and deserve support. What they have in common is that they rely on people who invest their time and effort into maintaining these projects.</p>
<p>Among all the contributors and maintainers, there is a mix of individuals who have different working styles and levels of visibility. Some are more &quot;high-profile&quot; and actively share their work, while others prefer to stay low-profile and focus on their contributions behind the scenes. The different working styles should not diminish the value of their work.</p>
<p>Naturely, front-facing projects and high-profile maintainers are much more likely to receive attention and sponsorships, while the underlying dependencies and low-profile maintainers are often overlooked. To illustrate this, let me present you <a href="https://www.explainxkcd.com/wiki/index.php/2347:_Dependency">this famous xkcd comic</a> again:</p>
<p><img
  src="https://antfu.me/images/oss-sponsor-dependencies.svg"
  alt="Open-source Sponsor Dependencies"
  class="dark:invert-100 dark:op80 border-none! shadow-none! max-w-120"
/></p>
<p>Imagine, with that critical dependency being removed, here we have a <a href="https://mastodon.social/@ahl/112243141693101752">falling apart version</a> by <a href="https://mastodon.social/@ahl">Adam Leventhal</a>:</p>
<p><img
  src="/images/oss-sponsor-falling.svg"
  alt="Open-source Sponsor Dependencies"
  class="dark:invert-100 dark:op80 border-none! shadow-none! max-w-120"
/></p>
<p>When you find a tool that has been helpful to you and you want to show your support, it's natural to look for a way to sponsor the tool or its maintainers. We greatly appreciate this kind of support. The problem of the unsupported dependencies is not usually the sponsor's responsibility.</p>
<p>However, it's important to recognize that when we develop a &quot;front-facing&quot; tool or framework, we often rely on other tools and dependencies to make our work possible. It wouldn't be fair for the &quot;front-facing&quot; projects to take all the credit. In reality, even those &quot;front-facing&quot; projects are often underfunded. That's why we are grateful to see many open-source projects forwarding sponsorships to supporting their dependencies.</p>
<p>For example, {ESLint} <a href="https://eslint.org/blog/2022/02/paying-contributors-sponsoring-projects/">is forwarding sponsorships to its contributors and dependencies</a>, {Astro} <a href="https://astro.build/blog/astro-ecosystem-fund/">is giving funds to the ecosystem</a>, projects that I am participating like {Vite|<a href="https://opencollective.com/vite/expenses">https://opencollective.com/vite/expenses</a>} and {Elk|<a href="https://opencollective.com/elk/expenses?collectiveSlug=elk&amp;type=INVOICE">https://opencollective.com/elk/expenses?collectiveSlug=elk&amp;type=INVOICE</a>} are also started following similar approaches on their Open Collective. Many other projects are doing it as well.</p>
<p>{@nzakas|Nicholas C. Zakas}, the creator of ESLint, also wrote a great article <a href="https://humanwhocodes.com/blog/2022/06/sponsoring-dependencies-open-source-sustainability/">Sponsoring dependencies: The next step in open source sustainability</a>, explaining the importance of this.</p>
<h2>About Me</h2>
<p>I am honestly super lucky to have the opportunity to work on numerous front-facing projects and be a relatively high-profile maintainer in the community and on social media. I am extremely grateful for <a href="https://github.com/sponsors/antfu">all the sponsorships I have received</a>. As I mentioned in <a href="https://antfu.me/posts/mental-health-oss#scope">another post</a>, I have received tremendous help from the community and contributors in creating the tools that you are using. I firmly believe that I shouldn't take all the credit and appreciation from sponsors alone. That's why I wrote this blog post - to find a way to give back to the ecosystem with the resources and influence I have.</p>
<h2>Sponsorships Forwarding on GitHub</h2>
<p>I am already <a href="https://github.com/antfu?tab=sponsoring">sponsoring a number of maintainers</a> who I benefit a lot from. {GitHub Sponsors|<a href="https://github.com/sponsors">https://github.com/sponsors</a>} is a great platform. It <a href="https://docs.github.com/en/sponsors/getting-started-with-github-sponsors/about-github-sponsors#about-github-sponsors">covers the transaction fees</a>, and provides great connections with sponsors, maintainers, and projects, making the discovery and sponsorship process smooth. However, while it is great for individual sponsors, it lacks the ability to forward sponsorships to other projects or maintainers. {@patak-dev} <a href="https://github.com/orgs/community/discussions/10177">raised this feature request for GitHub</a> two years ago, which is currently the top-upvoted request from the community, but unfortunately, it has not been resolved yet.</p>
<p>Here, let's do some simple math to see why this feature is essential:</p>
<ul>
<li>With GitHub Sponsors, the maintainer receives a monthly payout to their bank account.</li>
<li>This payout counts as personal income, and the maintainer needs to pay taxes based on the country they live in.</li>
<li>If the maintainer wants to sponsor another maintainer, they need to pay from their own pocket.</li>
<li>When the second maintainer receives the funds after another month, they must also pay taxes again.</li>
</ul>
<p>For example, the tax I have to pay here in France is roughly 41%. Assume both maintainers have the same tax rate. This means that when forwarding sponsorship on GitHub Sponsors, the second maintainer will only receive</p>
<div text-lg text-gray:75>(1 - <span text-blue>41%</span>) x (1 - <span text-blue>41%</span>) = <span text-orange>34.81%</span></div>
<p>of the original amount, plus two months of delay in between <span op75>(on top of the case that GitHub already covers the transaction fees)</span>. Sometimes, we even have circular sponsorships because we both want to show appreciation to the others. Ultimately, this is a significant loss, especially when there is not enough funding for open source already (it's also worth mentioning that GitHub Sponsors is <a href="https://docs.github.com/en/sponsors/getting-started-with-github-sponsors/about-github-sponsors#supported-regions-for-github-sponsors">not yet available in all countries</a>).</p>
<p>Despite GitHub Sponsors limitations, a lot of maintainers are still forwarding part of their funds to others ({@danielroe|Danielroe's Sponsoring|<a href="https://github.com/danielroe?tab=sponsoring">https://github.com/danielroe?tab=sponsoring</a>}, {@patak-dev|Matias' Sponsoring|<a href="https://github.com/patak-dev?tab=sponsoring">https://github.com/patak-dev?tab=sponsoring</a>}, {@kazupon|Kazupon's Sponsoring|<a href="https://github.com/kazupon?tab=sponsoring">https://github.com/kazupon?tab=sponsoring</a>} and many more). While we really wanted to support more dependencies and maintainers, the current situation on GitHub is not very feasible in the long run.</p>
<h2>Open Collective</h2>
<p>Another popular platform for open-source projects to receive sponsorships is <a href="https://opencollective.com/">Open Collective</a>. It provides great transparency on how funds are collected and spent.</p>
<p>For open-source projects, <a href="https://www.oscollective.org/">Open Source Collective</a> is a commonly used fiscal host on Open Collective. It charges a total of 10% transaction and hosting fees upfront. The important part is that it allows funds to be forwarded to other projects <strong>on the same fiscal host with no additional fees</strong>. This makes it a much better fit for the sponsorship forwarding use case.</p>
<h2>Anthony Fu's Collective</h2>
<p>So, I came up with the idea of creating my personal collective: {Anthony Fu Collective}</p>
<p>Where you can sponsor my work across the ecosystem, including but not limited to:<br><br>
{Vite} {Vue} {Nuxt} {Vitest} {VueUse} {UnoCSS} {Slidev} {Elk} {Shiki} {TwoSlash} {ESLint Stylistic}</p>
<p>The main difference is that I <strong>won't take the funds for myself</strong>;<br><span op50>(except for occasional expense reimbursements like hosting or domain renewal)</span></p>
<p><strong>All the funds will be redistributed to the dependencies and contributors</strong>. Each month, I will select a few dependencies and contributors to forward the funds. Depending on the amount raised, I may also set up recurring sponsorships for high-impact dependencies in the future. You give the trust and support to me, and I will make sure the funds are well spent.</p>
<p><img
  src="/images/oss-sponsor-antfu-oc.svg"
  alt="Open-source Sponsor Dependencies"
  class="dark:invert-100 dark:op80 dark:filter-hue-rotate-180 border-none! shadow-none! max-w-120"
/></p>
<p>To allow differentiating easily, I will treat the funds on <a href="https://github.com/sponsors/antfu">GitHub Sponsors</a> and <a href="https://opencollective.com/antfu">Open Collective</a> differently:</p>
<ul>
<li>{Sponsor Anthony Fu on Open Collective|<a href="https://opencollective.com/antfu">https://opencollective.com/antfu</a>} - To sponsor the dependencies and ecosystem of the projects Anthony maintains.</li>
<li>{Sponsor Anthony Fu on GitHub Sponsors|<a href="https://github.com/sponsors/antfu">https://github.com/sponsors/antfu</a>} - To sponsor Anthony's personal work on open-source projects.</li>
</ul>
<p>I would generally recommend sponsoring on Open Collective first to support the entire ecosystem and open source. Well, I'd also be grateful if you could sponsor on both platforms :P</p>
<p>Recurring sponsorships are highly appreciated. They help provide a more consistent monthly income and contribute to long-term sustainability for the projects and maintainers.</p>
<p>This collective is my approach trying to see how I can help the ecosystem with the resources and knowledge I have. It also serves as an initiative to encourage more maintainers and projects to follow, by forwarding the support they receive to the dependencies and contributors they benefit from.</p>
<h2>Transparency</h2>
<p>Since this involves money and trust, I think transparency is crucial here. I will try to answer honestly some questions you might have below. Feel free to raise more questions if you have any other concerns, I will try to update this post with my responses accordingly.</p>
<h3>Why am I Doing this?</h3>
<p>As I explained throughout this post, my intention is to contribute to the open-source ecosystem and make it better with my little efforts.</p>
<p>To be completely honest and transparent, yes, I couldn't say I am not doing this 100% selflessly. Despite the fact that I am not taking funds for myself, I might still indirectly benefit from this initiative.</p>
<p>I do undeniably have vanity and ego due to my human nature, and I am not ashamed to admit it. I do care about my reputation and influence to some extent. I see this endeavor as similar to contributing to open source. Does everyone do open source completely selflessly? I doubt that. But does that mean everyone is doing it solely for their own benefit? I don't believe that either. The beauty of open source lies in its non-zero-sum nature, where maintainers can derive benefits such as a sense of accomplishment, skill improvement, recognition, and reputation, while providing value to benefit the entire world.</p>
<p>Being part of the open-source community who also relies on it, I certainly have the incentive to help the community become better and more sustainable. I am not a materialistic person, and the sponsorships I have received are not huge, but they are enough for me to make a basic living. While I do believe there are other projects and maintainers who would benefit more from the funds at this time.</p>
<p>Ultimately, my goal is to increase the overall funding into the open-source ecosystem and develop robust systems to support projects, so that everyone can reap the benefits of open source and acknowledge the hard work put in by its contributors.</p>
<h3>How to Ensure the Distribution is Fair?</h3>
<p>Regrettably, I don't think it's possible to design a &quot;perfect algorithm&quot; to &quot;score&quot; and distribute the funds fairly. Representing a project, a person, or their work with a single number to rank is unrealistic.</p>
<p>When it comes to equality and equity, I believe it's important to prioritize supporting underrepresented and underfunded projects and maintainers. This would involve subjective judgment, and while I can't guarantee complete fairness, I will strive to be transparent and sincere. I will actively engage with the community and openly communicate how and why the funds are distributed.</p>
<h3>Why &quot;Personal&quot; Collective</h3>
<p>As mentioned above, there is no absolute way to distribute the funds fairly. In this case, it's based on my judgment and the trust you give me. As the starting point of this initiative, I believe it's easier and simpler to start with a personal collective and adjust based on the feedback and results. Meanwhile, a personal collective not bound to a specific project could be more flexible and better support the goals of this initiative.</p>
<p>This is certainly not going to be the only collective that forwards sponsorships. Maybe in the future, sponsoring multiple individuals collectively and group collectives would help make the distribution less opinionated and less biased.</p>
<p>For a related example, inspired by <a href="https://x.com/patak_dev/status/1778020676781101313">this tweet</a>, we recently started an organization with several maintainers to improve the performance and quality across JavaScript packages. Later, we might set up a collective for this organization so you can sponsor the efforts on performance improvements. Stay tuned for a future announcement.</p>
<h3>Other Alternative Platforms?</h3>
<p>There are quite a few other platforms for open-source funding that also bring exciting solutions to this problem.</p>
<p>This collective is only one more idea, an attempt to help our ecosystem become more sustainable - feel free to explore other platforms and initiatives you think are making sense. The main difference between this collective and the others is that it has me working behind it, instead of just metrics and algorithms. The human factor also made it possible to reduce sponsorships waterflow, where on some platforms the sub-dependencies are harder to get enough funds. With this collective, I can reach the critical dependencies and contributors directly to support them. I will spend time doing the research and communications every month to find important dependencies to support.</p>
<h2>Sponsor Now</h2>
<p>This initiative is something I couldn't do alone. I would need your support to make it happen.</p>
<p>If I have convinced you this initiative is meaningful, use the buttons below to help me support the open-source ecosystem and make it more sustainable. Thank you!</p>
<SponsorButtonCollective />
<h2>Feedback</h2>
<p>I am eager to hear your feedback on this initiative. If you have any thoughts, concerns, or suggestions, please feel free to reach out to leave me a comment under <a href="https://twitter.com/antfu7/status/1781749305230885140">this tweet</a> or <a href="https://elk.zone/m.webtoo.ls/@antfu/112305352609322422">this toot</a>. You can also mail me at <a href="mailto:hi@antfu.me">hi@antfu.me</a>.</p>
<p>Thank you for reading this long post. I hope we can take this initiative as a small step and make open-source better and more sustainable together. 💚</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Refreshed Nuxt ESLint Integrations]]></title>
            <link>https://antfu.me/posts/refreshed-nuxt-eslint</link>
            <guid isPermaLink="true">https://antfu.me/posts/refreshed-nuxt-eslint</guid>
            <pubDate>Wed, 10 Apr 2024 12:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><a href="https://nuxt.com/blog/eslint-module">Go to nuxt.com and read the full article</a>.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Introducing ESLint Config Inspector]]></title>
            <link>https://antfu.me/posts/eslint-config-inspector</link>
            <guid isPermaLink="true">https://antfu.me/posts/eslint-config-inspector</guid>
            <pubDate>Fri, 05 Apr 2024 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><a href="https://eslint.org/blog/2024/04/eslint-config-inspector/">Go to eslint.org and read the full announcement</a>.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Anthony's Roads to Open Source - The Set Theory (React ver.)]]></title>
            <link>https://antfu.me/posts/roads-to-oss-set-theory-react-paris-2024</link>
            <guid isPermaLink="true">https://antfu.me/posts/roads-to-oss-set-theory-react-paris-2024</guid>
            <pubDate>Fri, 22 Mar 2024 08:00:00 GMT</pubDate>
            <description><![CDATA[Anthony's Roads to Open Source - The Set Theory - React Paris 2024]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Slides: <a href="https://antfu.me/talks/2024-03-22">PDF</a> | <a href="https://talks.antfu.me/2024/reactparis">SPA</a></p>
<p>Made with <Slidev class="inline"/> <a href="https://github.com/slidevjs/slidev"><strong>Slidev</strong></a> - presentation slides for developers.</p>
</blockquote>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Mental Health in Open Source]]></title>
            <link>https://antfu.me/posts/mental-health-oss</link>
            <guid isPermaLink="true">https://antfu.me/posts/mental-health-oss</guid>
            <pubDate>Sat, 16 Mar 2024 12:00:00 GMT</pubDate>
            <description><![CDATA[Some of my thoughts on my mental health during my journey in Open Source]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<blockquote>
<p><a href="/posts/mental-health-oss-zh">中文翻译 Chinese Version</a></p>
</blockquote>
<blockquote>
<p><strong>TL;DR</strong>: I am doing great and not going anywhere. Having some pressure but still holding up and trying to improve. Thank you and don't worry!</p>
</blockquote>
<p>This is the 4th year since I have started doing Open Source. To be completely honest, I began to feel things were getting out of my capacity more and more often. I am still not sure if I have ever been through actual <a href="https://www.darlingdowns.health.qld.gov.au/about-us/our-stories/feature-articles/signs-you-might-be-experiencing-a-burnout-and-how-to-regain-balance-in-your-life#:~:text=Burnout%20is%20a%20state%20of,an%20increasing%20sense%20of%20hopelessness">burnout</a> or not, but I surely have experienced the ups and downs of my productivity and motivation periodically.</p>
<p>This post is not a guide, or not even complaining. It's more like my personal diary, which I serve as a record for myself. I just think it might be interesting if I could share those with you.</p>
<p>If you are been through a burnout or feeling close to it, I'd recommend you to take some breaks, talk to someone, and seek professional help if needed. Here is also a nice article <a href="https://opensource.guide/maintaining-balance-for-open-source-maintainers/">Maintaining Balance for Open Source Maintainers</a> that you could reference. Take great care!</p>
<p>And so here, let me tell you some random stuff I have been thinking about recently</p>
<hr>
<h3>Unprepared</h3>
<p>In a way, Open Source is still very new to me, even today.</p>
<p>Since I started to learn programming and knowing about open source, I have been dreaming about being a full-time open source developer. When I was in college, I was eager to get recognized by the open source community, trying hard to &quot;figure out&quot; some impactful work that I could accomplish. All of a sudden, you will reach a pivotal point, where your project might unexpectedly take off, or you have been invited to join a big project - in a moment, you will start to feel all those excitements as well as the responsibilities suddenly come along. In a few days, when the initial excitement starts to fade away, you start to realize that it also means so much responsibility and other things that you have never thought about. Despite that I have been trying all my college years to get into open source, when I finally stepped into it, I realized how much I was unprepared.</p>
<p>One interesting thing about Open Source is that <strong>one is probably never prepared</strong>. You might encounter tricky technical problems, or have to keep up with the new technologies, but there are also a bunch of things other than coding that you have to deal with. You have to be your <em>customer support</em> to answer questions; be a <em>designer</em>, a <em>writer</em> to prepare nice documentation; a <em>project manager</em> to keep the project on track; a <em>team leader</em> to onboard new contributors and keep the team motivated; <em>marketing</em> your stuff; <em>speaking</em> at conferences; and so on. Those are the &quot;side-effects&quot; of being an open source developer, many things come to you in a bundle, not only the code.</p>
<p>For me, it's so much a challenge. I am quite introverted, I am not good at chatting or making conversations. I was terrible on my English exams back in school, and wasn't confident at all to speak in English. I got stage fright even just in front of my classmates. And I suppose I don't enjoy team management either, even though I never led a team - so many things to be afraid of.</p>
<p>It doesn't give you the time to prepare for ready (or on the other hand, one might never be ready without taking the first step), as the project grows, as your responsibilities grow, you will be forced to learn and adapt. When it naturally grows a team, you have to learn to communicate, to lead. When someone invites you to do a podcast or give a talk, they won't wait 3 years for you to practice the language or speaking skills -- you either miss the chance or you fight your fear and do it. Because I love doing Open Source so much, I have to conquer them and myself.</p>
<p>Might be overwhelming it seems. But if you take those challenges and get through them one by one, gradually, you might find that they are quite fun and rewarding. In the end, I appreciate a lot about all those chances for pushing me out of my comfort zone and forcing myself to improve. Throughout these 4 years in Open Source, despite still being not perfect at many things, I managed to speak English much more confidently. I have given talks at many conferences, some of which even have thousands of attendees. I still get super nervous before every talk, but at least I am no longer afraid of doing it.</p>
<p>There are still so many challenges and surprises coming up. I am half fearful and half excited to see what's next.</p>
<h3>&quot;Expectations&quot;</h3>
<p>Human adapts. That drives us to survive as well as keep improving, but that also makes us hard to stay satisfied.</p>
<p>I was so excited when I started my first few open source projects. I would keep refreshing the page, eagerly waiting for new issues, new pull requests, and new comments to show up. Every single star would make me happy, and I'll try to help with every single issue as much as possible. I set milestones like 100 stars, 500 stars and celebrate when I reach them. I still remember how proud I was when I told my friends that I had a few hundred stars on my projects and I was making some impact on the world.</p>
<p>Once you reach those goals, things start to become &quot;usual&quot;. You will then start to expect more and set higher goals. At a certain point, I start to not care about those numbers of stars or downloads anymore. It's not necessarily a bad thing as they are not the metrics I should be focusing on anyway, but I sometimes miss the old days when I could get joy from those simple things.</p>
<p>I gradually realized, that the experience of so many things in our life has a direct relationship with our <strong>expectations</strong>. When we just get started, we have little expectations, which could be relatively easy to reach. As we go forward and stand on a higher ground, we start to expect much more, that might not scale linearly. Getting 100 more stars when you have 1000 does not sound as impressive as when you have nothing. When you have 1000 stars, you will look for another 1000, and only 100 can no longer satisfy you. That is odd to me, and I don't enjoy this &quot;human nature&quot; of mine.</p>
<p>I found that lowering my expectations and being grateful for what I have is a good way to keep me happy. When you start to realize you can't keep reaching your milestones one and another, it's a good way out to stop seeking higher, take a break and enjoy the view around you - maybe you have already reached high enough. Since I started to &quot;not overly concerned about gains and losses&quot;, I found myself much happier trying different ideas even if they might not be successful - because I don't have high expectations of them, there is no such concept of &quot;failure&quot; to me. And if some of them later might turn out to be good ideas, it would be a nice &quot;unexpected surprise&quot;.</p>
<blockquote>
<p>If you are interested, I explained about my ideas-finding process in the <a href="/posts/about-yak-shaving">About Yak Shaving</a> post.</p>
</blockquote>
<h3>&quot;Self-Expectations&quot;</h3>
<p>Expectations apply not only to what we are doing, but also come to myself. When I care about a project too much, I often find I am expecting myself too much in the role of being a kind and friendly maintainer. I will feel bad when I read people criticizing my projects, when a certain bug causes people trouble, or when I don't reply to issues in time, etc. The feelings become even stronger on popular projects, as you know there are a lot of people who depend on it. Those self-expectations put me under quite a lot of pressure and stress.</p>
<p>As I mentioned in <a href="/posts/why-reproductions-are-required">another post, the ratio of maintainers to users is often unbalanced</a> in open source projects. It's really hard to meet a new collaborator or team member, but there is basically no threshold to grow more users due to Open Source being naturally free.</p>
<p>I think it's hard for maintainers to make the mind shift and consider that they are not obligated to solve the issues for others -- because open-source software is usually <a href="/posts/why-reproductions-are-required#open-source-software-is-served-as-is"><strong>served as-is</strong></a>. Especially for maintainers who care about their users and community, it's hard to ignore when we receive new issues. But in another perspective, one person's time and energy are limited. When the amount of work grows out of one's capacity, it's better to set priorities and focus on the most important things first.</p>
<p>I wish someone could tell me this when I started maintaining high-traffic open source projects (you have great resources online like <a href="https://opensource.guide/best-practices/#its-okay-to-hit-pause">this</a>) - it took me a long time to realize that I don't have to be perfect, and it's okay to do things at my own pace. Instead of receiving notifications passively, it's better for me to turn off the push notifications, and to proactively check the issues and pull requests when I feel ready to do so.</p>
<blockquote>
<p>I made a talk about <a href="/posts/manage-github-notifications-2023">How I Manage GitHub Notifications</a>, if you are interested in more about the methodology.</p>
</blockquote>
<p>Lowering the expectations of yourself - no one is perfect, and no one is a machine. Don't let them become a burden to you. It's more important to maintain a healthy and sustainable pace, and to keep yourself happy and motivated to have more positive impacts in the long run.</p>
<h3>When Your Dream Becomes Your Job</h3>
<p>It's awesome to live in your own dream, and honestly, a privilege. But also, to be realisistic, having a dream is quite different from living in it. Dreams are always idealized, excluding all the boring details. My dream is to be a full-time open source developer. Yes, it sounds great to be independent, to work on what you like, to have a flexible or no schedule at all, to work from anywhere, and to benefit the world, etc. But in reality, things are not that simple.</p>
<p>It's quite similar to &quot;Make your hobby your job&quot;. It indeed has a great amount of benefits, like you will be more enjoyable and productive, but it also comes with obligations and responsibilities. When a hobby becomes a job, you lose the freedom to choose when and what to. Before, you would do your hobbies as after-work relaxation, but now when you want to relax with your hobbies, they become work.</p>
<p>I was lucky that software development is a big field with many different things to do. Outside of the &quot;main&quot; open source project maintenance, I sometimes do some small projects (<a href="https://100.antfu.me/">Generative Art</a>, <a href="/posts/ai-qrcode">Stable Diffusion</a>, <a href="/projects">some little experiments</a>, etc.) for fun to refresh my mind (as a kind of &quot;relax&quot; from main projects). I also enjoy playing indie games a lot, while I keep thinking of getting serious about developing some games - but that's another story - at least now I still have some ways to escape when I really want to stay away from code.</p>
<p>I probably enjoy programming too much that I don't have strong feelings about this. The line between &quot;work&quot; and &quot;fun&quot; is quite blurry on my side. Sometimes, a fun project can become something serious that people rely on.</p>
<h3>Velocity, Scope, and Quality</h3>
<p>This is actually the topic that drove me to write this blog post.</p>
<p>Let's start with this <a href="https://en.wikipedia.org/wiki/Project_management_triangle">&quot;Iron Triangle&quot;</a> of <b important-text-hex-E3B65E>Velocity</b>, <b important-text-hex-D777B1>Scope</b> and <b important-text-hex-80BEDF>Quality</b>. <br><span op50>(typicall they are Quality/Speed/Cost where I made a few adjustments here)</span></p>
<figure>
  <img src="https://antfu.me/images/oss-mental-iron-triangle.svg" />
  <figcaption text-center>The Iron Triangle of Velocity-Scope-Quality</figcaption>
</figure>
<p>Usually, people would say -- in these three factors, you can <strong>only pick two</strong>. If you want to deliver a project faster, you might have to sacrifice the quality or have a smaller scope of features. If you want to have a high-quality and feature-rich product, you might have to sacrifice speed to deliver good stuff slowly, and so on.</p>
<p>To my personal standards, having high-quality software in Open Source is an unbendable standard that I would never compromise.</p>
<p>While also, keep a certain velocity and momentum is also very important to me. The majority of my motivation is driven by the feeling of accomplishment after I have finished something. I could be in an excellent <a href="https://en.wikipedia.org/wiki/Flow_%28psychology%29">flow</a> when I can create the feedback loop of iterating things out then delivering.</p>
<p>So, to say, I usually pick <b important-text-hex-80BEDF>Quality</b> and <b important-text-hex-E3B65E>Velocity</b>. In the beginning, the scope of my projects was quite clear and small. I managed to keep the quality high, deliver things fast, and get feedback from the community quickly. At that time, I was able to stay productive and motivated to keep working on those projects.</p>
<h3>Scope</h3>
<p>I was <em>&quot;accidentally&quot;</em> able to keep that momentum and velocity for quite a long time. I started getting into Open Source with {i18n Ally} and {VueUse}, and since then, I joined Vue and Vite teams. <strong>Within 2021 alone</strong>, I came up with {Slidev} <span op75 text-sm>(April 2021)</span>, {UnoCSS} <span op75 text-sm>(October 2021)</span> and {Vitest} <span op75 text-sm>(December 2021)</span> -- Everything went almost too smoothly that I barely realized the capacity of having larger scope will have a certain limit.<br>
I continued doing so with the &quot;velocity&quot; <em>ignorantly</em> since then. I am super lucky to meet the amazing teams and community, and get help from them:</p>
<ul>
<li>The awesome &amp; super supportive {Nuxt} team {@Atinux} {@danielroe} {@pi0}</li>
<li>{@sheremet-va} {@AriPerkkio} and <a href="https://github.com/vitest-dev/vitest/graphs/contributors">the Vitest team</a> for taking care of {Vitest}</li>
<li>{@chu121su12} {@zyyv} and <a href="https://github.com/unocss/unocss/graphs/contributors">the UnoCSS team</a> for polishing {UnoCSS} a lot</li>
<li>{@okxiaoliang4} {@wheatjs} {@Alfred-Skyblue} {@Tahul} and <a href="https://vueuse.org/">the VueUse team</a> for {VueUse}</li>
<li>{@sxzz} for managing {Unplugin}</li>
<li>{@KermanX} {@tonai} for pushing many features on {Slidev}</li>
<li>{@arashsheyda} for helping a lot on {Nuxt DevTools}</li>
<li>{@shuuji3} {@Shinigami92} for contributing to {Elk}</li>
<li>{@patak-dev} {@sapphi-red} {@bluwy} for keep pushing Vite forward with the awesome community</li>
<li>{@userquin} for maintaining {Vite PWA} and helping basically everything everywhere</li>
<li>{@yyx990803}, whom I learned a lot about OSS and decision-making from</li>
<li>...and many of you who have contributed to Open Source or provided financial support via Sponsorships!</li>
</ul>
<p>It's a shame I couldn't list all of them, and many of them are actually overlapping across projects. What I am trying to say is that, I am not working alone, and I can't do all of these alone. I have <strong>borrowed so much help</strong> from the community and the teams to have all those projects. I am truly grateful for that. On top of <b important-text-hex-80BEDF>Quality</b> and <b important-text-hex-E3B65E>Velocity</b>, it seems that I also work on a wide <b important-text-hex-D777B1>Scope</b> of projects - looks like it broke the rule of the iron triangle - but actually, the awesome community behind the scenes is the <em>&quot;magic&quot;</em> that makes it possible.</p>
<h3>Capacity</h3>
<p>The amount of work required to maintain multiple high-traffic open source projects is honestly massive. I should have reached my limit a long while ago without help from the community. While the community helps me a lot, it still, takes a lot of energy to communicate, coordinate, as well as consistently context-switching. Over time, I accumulated many things I had to do myself, many ideas I wanted to try, and many things I wanted to improve.</p>
<p>I want to keep those projects alive and keep them moving forward; I want to write more blog posts to share my thoughts; I want to do more talks, to travel and meet people; I want to do more live streams as I know many are waiting for it; I have to clean this thing up, to do that release; I also want to learn French; And spend more time with my family - I mean, this is probably just life. People have their own concerns and responsibilities, I ain't any more special or busier than others.</p>
<blockquote>
<p><span font-wisper font-bold op80 text-1.4rem leading-2.1rem>&quot;But somehow, there seems to be something, <br>something makes me hard to breathe.&quot;</span></p>
</blockquote>
<p>I am probably reluctant to admit my potential burnout. Not because I am afraid of it, but more like, I don't want to give up and handle it passively. I know to take rest when I need it, but calling myself &quot;burnout&quot; and giving up is an &quot;easy way out&quot; to escape from the responsibilities. I want to find out the <em>&quot;root cause&quot;</em> and try to improve the situation instead of just <em>&quot;workaround&quot;</em> it. As we talked about previously, the shift of &quot;expectations&quot;, and the re-evaluation of my &quot;unpreparedness&quot; and &quot;self-expectations&quot; are my solutions to the moments when I was close to burnout for different reasons. By adjusting myself and adopting, I was usually able to recover from the low points in roughly a week and keep moving forward.</p>
<p>The case this time is a bit different, it's not about me being unmotivated, but rather, there are too many things I wanted to do but I am running out of capacity. I started to think, that maybe it was that I expected myself to keep delivering everything with the same <b important-text-hex-E3B65E>Velocity</b>, I was anxious about not doing enough and not doing fast. Getting quick feedback is awesome and quite productive, but I am probably growing some impatience because I am too used to being quick. Combined, they make me easy to get frustrated when working on something that requires mid-to-long-term effort.</p>
<p>For example, <em>writing</em>. I am not good at writing, and I don't honestly enjoy it. Documentations, blog posts, tutorials, and talks -- all require a lot of time, and also, something I have to do. I easily get distracted and lose focus when I am writing or giving up in the middle. So I <a href="https://twitter.com/antfu7/status/1764397930796953823">asked about it on Twitter</a> and got a lot of great advice from the community (check the comments out and you might find something useful for you too). I started trying to take it easy and doing it slowly, trying to shift my mind to not expecting immediate results and enjoying the process. This blog you are reading took me roughly a week to write (that's a pretty long time for me), fragmented into multiple sessions. I feel also helps me to rethink and reorganize my thoughts and feelings, and actually, writing them down makes me feel much better about my anxiety.</p>
<p>So, I should probably re-evaluate my capacity and expectations. I have to understand and accept that I can not always keep the same velocity and I don't have to push myself too hard. Slowing down a bit to take more care about the details, maybe I will find different joy and satisfaction in the process.</p>
<p>Honestly, I am not even sure what I am trying to tell in this blog post - maybe just simply sharing my thoughts and feelings with you. Right now, I still feel quite some pressure. I am still adapting and trying to find a better way of dealing with it. I feel a lot better throughout this week of writing and talking to friends, and I am sure I will get through this. This might just be like many other things in our lives; we don't always have perfect solutions, but we have to keep going forward and find our way out.</p>
<p>Maintaining good mental health is one of the important tasks for every open source maintainer to keep sustainable. I don't think there would be &quot;an answer&quot; or &quot;a solution&quot; to the highs and lows throughout the journey. It's more like a continuous process of learning and adapting to find the ways that work for each of us.</p>
<h3>I'd Love to Hear From You</h3>
<p><strong>Thank you</strong> for reading through my messy and verbose thoughts until the end!</p>
<p>I know my opinions must be heavily biased. If it ever triggers any thoughts or feelings for you, I am <strong>curious to hear what you think or what are your ways</strong>. You can leave some comments under this <a href="https://twitter.com/antfu7/status/1768973757681438909">tweet</a> or <a href="https://elk.zone/m.webtoo.ls/@antfu/112105304547243075">mastodon</a>, or send me an email at <a href="mailto:hi@antfu.me">hi@antfu.me</a> if you prefer private conversations. Looking forward to hearing from you!</p>
<p>Meanwhile, there are actually many other things about open source I didn't get to talk about in this post. <a href="https://twitter.com/kettanaito">Artem Zakharchenko</a> wrote a great article <a href="https://kettanaito.com/blog/the-dark-side-of-open-source">The Dark Side of Open Source</a>, from different perspectives and points that I also resonate a lot with. Highly recommended to give it a read as well.</p>
<h3>Thanks</h3>
<p>Thanks to {@patak-dev} and {@posva} for our deep conversations around this topic, they really helped me a lot and provided great support.</p>
<p>And <strong>You</strong>, as well as the great open source community! I am so grateful for all the help and support I have received from you.</p>
<p>Till next time, take care!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[The Evolution of Shiki v1.0]]></title>
            <link>https://antfu.me/posts/evolution-of-shiki-v1</link>
            <guid isPermaLink="true">https://antfu.me/posts/evolution-of-shiki-v1</guid>
            <pubDate>Mon, 11 Mar 2024 12:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><a href="https://nuxt.com/blog/shiki-v1">Go to nuxt.com and read the full article</a>.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[The Magic in Shiki Magic Move]]></title>
            <link>https://antfu.me/posts/shiki-magic-move</link>
            <guid isPermaLink="true">https://antfu.me/posts/shiki-magic-move</guid>
            <pubDate>Mon, 04 Mar 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[The methodology behind Shiki Magic Move.]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<ShikiMagicMoveDemo />
<p><a href="https://support.apple.com/guide/keynote/add-transitions-tanff5ae749e/mac">Magic Move is a feature for transition in Keynote</a>, or it's called <a href="https://support.microsoft.com/en-us/office/use-the-morph-transition-in-powerpoint-8dd1c7b2-b935-44f5-a74c-741d8d9244ea">Morph Transition in PowerPoint</a>, and it automatically animates the transitions for objects between slides. It is quite impressive, very intuitive and effortless to use, and can be applied to various types of objects. Like you can paste a highlighted code block, or make another in a second slide, Magic Move will do the transition as granular to the tokens level for you.</p>
<p>The only annoying part of this process is that Keynote does not support code highlighting - so you need to highlight the code somewhere and paste them manually <strong>every single time</strong>. This is one of the reasons I made <a href="https://sli.dev/">Slidev</a> - to have first-class tooling for developers to make presentations easier. While moving to web technologies with Slidev opens up almost infinite possibilities, on the other hand, it also makes some nice cool features in Keynote and PowerPoint harder to achieve (you need to write quite some extra code) - for example, the Magic Move.</p>
<p>Browsers' new <a href="https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API">View Transitions API</a> makes morphing between elements a lot easier, only that it requires some manual work to assign keys to make the pairs. While it's rather ok to do for a few big elements, doing such manually for every single token in a code block is basically unacceptable.</p>
<p>Roughly a year ago, <a href="https://github.com/posva">Eduardo @posva</a> and I came up with the idea of using Shiki combined with Vue's <a href="https://vuejs.org/guide/built-ins/transition-group"><code>&lt;TransitionGroup&gt;</code></a> to achieve a similar effect for code blocks:</p>
<Tweet h-650px>
<p lang="en" dir="ltr">Do you like Magic Move in Keynote for code?<a href="https://twitter.com/antfu7?ref_src=twsrc%5Etfw">@antfu7</a> and I are cooking something 😎 <a href="https://t.co/9JxjCzRA1S">pic.twitter.com/9JxjCzRA1S</a></p>&mdash; Eduardo.𝚟𝚞𝚎 (@posva) <a href="https://twitter.com/posva/status/1619083357756821504?ref_src=twsrc%5Etfw">January 27, 2023</a>
</Tweet>
<p>We managed to make the proof of concept work, as shown in the video. However, due to some hard edge cases, both of us were busy with other things, and we didn't manage to make it a usable library at that time. It has come to our discussions from time to time but we didn't come up with a good solution. Until one day, a few weeks ago while I was preparing my slides, my <a href="https://lmddgtfy.net/?q=Productive%20Procrastination"><s>productive</s> procrastination</a> kicked in and I decided to give it another try to <s>escape from my slides</s>.</p>
<p>And luck me, I found a quite nice approach and made it happen:</p>
<Tweet h-670px>
<p lang="en" dir="ltr">The procrastination in preparing talks drove me to bring up the rework of the idea we had last year with <a href="https://twitter.com/posva?ref_src=twsrc%5Etfw">@posva</a> - animate Shiki tokens like Magic Move! 🪄<br><br>Found a much more reliable approach that could finally come out as a library (soon)<a href="https://t.co/b5SgQtTw2s">https://t.co/b5SgQtTw2s</a> <a href="https://t.co/s5LutlYmAK">pic.twitter.com/s5LutlYmAK</a></p>&mdash; Anthony Fu (@antfu7) <a href="https://twitter.com/antfu7/status/1760751386122211371?ref_src=twsrc%5Etfw">February 22, 2024</a>
</Tweet>
<p>Made <a href="https://shiki-magic-move.netlify.app/">a playground</a> where you can try it out yourself, and you can find the source code at <GitHubLink repo="shikijs/shiki-magic-move" />.</p>
<p>So today here, let's break it down and see how it works.</p>
<h2>How it works</h2>
<p>To get started, we could see each word in the code with different colors as a token (they are <code>&lt;span&gt;</code> elements in practice). Basically, we can consider them as a set of small objects that we want to animate individually.</p>
<p>To break done the animations of Magic Move, we see we are basically trying to find connections between two sets of objects and animate them from one to another. Different from 1-to-1 transitions, here we expect the code before and after to be different, which means we could categorize the type of transitions into 3 categories:</p>
<ShikiMagicMoveMatch />
<p>In the playground above, we assign a key to each token. <code>Move</code> tokens come with pairs, so we assign the same key to make the &quot;connection&quot;.</p>
<p>When doing highlighting, <a href="https://github.com/shikijs/shiki">Shiki</a> turns the input code into an array of tokens. We can run Shiki twice for code before and after to get two collections of tokens. It should be fairly simple to find the <code>Enter</code> and <code>Leave</code> tokens by running two loops to compare the two collections. However, the challenge is to find good pairs of <code>Move</code> tokens. If we only pair them by the content of each token, it would be the case that 1-to-many or many-to-1 might make the pair transition off.</p>
<p>I came up with the idea of using a text diff algorithm to find the chunks of the code that are matched between the two versions. I ended up using Google's <a href="https://github.com/google/diff-match-patch">Diff Match Patch</a> (I later refactored it into ESM as <a href="https://github.com/antfu/diff-match-patch-es"><code>diff-match-patch-es</code></a>) to achieve this.<br>
With the diff result, we can now <a href="https://github.com/shikijs/shiki-magic-move/blob/e409aa5cf877a4005cf2b01729f1113beb405d13/src/core.ts#L226-L256">reliably find the pairs</a> of the <code>Move</code> tokens without worrying that tokens might travel to the wrong place.</p>
<p>With this core logic in place, we can generate the correct keys for each token for the connection. This made the rest of the task clear, we just needed to apply different transitions to different types of tokens. We could feed them into any key-based transition library, for example, Vue provides a built-in <a href="https://vuejs.org/guide/built-ins/transition-group"><code>&lt;TransitionGroup&gt;</code> component</a> that does the job perfectly, <a href="https://vuejs.org/examples/#list-transition">live example</a>.</p>
<h2>Transitions</h2>
<p>While Vue's <code>&lt;TransitionGroup&gt;</code> should get the transition done automatically, it's actually a bit tricky to do in our specific case. The main reason is that the token elements in the code are <code>&lt;span&gt;</code> that rely on browsers' layout engine to calculate the position. During the transition, we want to make each token positioned absolutely to avoid messing up the layout. This means we need to record the position of each token with <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect"><code>getBoundingClientRect()</code></a> before the transition starts, apply the absolute position to them during transitions, and then restore to the inline layout after the transition ends.</p>
<p>Ran into some limitations of <code>&lt;TransitionGroup&gt;</code> and also with the wish to have this a framework-agnostic solution, I ended up writing <a href="https://github.com/shikijs/shiki-magic-move/blob/5ec48554d87b4f4e2dd2e15f27ef9e51ef00074b/src/renderer.ts#L28">a custom renderer</a> to do that, referencing a lot of the code from <code>&lt;TransitionGroup&gt;</code>.</p>
<p>Because we are relying on the browsers' layout engine to calculate the position, we need to get the destination position of each token (the position of the final code) before the transition starts. I found this trick in Vue's code to <a href="https://github.com/vuejs/core/blob/f66a75ea75c8aece065b61e2126b4c5b2338aa6e/packages/runtime-dom/src/components/TransitionGroup.ts#L77-L78">force layout reflow</a> combined with temporary setting <a href="https://github.com/vuejs/core/blob/f66a75ea75c8aece065b61e2126b4c5b2338aa6e/packages/runtime-dom/src/components/TransitionGroup.ts#L186-L187">transition duration to 0</a> to get the new position immediately so we could start animating.</p>
<p>Then it comes to do the transitions for different types of tokens:</p>
<h3>Enter Transition</h3>
<p>The enter transition is the most straightforward one. Because the token will stay at the destination position after the transition, we don't need to do anything with the positioning. We usually just need to apply the opacity transition to make it appear. Here we add/remove classes for users to apply the transition with CSS.</p>
<p>Pseudo-code below:</p>
<pre><code class="language-ts">for (const el of enterElements) {
  el.classList.add('transition-enter')
  el.classList.add('transition-enter-from')
}

// Replace the children of the container with
// elements from the new code
container.replaceChildren(...newChildren)
// Force layout reflow
forceReflow()

for (const el of enterElements) {
  el.classList.remove('transition-enter-from')
  el.classList.add('transition-enter-to')
}

// Here the transition starts
// from `.transition-enter-from` to `.transition-enter-to`

for (const el of enterElements) {
  // Transition Finished
  el.addEventListener('transitionend', () =&gt; {
    el.classList.remove('transition-enter-to')
  })
}
</code></pre>
<p><a href="https://github.com/shikijs/shiki-magic-move/blob/5ec48554d87b4f4e2dd2e15f27ef9e51ef00074b/src/renderer.ts#L233-L249">[actual source code]</a></p>
<h3>Leave Transition</h3>
<p>Since &quot;Leave&quot; tokens eventually disappear after the transition, we need to keep them in the DOM tree for a while for animations but we don't want them to participate in the layout. We can apply <code>position: absolute</code> to them and set the <code>top</code> and <code>left</code> to the original position to make them stay in place. Then we can apply the opacity transition to make them disappear.</p>
<p>Pseudo-code below:</p>
<pre><code class="language-ts">for (const el of leaveElements) {
  // Get the position of the token stored before
  const pos = position.get(el)!
  // Set absolute position
  el.style.position = 'absolute'
  el.style.top = `${pos.y}px`
  el.style.left = `${pos.x}px`

  el.classList.add('transition-leave')
  el.classList.add('transition-leave-from')
}

// Replace the children of the container
// Same as the enter transition
container.replaceChildren(...newChildren)
forceReflow()

for (const el of enterElements) {
  el.classList.remove('transition-leave-from')
  el.classList.add('transition-leave-to')
}

for (const el of enterElements) {
  el.addEventListener('transitionend', () =&gt; {
    el.classList.remove('transition-leave-to')
  })
}
</code></pre>
<p><a href="https://github.com/shikijs/shiki-magic-move/blob/5ec48554d87b4f4e2dd2e15f27ef9e51ef00074b/src/renderer.ts#L206-L229">[actual source code]</a></p>
<h3>Move Transition</h3>
<p>To animate &quot;Move&quot; tokens requires a bit more work. We use a technique called <a href="https://aerotwist.com/blog/flip-your-animations/">&quot;FLIP&quot;</a> (First, Last, Invert, Play) to make the transition smooth. We need to record the position of each token before the transition starts, then apply the absolute position to them during the transition, and then restore it to the inline layout after the transition ends.</p>
<p>It's a bit unintuitive to understand at first, but luckily <a href="https://css-tricks.com/author/davidkpiano/">David Khourshid</a> made a great explanation at <a href="https://css-tricks.com/animating-layouts-with-the-flip-technique/">Animating Layouts with the FLIP Technique</a>, definitely worth reading!</p>
<p>Pseudo-code below:</p>
<pre><code class="language-ts">for (const el of moveElements) {
  const newPos = el.getBoundingClientRect()
  const oldPos = position.get(el)!
  const dx = oldPos.x - newPos.x
  const dy = oldPos.y - newPos.y

  // Set duration to 0 to get the new position immediately
  el.style.transitionDuration = '0ms'
  el.style.transitionDelay = '0ms'
  // Transform new elements to the old position
  el.style.transform = `translate(${dx}px, ${dy}px)`
}

// Replace the children of the container
container.replaceChildren(...newChildren)
forceReflow()

for (const el of moveElements) {
  // Remove transform overrides,
  // so it will start animating back to the new position
  el.classList.add('transition-move')
  el.style.transform = ''
  el.style.transitionDuration = ''
  el.style.transitionDelay = ''
}

for (const el of moveElements) {
  el.addEventListener('transitionend', () =&gt; {
    el.classList.remove('transition-move')
  })
}
</code></pre>
<p><a href="https://github.com/shikijs/shiki-magic-move/blob/5ec48554d87b4f4e2dd2e15f27ef9e51ef00074b/src/renderer.ts#L254-L276">[actual source code]</a></p>
<h2>Integrations</h2>
<p>Shiki Magic Move is open-sourced at <GitHubLink repo="shikijs/shiki-magic-move" />. The core logic is pretty lightweight and framework-agnostic, despite that the library is a bit low-level. Currently, I only have the bandwidth to make a Vue wrapper for it, and I am counting on you to contribute and add more first-class integrations for other frameworks as well as higher-level integrations.</p>
<p>If you are using <a href="https://sli.dev/">Slidev</a>, you can <a href="https://sli.dev/guide/syntax#shiki-magic-move">try it today</a> to enhance your slides with Magic!</p>
<p>Hope you enjoy it, happy hacking!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Anthony's Roads to Open Source - The Progressive Path]]></title>
            <link>https://antfu.me/posts/roads-to-oss-progressive-vueams-2024</link>
            <guid isPermaLink="true">https://antfu.me/posts/roads-to-oss-progressive-vueams-2024</guid>
            <pubDate>Thu, 29 Feb 2024 08:00:00 GMT</pubDate>
            <description><![CDATA[Anthony's Roads to Open Source - The Progressive Path - Vue Amsterdam]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Slides: <a href="https://antfu.me/talks/2024-02-29">PDF</a> | <a href="https://talks.antfu.me/2024/vue-amsterdam">SPA</a></p>
<p>Made with <Slidev class="inline"/> <a href="https://github.com/slidevjs/slidev"><strong>Slidev</strong></a> - presentation slides for developers.</p>
</blockquote>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[TwoSlash with Shiki]]></title>
            <link>https://antfu.me/posts/shikiji-twoslash</link>
            <guid isPermaLink="true">https://antfu.me/posts/shikiji-twoslash</guid>
            <pubDate>Tue, 12 Dec 2023 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Here is an example of using <a href="https://twoslash.netlify.app/">TwoSlash</a> with <a href="https://github.com/shikijs/shiki">Shiki</a>:</p>
<pre><code class="language-ts">// @errors: 2540
console.log((1 + 2 + 3 + 4).toFixed(2))
//                            ^|

interface Todo {
  title: string
}

const todo: Readonly&lt;Todo&gt; = {
  title: 'Delete inactive users',
//  ^?
}

todo.title = 'Hello'
</code></pre>
<p>Try hovering on each token to see the type information. Shiki runs <a href="https://github.com/microsoft/vscode-oniguruma"><code>oniguruma</code></a> to give syntax highlighting, and TwoSlash runs <code>typescript</code> to give type information. Both are quite heavy libraries, but this is done <strong>ahead of time on building</strong>, it means that what you see here is completely static pure HTML and CSS!</p>
<p>The Shiki integration allows you to provide <a href="https://shiki.style/packages/twoslash#renderers">custom renderer</a> of how the HTML been generated based on AST. This allows us to have dual themes support, and also the syntax highlighting for the type information.</p>
<p>Check <a href="https://shiki.style/packages/twoslash"><code>@shikijs/twoslash</code></a> and <a href="https://github.com/antfu/antfu.me/commit/d2dfb25139e9f2d42f4135998ad2052179237641#diff-6a3b01ba97829c9566ef2d8dc466ffcffb4bdac08706d3d6319e42e0aa6890dd">commit for the integration on antfu.me</a>, for more information :)</p>
<details>
<summary>The input code</summary>
<pre><code class="language-md">```ts twoslash
// @errors: 2540
console.log((1 + 2 + 3 + 4).toFixed(2))
//                            ^|

interface Todo {
  title: string
}

const todo: Readonly&lt;Todo&gt; = {
  title: 'Delete inactive users',
//  ^?
}

todo.title = 'Hello'
```
</code></pre>
</details>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[GitHub-style Alerts]]></title>
            <link>https://antfu.me/posts/github-alerts</link>
            <guid isPermaLink="true">https://antfu.me/posts/github-alerts</guid>
            <pubDate>Thu, 30 Nov 2023 10:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>[!TIP]<br>
Just a quick tip, that you can use <code>&gt; [!TIP]</code> to create<br>
a GitHub-style alert in your <a href="http://README.md">README.md</a> like this.</p>
</blockquote>
<pre><code class="language-md">&gt; [!TIP]
&gt; Just a quick tip, that you can use `&gt; [!TIP]` to create
&gt; a GitHub-style alert in your README.md like this.
</code></pre>
<p>The original <a href="https://github.com/orgs/community/discussions/16925">GitHub proposal is here</a>. It's rolled out basically everywhere on GitHub now.</p>
<h2>Use it in your website</h2>
<p>And in case you like it, and wanted to use it in your own website, I wrote a quick <code>markdown-it</code> plugin for it:</p>
<p>
<GitHubLink repo="antfu/markdown-it-github-alerts" />
</p>
<h3>Vite</h3>
<p>If you are using <a href="https://github.com/unplugin/unplugin-vue-markdown"><code>unplugin-vue-markdown</code></a>:</p>
<pre><code class="language-ts">import MarkdownItGitHubAlerts from 'markdown-it-github-alerts'
// vite.config.ts
import Markdown from 'unplugin-vue-markdown/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    Markdown({
      markdownItSetup(md) {
        md.use(MarkdownItGitHubAlerts)
      }
    })
  ],
})
</code></pre>
<h3>VitePress</h3>
<p>If you are using VitePress, you can add this to your <code>.vitepress/config.js</code>:</p>
<pre><code class="language-ts">import MarkdownItGitHubAlerts from 'markdown-it-github-alerts'
import { defineConfig } from 'vitepress'

export default defineConfig({
  markdown: {
    config(md) {
      md.use(MarkdownItGitHubAlerts)
    }
  }
})
</code></pre>
<h3>Nuxt Content</h3>
<p>Nuxt Content use <a href="https://github.com/remarkjs/remark"><code>remark</code></a> instead of <code>markdown-it</code>, so the plugin won't work yet. I will implement it later if there is demand.</p>
<h2>Reference</h2>
<ul>
<li><a href="https://github.com/antfu/antfu.me/commit/72d8dc2fb70bf21582c42d9424337560a7edea6b">Commit of this blog post and setting it up</a></li>
<li><a href="https://github.com/vuejs/vitepress/issues/3278">Proposal to VitePress for adding this by default</a></li>
</ul>
<blockquote>
<p>[!IMPORTANT]<br>
Thanks for reading! :)</p>
</blockquote>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Nuxt Devtools v1.0]]></title>
            <link>https://antfu.me/posts/nuxt-devtools-v1</link>
            <guid isPermaLink="true">https://antfu.me/posts/nuxt-devtools-v1</guid>
            <pubDate>Mon, 13 Nov 2023 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><a href="https://nuxt.com/blog/nuxt-devtools-v1-0">Go to nuxt.com and read the full announcement</a>.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Anthony's Roads to Open Source - The Set Theory]]></title>
            <link>https://antfu.me/posts/roads-to-oss-set-theory-vuefesjapan-2023</link>
            <guid isPermaLink="true">https://antfu.me/posts/roads-to-oss-set-theory-vuefesjapan-2023</guid>
            <pubDate>Sat, 28 Oct 2023 08:00:00 GMT</pubDate>
            <description><![CDATA[Anthony's Roads to Open Source - The Set Theory - Vue Fes Japan 2023]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Slides: <a href="https://antfu.me/talks/2023-10-28">PDF</a> | <a href="https://talks.antfu.me/2023/vuefesjapan">SPA</a></p>
<p>Made with <Slidev class="inline"/> <a href="https://github.com/slidevjs/slidev"><strong>Slidev</strong></a> - presentation slides for developers.</p>
</blockquote>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Now, and the Future of Nuxt Devtools]]></title>
            <link>https://antfu.me/posts/now-and-future-devtools-nuxt-nation-2023</link>
            <guid isPermaLink="true">https://antfu.me/posts/now-and-future-devtools-nuxt-nation-2023</guid>
            <pubDate>Wed, 18 Oct 2023 08:00:00 GMT</pubDate>
            <description><![CDATA[Now, and the Future of Nuxt Devtools - Nuxt Nation 2023]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Slides: <a href="https://antfu.me/talks/2023-10-18">PDF</a> | <a href="https://talks.antfu.me/2023/nuxt-devtools-next">SPA</a></p>
<p>Recording: <a href="https://youtu.be/E6kTiIbU3N8">YouTube</a></p>
<p>Made with <Slidev class="inline"/> <a href="https://github.com/slidevjs/slidev"><strong>Slidev</strong></a> - presentation slides for developers.</p>
</blockquote>
<YouTubeEmbed id="E6kTiIbU3N8" />
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Anthony's Roads to Open Source - The Set Theory]]></title>
            <link>https://antfu.me/posts/roads-to-oss-set-theory-viteconf-2023</link>
            <guid isPermaLink="true">https://antfu.me/posts/roads-to-oss-set-theory-viteconf-2023</guid>
            <pubDate>Thu, 05 Oct 2023 08:00:00 GMT</pubDate>
            <description><![CDATA[Anthony's Roads to Open Source - The Set Theory - ViteConf 2023]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Slides: <a href="https://antfu.me/talks/2023-10-05">PDF</a> | <a href="https://talks.antfu.me/2023/viteconf">SPA</a></p>
<p>Recording: <a href="https://www.youtube.com/watch?v=NJbCfAKtxUI">YouTube</a></p>
<p>Made with <Slidev class="inline"/> <a href="https://github.com/slidevjs/slidev"><strong>Slidev</strong></a> - presentation slides for developers.</p>
</blockquote>
<YouTubeEmbed id="NJbCfAKtxUI" />
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Animated SVG Logo]]></title>
            <link>https://antfu.me/posts/animated-svg-logo</link>
            <guid isPermaLink="true">https://antfu.me/posts/animated-svg-logo</guid>
            <pubDate>Wed, 19 Jul 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I recently replaced the logo on the top left corner with an animated SVG:</p>
<p flex>
  <a href="/favicon-animated.svg" target="_blank" important-border-none p4 ma>
    <Logo w-30 />
  </a>
</p>
<h2>Inspiration</h2>
<p>The first time I saw such stroke animations in SVG is the <a href="https://icones.js.org/collection/line-md">Material Line Icons</a> by <a href="https://github.com/cyberalien">Vjacheslav Trushkin</a>. It was cool, but I never thought about making one my own until I saw <a href="https://muan.co/">Mu-An Chiou</a>'s <a href="https://muan.co/pages/banners">banner</a> on her website. I suddenly feel like I want to be the cool guy too!</p>
<h2>Breakdown</h2>
<p>I downloaded Mu-An's SVG to read the code, cross-referencing the Material Line Icons. I found the trick is quite interesting, they animated <code>stroke-dasharray</code> to achieve the effect. This feels quite unintuitive as when you check the <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray">MDN documentation</a>, it looks like a pretty boring attribute.</p>
<p>I searched a bit more and found these two interesting articles:</p>
<ul>
<li><a href="https://jakearchibald.com/2013/animated-line-drawing-svg/">Animated line drawing in SVG</a> by Jake Archibald</li>
<li><a href="https://css-tricks.com/svg-line-animation-works/">How SVG Line Animation Works</a> by Chris Coyier</li>
</ul>
<p>They covered this technique very well, highly recommend reading them if you are interested. Basically, the animation is done by a <strong>very long</strong> dash moving, in which you will see the dash as the drawing line and the gap as empty space. The length and position of the dash are controlled by <code>stroke-dasharray</code> and <code>stroke-dashoffset</code>, which are both animatable.</p>
<h2>The Original Logo</h2>
<p>My original logo <a href="/logo.svg" target="_blank" important-border-none inline-block><img src="https://antfu.me/logo.svg" h-1.5em dark:filter-invert important-m0 inline-block alt="My Logo in SVG" /></a> comes from around 8 years ago, I draw it with a pressure-sensitive pen on my Surface Pro 4. It was used as a temporary placeholder for the portfolio I was trying to build at that time. I later image-traced it with Adobe Illustrator to get the SVG version. Surprising to recall, it has been so long since then.</p>
<div rounded shadow of-hidden border="~ base op20">
<img src="/images/animated-svg-logo-vector.png" dark:invert-95 important-m0>
</div>
<h2>Rework the Logo</h2>
<p>As we see, the animation is done by moving the dash on strokes, while my Logo is a vector outline with multiple control points. So I need to redraw it with a single stroke. It took a bit of practice to get used to the <a href="https://blog.openreplay.com/using-the-pen-tool-in-figma/">pen tool</a>, I managed to make it with Figma.</p>
<div rounded shadow of-hidden border="~ base op20">
<img src="/images/animated-svg-logo-redraw.png" dark:invert-95 important-m0>
</div>
<p>Manually adding the styles in the exported SVG,</p>
<pre><code class="language-css">@media (prefers-reduced-motion) {
  path {
    animation: none !important;
    stroke-dasharray: unset !important;
  }
}
@keyframes grow {
  0% {
    stroke-dashoffset: 1px;
    stroke-dasharray: 0 350px;
    opacity: 0;
  }
  10% {
    opacity: 1;
  }
  40% {
    stroke-dasharray: 350px 0;
  }
  /* Moving back */
  85% {
    stroke-dasharray: 350px 0;
  }
  95%,
  to {
    stroke-dasharray: 0 350px;
  }
}
path {
  stroke-dashoffset: 1px;
  stroke-dasharray: 350px 0;
  animation: grow 10s ease forwards infinite;
  transform-origin: center;
  stroke: #303030;
  animation-delay: 0s;
}
</code></pre>
<p>now we have a decent animated logo:</p>
<p flex>
  <a href="/favicon-animated-stroke.svg" target="_blank" important-border-none p4 ma>
    <LogoStroke w-50 />
  </a>
</p>
<p>The only downside is that the stroke is evenly thick everywhere, making it looks less like a signature. I tried to look for solutions and end up with the <a href="https://www.w3.org/Graphics/SVG/WG/wiki/Proposals/Variable_width_stroke">Variable width stroke proposal</a>, however, it does not seem to be going anywhere. Well, it's stroke anyway, it's supposed to be even. Giving the animation is super cool already, what else can I ask for?</p>
<h2>Variable Stroke Width</h2>
<p>When I almost gave up, I was playing around with Figma to do some final cleanup with the drafts, I suddenly realized that SVG does have mask support. So what if I have the original SVG shape as the mask, and let the stroke animate inside the mask? So I gave it a try and surprisingly it works!</p>
<div rounded shadow of-hidden border="~ base op20">
<img src="/images/animated-svg-logo-mask.png" dark:invert-95 important-m0>
</div>
<p>Basically we are using the mask to limit the stroke's visibility, a trick to workaround the limitation of the stroke width. Note it's not 100% pixel-perfect, as in the interaction we can't control the stroke width, so the stroke will be a bit off the mask. We can try to adjust the mask to make it look a bit better, but you will still see a big glitch when zooming in a lot. I guess it might be possible to solve this with multiple strokes and masks, but this one is already quite good to me.</p>
<p flex>
  <a href="/favicon-animated.svg" target="_blank" important-border-none p4 ma>
    <Logo w-50 />
  </a>
</p>
<p>Hope you enjoy the article, and I'd love to see your animated SVG logo too!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[pnpm on External disk]]></title>
            <link>https://antfu.me/posts/pnpm-external-disk</link>
            <guid isPermaLink="true">https://antfu.me/posts/pnpm-external-disk</guid>
            <pubDate>Thu, 13 Jul 2023 12:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>If you tried to use <a href="https://pnpm.io/">pnpm</a> to install a project on an external disk, it may not work right away because pnpm is heavily relying on symlinks, which doesn't work cross mount points. To workaround this, you can add the following config to your <code>.npmrc</code>:</p>
<pre><code class="language-ini">package-import-method=copy
prefer-symlinked-executables=false
</code></pre>
<p>This will make pnpm copy the files instead of symlinking them. And expose the executables in <code>node_modules/.bin</code> correctly.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Stable Diffusion QR Code 101]]></title>
            <link>https://antfu.me/posts/ai-qrcode-101</link>
            <guid isPermaLink="true">https://antfu.me/posts/ai-qrcode-101</guid>
            <pubDate>Mon, 10 Jul 2023 05:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<p>
<span i-carbon-events mr1 /> Co-authored by <a href="https://antfu.me" target="_blank">Anthony Fu</a>, <a href="https://space.bilibili.com/339984/" target="_blank">赛博迪克朗</a>, wangcai and <a href="https://www.xiaohongshu.com/user/profile/5be8fb806b58b745447aab0f" target="_blank">代々木</a>
</p>
<blockquote>
<p><strong>This is a live document</strong>, will be updated as we learn more. Check back occasionally.</p>
</blockquote>
<p>A summary of discussions made in <a href="https://discord.gg/V9CNuqYfte">QRBTF's Discord server</a> and <a href="https://chat.antfu.me">Anthony's Discord server</a>. Thanks to everyone who participated in those servers.</p>
<h2>What's a Stable Diffusion QR Code?</h2>
<p>Images that are generated with <a href="https://stability.ai/blog/stable-diffusion-public-release">Stable Diffusion</a> with QR Codes as <a href="https://github.com/lllyasviel/ControlNet">ControlNet</a>'s input, making the QR Code data points blend into the artwork while still being scannable by QR Code readers.</p>
<figure pt-5>
  <div grid="~ cols-1 md:cols-3 gap-1" lg:scale-120 md:scale-110>
    <img src="https://antfu.me/images/ai-qrcode-101-example2.jpg" rounded shadow important-m0 />
    <img src="/images/ai-qrcode-101-example1.jpg" rounded shadow important-m0 />
    <img src="/images/ai-qrcode-101-example3.jpg" rounded shadow important-m0 />
  </div>
  <figcaption important-mt8 text-center>Examples from <a href="https://qrbtf.com/">QRBTF.com</a></figcaption>
</figure>
<p>The original idea was created by the people behind <a href="https://qrbtf.com/">QRBTF</a>, and was first revealed on <a href="https://www.reddit.com/r/StableDiffusion/comments/141hg9x/controlnet_for_qr_code/">this reddit</a> by <a href="https://www.reddit.com/user/nhciao/">nhciao</a>.</p>
<p><a href="https://qrbtf.com/">QRBTF</a> recently launched <a href="https://qrbtf.com/ai">their online generation service for open beta</a>. As of July 14th, 2023, they haven't released their model or methodology yet, you can join their <a href="https://discord.gg/V9CNuqYfte">Discord server</a> to follow the latest news.</p>
<p>The methods mentioned in this guide are <strong>based on community research and experiments</strong>.</p>
<h2>How to Generate?</h2>
<p>There are a few online services you can try, but this guide will focus on doing it locally on our own. You will need the basic knowledge of Stable Diffusion and ControlNet, a computer with a GPU (or a cloud GPU instance) to start.</p>
<p>If you are new to Stable Diffusion, we recommend reading these guides to get started:</p>
<ul>
<li><a href="https://aituts.com/stable-diffusion/">Stable Diffusion Knowledge Hub</a></li>
<li><a href="https://aituts.com/stable-diffusion-lora/">Stable Diffusion LoRA Models</a></li>
</ul>
<p>Once you set them up, there are two approaches to generating a stylized QR Code:</p>
<h3>Method A: <strong>Text to Image with ControlNet</strong></h3>
<p>Generate an image with prompts, and use ControlNet with a QR Code input to intervention the generation process.</p>
<ul>
<li><a href="/posts/ai-qrcode">Stylistic QR Code with Stable Diffusion</a> - by Anthony Fu</li>
<li><a href="/posts/ai-qrcode-refine">Refining AI Generated QR Code</a> - by Anthony Fu</li>
<li><a href="https://www.bilibili.com/video/BV1zF411R7xg/">[Video] 二维码融合技术2.0</a> - by 赛博迪克朗</li>
</ul>
<h3>Method B: <strong>Image to Image</strong></h3>
<p>Use a QR Code image as input, and let Stable Diffusion redraw each part of the QR Code. Doesn't require ControlNet.</p>
<ul>
<li><a href="https://stable-diffusion-art.com/qr-code/">How to make a QR code with Stable Diffusion</a> - by Andrew</li>
</ul>
<h3>Our Recommendation</h3>
<p>We found that Method A, <strong>Text to Image approach produces much better results</strong>, and it's easier to control the output. We will mostly focus on that approach in this guide.</p>
<h2>ControlNet Models</h2>
<p>Here are a few ControlNet models we found useful:</p>
<ul>
<li><a href="https://civitai.com/models/90940/controlnet-qr-pattern-qr-codes">QR Pattern</a></li>
<li><a href="https://huggingface.co/monster-labs/control_v1p_sd15_qrcode_monster">QR Code Monster</a></li>
<li><a href="https://huggingface.co/ioclab/ioc-controlnet/tree/main/models">IoC Lab Control Net</a>
<ul>
<li>Brightness Model</li>
<li>Illumination Model</li>
</ul>
</li>
</ul>
<p>See <a href="#model-comparison">model comparison</a> section for more details.</p>
<h2>The Code is Not Scannable</h2>
<p>Before going into details, let's picture the goal of your QR Code first. Here are 3 typical approaches listed by <a href="https://github.com/1r00t">@1r00t</a>:</p>
<ul>
<li>Artistic code that scans 100% and is obvious but creative</li>
<li>Code that is kind of hidden but with a bit of fiddling you get it</li>
<li>Code that is completely hidden as a sort of secret message</li>
</ul>
<p>All these approaches are viable. They are on a different balance between the art and the functionality. It's better to have such expectations so you can tune your models, parameters and prompts accordingly.</p>
<hr>
<h3>Scanners</h3>
<p>When the images are generated, we will use a QR Code scanner to verify if the code is scannable.</p>
<p>If your goal is to make a more blended-in QR Code, and you are okay with the code not being scannable by all QR Code readers, it's better to use an error-tolerant scanner to verify. We recommend using iOS's code <strong>scanner from the Control Center</strong>, or the scanner from <a href="https://www.wechat.com/en/">WeChat</a> to verify your QR Code. They are the most tolerant ones we found so far.</p>
<p>Meanwhile, if you failed to find a good scanner on your phone, or want to verify the QR Codes directly in your computer, we recently enrolled a <a href="https://qrcode.antfu.me/#scan">new scanner in Anthony's QR Toolkit</a>, based on <a href="https://docs.opencv.org/4.5.4/d5/d04/classcv_1_1wechat__qrcode_1_1WeChatQRCode.html">WeChat's open sourced algorithm</a> (Ported to WebAssembly, source code at <a href="https://github.com/antfu/qr-scanner-wechat">antfu/qr-scanner-wechat</a>).</p>
<p><img src="/images/ai-qrcode-101-toolkit-scanner.png" alt=""></p>
<hr>
<h3>Compare with the Original QR Code</h3>
<p>You can use <a href="https://qrcode.antfu.me/">Anthony's QR Toolkit</a> to compare the generated QR Code with the original one. It will show you the mismatches and help you to optimize the generation process.</p>
<p><img src="/images/ai-qrcode-refine-compare-2.png" alt=""></p>
<p>Read more about it in <a href="/posts/ai-qrcode-refine">this post</a>.</p>
<hr>
<h2>Parameters</h2>
<h3>ControlNet</h3>
<p>The parameters of the ControlNet affect when and how the control is applied to the generation process.</p>
<ul>
<li><strong>Control weight</strong> - The weight of the ControlNet. The higher the weight, the more the output will be affected by the ControlNet.</li>
<li><strong>Start control step</strong> - The percentage of the generation process when the ControlNet starts to take effect.</li>
<li><strong>End control step</strong> - The percentage of the generation process when the ControlNet stops taking effect.</li>
</ul>
<div relative flex="~ col items-center" py3>
  <div absolute top-0 left="1/14" translate-x="-1/2" hidden md:block>prompts</div>
  <div absolute top-0 left="5/12" translate-x="-1/2">prompts + control net</div>
  <div absolute top-0 left="10/12" translate-x="-1/2">prompts</div>
  <div w-full mt-1.5em h-1em rounded bg-gray:10 border="~ base" relative>
    <div absolute left="1/6" bg-yellow op80 w="3/6" h-full/>
  </div>
  <div absolute top-2.7em flex="~ col items-center" left="1/6" translate-x="-1/2">
    <div i-ri-arrow-up-s-fill text-lg/>
    <div>Control Start</div>
  </div>
   <div absolute top-2.7em flex="~ col items-center" left="4/6" translate-x="-1/2">
    <div i-ri-arrow-up-s-fill text-lg/>
    <div>Control End</div>
  </div>
</div>
<div mt-14 />
<p>The start control step will allow the prompts and the model to be creative before it knows the QR Code control exists. And the end control step will allow the model to try to blend the QR Code into the artwork more (but will make the code less scannable).</p>
<p>It requires a few trials and errors to find the right balance so that the ControlNet has enough time to intervene, but not too much so the code can be artistic enough.</p>
<p>Different models might have different strengths of the control, so you might need to adjust the parameters accordingly. It's better to read their instructions first.</p>
<hr>
<div border="~ rounded-full base" px3 py1 inline text-sm float-right>
<span i-ri-book-2-line /> Credits to <a href="https://space.bilibili.com/339984/" target="_blank">赛博迪克朗</a>
</div>
<h3>Model Comparison</h3>
<p>Thanks a lot to <a href="https://space.bilibili.com/339984/">赛博迪克朗</a> for running the following matrixes against each model.</p>
<p>Here is the original image (without ControlNet) and the QR Code Input:</p>
<div grid="~ cols-2 gap-4">
  <figure important-m0>
    <img src="/images/ai-qrcode-101-multi-cn-original.png" rounded shadow  />
    <figcaption text-center>
      Original Image
    </figcaption>
  </figure>
  <figure important-m0>
    <img src="/images/ai-qrcode-101-multi-cn-qr.png" rounded shadow  />
    <figcaption text-center>
      QR Code
    </figcaption>
  </figure>
</div>
<p>The comparison matrixes are generated with the exact same prompts and same seed as the original image, but only the parameters of the ControlNet are changed.</p>
<details>
<summary cursor-pointer select-none>Detailed prompts and paramaters</summary>
<div class="code-wrap" border="~ base rounded" px4 pt3 mt2>
<pre><code class="language-ruby">best quality, masterpiece, depth of field, 1girl, dress, trees, flowers, sky, water
</code></pre>
<pre><code class="language-ruby">NSFW, nude, bad-hands-5, bad-picture-chill-75v, badhandv4, easynegative, ng_deepnegative_v1_75t, verybadimagenegative_v1.3, bhands-neg, watermark, character watermark, photo date watermark, Date watermarking
</code></pre>
<ul>
<li>Checkpoint: <a href="https://civitai.com/models/28779/primemix">PrimeMix</a></li>
<li>Steps: 50</li>
<li>Sampler: DPM++ 2M SDE Karras</li>
<li>CFG scale: 7</li>
<li>Seed: 1902542336</li>
<li>Size: 768x1024</li>
</ul>
</div>
</details>
<p>You can <strong>drag the sliders</strong> below to see the difference between the start and end control steps:</p>
<h4><a href="https://civitai.com/models/90940/controlnet-qr-pattern-qr-codes">QR Pattern Model</a></h4>
<div grid="~ cols-1 md:cols-2 gap-2">
  <QRCodeMatrix
    src="/images/ai-qrcode-101-matrix-pattern-start.webp"
    xTitle="Weight"
    :xScale="{ min: 0.7, max: 1.6, step: 0.1}"
    :xValue="3"
    yTitle="Start"
    :yScale="{ min: 0.1, max: 0.5, step: 0.1}"
    :aspectRatio="0.75"
    :fixedRowsAfter="[['End', '1.0']]"
    :fixedRowsBefore="[['Model', 'QR Pattern']]"
  >
    <template #post="{ xValue, yValue }">
      <QRCodeControlNetScale :start="+yValue" :end="1" :weight="+xValue" />
    </template>
  </QRCodeMatrix>
  <QRCodeMatrix
    src="/images/ai-qrcode-101-matrix-pattern-end.webp"
    xTitle="Weight"
    :xScale="{ min: 0.7, max: 1.6, step: 0.1 }"
    :xValue="3"
    yTitle="End"
    :yScale="{ min: 0.4, max: 1.0, step: 0.1 }"
    :aspectRatio="0.75"
    :fixedRowsBetween="[['Start', '0']]"
    :fixedRowsBefore="[['Model', 'QR Pattern']]"
  >
    <template #post="{ xValue, yValue }">
      <QRCodeControlNetScale :start="0" :end="+yValue" :weight="+xValue" />
    </template>
  </QRCodeMatrix>
</div>
<h4><a href="https://huggingface.co/monster-labs/control_v1p_sd15_qrcode_monster">QR Code Monster Model</a></h4>
<div grid="~ cols-1 md:cols-2 gap-2">
  <QRCodeMatrix
    src="/images/ai-qrcode-101-matrix-monster-start.webp"
    xTitle="Weight"
    :xScale="{ min: 0.7, max: 1.6, step: 0.1 }"
    :xValue="3"
    yTitle="Start"
    :yScale="{ min: 0.1, max: 0.5, step: 0.1 }"
    :aspectRatio="0.75"
    :fixedRowsAfter="[['End', '1.0']]"
    :fixedRowsBefore="[['Model', 'QR Code Monster']]"
  >
    <template #post="{ xValue, yValue }">
      <QRCodeControlNetScale :start="+yValue" :end="1" :weight="+xValue" />
    </template>
  </QRCodeMatrix>
  <QRCodeMatrix
    src="/images/ai-qrcode-101-matrix-monster-end.webp"
    xTitle="Weight"
    :xScale="{ min: 0.7, max: 1.6, step: 0.1 }"
    :xValue="3"
    yTitle="End"
    :yScale="{ min: 0.4, max: 1.0, step: 0.1 }"
    :aspectRatio="0.75"
    :fixedRowsBetween="[['Start', '0']]"
    :fixedRowsBefore="[['Model', 'QR Code Monster']]"
  >
    <template #post="{ xValue, yValue }">
      <QRCodeControlNetScale :start="0" :end="+yValue" :weight="+xValue" />
    </template>
  </QRCodeMatrix>
</div>
<h4><a href="https://huggingface.co/ioclab/ioc-controlnet/tree/main/models">IoC Lab Brightness Model</a></h4>
<div grid="~ cols-1 md:cols-2 gap-2">
  <QRCodeMatrix
    src="/images/ai-qrcode-101-matrix-brightness-start.webp"
    xTitle="Weight"
    :xScale="{ min: 0.1, max: 0.9, step: 0.1 }"
    :xValue="1"
    yTitle="Start"
    :yScale="{ min: 0, max: 0.5, step: 0.1 }"
    :aspectRatio="0.75"
    :fixedRowsAfter="[['End', '0']]"
    :fixedRowsBefore="[['Model', 'IoC Lab Brightness']]"
  >
    <template #post="{ xValue, yValue }">
      <QRCodeControlNetScale :start="+yValue" :end="1" :weight="+xValue" />
    </template>
  </QRCodeMatrix>
  <QRCodeMatrix
    src="/images/ai-qrcode-101-matrix-brightness-end.webp"
    xTitle="Weight"
    :xScale="{ min: 0.1, max: 0.9, step: 0.1 }"
    :xValue="2"
    yTitle="End"
    :yScale="{ min: 0.5, max: 1.0, step: 0.1 }"
    :aspectRatio="0.75"
    :fixedRowsBetween="[['Start', '0']]"
    :fixedRowsBefore="[['Model', 'IoC Lab Brightness']]"
  >
    <template #post="{ xValue, yValue }">
      <QRCodeControlNetScale :start="0" :end="+yValue" :weight="+xValue" />
    </template>
  </QRCodeMatrix>
</div>
<hr>
<h2>Improve the Result</h2>
<p>Say that you already generated a bunch of QR Codes and find some of them you like. You want to improve them to make them more scannable, or more blended-in, or more artistic. Here are some tips we found useful.</p>
<h3>Tweak the Input</h3>
<p>The <strong>input QR Code is one of the most important parts</strong> of the whole process to generate well-blended code.</p>
<p>You can refer to <a href="/posts/ai-qrcode-refine#generating-the-base-qr-code">this post</a> to see a comparison of how different QR Code input affects the output.</p>
<p><img src="/images/ai-qrcode-refine-input-compare.jpg" alt="Comparison grid between different styled QR Code as input"></p>
<p>We recommend using <a href="https://qrcode.antfu.me/">Anthony's QR Toolkit</a> to generate the QR Code. It allows you to customize the QR Code and distort it as needed.</p>
<p>Meanwhile, the margin area of the QR Code also affects the look and feel, for example:</p>
<div flex="~ col items-center gap-4" py4>
<QRCodeCompare scale-85 md:scale-100 h-100 input="/images/ai-qrcode-101-input-edit1-i.png" output="/images/ai-qrcode-101-input-edit1-o.jpg" />
<div><div i-ri-arrow-down-line/> Adding some noise to the margin</div>
<QRCodeCompare scale-85 md:scale-100 h-100 input="/images/ai-qrcode-101-input-edit2-i.png" output="/images/ai-qrcode-101-input-edit2-o.jpg" />
<div><div i-ri-arrow-down-line/> Manually connect some points in margin (Photoshop etc.)</div>
<QRCodeCompare scale-85 md:scale-100 h-100 input="/images/ai-qrcode-101-input-edit6-i.png" output="/images/ai-qrcode-101-input-edit6-o.jpg" />
</div>
<hr>
<div border="~ rounded-full base" px3 py1 inline text-sm float-right>
<span i-ri-book-2-line /> Credits to <a href="https://www.xiaohongshu.com/user/profile/5be8fb806b58b745447aab0f" target="_blank">代々木</a>
</div>
<h3>Improve the Prompts</h3>
<p>Theoretically, you can use any prompts to generate those QR Codes.</p>
<p>To help the QR codes more blend in, we find that it's helpful to include some fluidity or fragmented items in the prompts.</p>
<h4>Example Outputs</h4>
<div py15>
  <div grid="~ md:cols-3 cols-2 gap-x-2 gap-y-4" lg:scale-110 md:scale-105>
    <figure important-my-0>
      <img src="/images/ai-qrcode-101-prompt-ribbon.jpg" rounded-md shadow />
      <figcaption text-center>
        <b text-lg>ribbon</b>
        <div text-xs mt1>by <a href="https://www.xiaohongshu.com/user/profile/5be8fb806b58b745447aab0f" target="_blank">代々木</a></div>
      </figcaption>
    </figure>
    <figure important-my-0>
      <img src="/images/ai-qrcode-101-prompt-feather.jpg" rounded-md shadow />
      <figcaption text-center>
        <b text-lg>feather</b>
        <div text-xs mt1>by <a href="https://www.xiaohongshu.com/user/profile/5be8fb806b58b745447aab0f" target="_blank">代々木</a></div>
      </figcaption>
    </figure>
    <figure important-my-0>
      <img src="/images/ai-qrcode-refine-distort-result.png" rounded-md shadow />
      <figcaption text-center>
        <b text-lg>plants</b>
        <div text-xs mt1>by <a href="https://antfu.me" target="_blank">Anthony Fu</a></div>
      </figcaption>
    </figure>
    <figure important-my-0>
      <img src="/images/ai-qrcode-101-prompt-bird.jpg" rounded-md shadow />
      <figcaption text-center>
        <b text-lg>bird</b>
        <div text-xs mt1>by <a href="https://www.xiaohongshu.com/user/profile/5be8fb806b58b745447aab0f" target="_blank">代々木</a></div>
      </figcaption>
    </figure>
    <figure important-my-0>
      <img src="/images/ai-qrcode-101-prompt-lace.jpg" rounded-md shadow />
      <figcaption text-center>
        <b text-lg>lace</b>
        <div text-xs mt1>by <a href="https://antfu.me" target="_blank">Anthony Fu</a></div>
      </figcaption>
    </figure>
    <figure important-my-0>
      <img src="/images/ai-qrcode-101-prompt-snow.jpg" rounded-md shadow />
      <figcaption text-center>
        <b text-lg>snow</b>
        <div text-xs mt1>by <a href="https://antfu.me" target="_blank">Anthony Fu</a></div>
      </figcaption>
    </figure>
    <figure important-my-0>
      <img src="/images/ai-qrcode-101-prompt-wave.png" rounded-md shadow />
      <figcaption text-center>
        <b text-lg>wave</b>
        <div text-xs mt1>by <a href="https://v.douyin.com/iDLHquJ/" target="_blank">五倍速企鹅</a></div>
      </figcaption>
    </figure>
     <figure important-my-0>
      <img src="/images/ai-qrcode-101-prompt-katana-swords.jpg" rounded-md shadow />
      <figcaption text-center>
        <b text-lg>katana swords</b>
        <div text-xs mt1>by <a href="https://www.xiaohongshu.com/user/profile/5be8fb806b58b745447aab0f" target="_blank">代々木</a></div>
      </figcaption>
    </figure>
    <figure important-my-0>
      <img src="/images/ai-qrcode-101-prompt-shibori-patterns.jpg" rounded-md shadow />
      <figcaption text-center>
        <b text-lg>shibori patterns</b>
        <div text-xs mt1>by <a href="https://www.xiaohongshu.com/user/profile/5be8fb806b58b745447aab0f" target="_blank">代々木</a></div>
      </figcaption>
    </figure>
    <figure important-my-0>
      <img src="/images/ai-qrcode-101-prompt-buildings.png" rounded-md shadow />
      <figcaption text-center>
        <b text-lg>buildings</b>
        <div text-xs mt1>by <a href="https://space.bilibili.com/251938958" target="_blank">阿帝</a></div>
      </figcaption>
    </figure>
    <figure important-my-0>
      <img src="/images/ai-qrcode-101-prompt-leaf.png" rounded-md shadow />
      <figcaption text-center>
        <b text-lg>leaf</b>
        <div text-xs mt1>by <a href="https://v.douyin.com/iDLHquJ/" target="_blank">五倍速企鹅</a></div>
      </figcaption>
    </figure>
  </div>
</div>
<h4>Example Prompts</h4>
<p class="code-wrap">
<p><strong>Ribbon</strong> - by <a href="https://www.xiaohongshu.com/user/profile/5be8fb806b58b745447aab0f">代々木</a></p>
<pre><code class="language-ruby">(1 girl:1.6), full body, from side, ultra wide shot, (azure blue dress:1.3), (grey long hair:1.3), (white ribbon:1.6), (white lace:1.6), BREAK, (dark background:1.3)
</code></pre>
<p><strong>Feather</strong> - by <a href="https://www.xiaohongshu.com/user/profile/5be8fb806b58b745447aab0f">代々木</a></p>
<pre><code class="language-ruby">(1 girl:1.3), upper body, (grey long hair:1.3), (blue dress:1.3), zigzag patterns, graphic impact, (white feathers:1.6), BREAK, (dark background:1.3)
</code></pre>
<p><strong>Birds</strong> - by <a href="https://www.xiaohongshu.com/user/profile/5be8fb806b58b745447aab0f">代々木</a></p>
<pre><code class="language-ruby">(1 girl:1.3), upper body, rosemaling patterns, Norwegian folk art, decorative designs, vibrant colors, (white birds:1.6), BREAK, (dark background:1.3)
</code></pre>
<p><strong>Wave</strong> - by <a href="https://v.douyin.com/iDLHquJ/">五倍速企鹅</a></p>
<pre><code class="language-ruby">(1 girl:1.3),(white dress:1.3), upper body, blonde hair, from side, decorative designs, (wave:1.3),BREAK, (blue background:1.3)
</code></pre>
<p><strong>Leaf</strong> - by <a href="https://v.douyin.com/iDLHquJ/">五倍速企鹅</a></p>
<pre><code class="language-ruby">(1 girl:1.3),(pink dress:1.3), upper body, white hair, from side, decorative designs, (leaf:1.6),BREAK, (sunset background:1.3)
</code></pre>
</p>
<hr>
<div border="~ rounded-full base" px3 py1 inline text-sm float-right>
<span i-ri-book-2-line /> Credits to wangcai
</div>
<h3>XYZ Plot</h3>
<p>In case you are uncertain about which model or prompts to use, you can utilize the XYZ Plot script to generate a matrix of images with different prompts and models for easier comparison.</p>
<p><img src="/images/ai-qrcode-101-xyz.png" alt=""></p>
<p>You can learn more about how to use XYZ plot in <a href="https://gigazine.net/gsc_news/en/20220909-automatic1111-stable-diffusion-webui-prompt-matrix/">this tutorial</a>.</p>
<p>Below is an example of a matrix runned by <code>wangcai</code>, testing some popular checkpoint models and the prompts we mentioned above. You can click to select different combinations, or click the image to see the full matrix.</p>
<QRCodeMatrixModelPrompts />
<p>Similarly, this is a matrix testing samplers:</p>
<QRCodeMatrixModelSamplers />
<p>We encourage you to try different prompts and models to find the best combination for your use case.</p>
<hr>
<div border="~ rounded-full base" px3 py1 inline text-sm float-right>
<span i-ri-book-2-line /> Credits to <a href="https://space.bilibili.com/339984/" target="_blank">赛博迪克朗</a>
</div>
<h3>Non-Square Image</h3>
<p>To make the QR Code less obvious, you can try to generate a non-square image, leaving some extra space around the QR Code for the Stable Diffusion to be creative. With that, you can shift the focus of the viewers to the other parts of the image.</p>
<figure>
  <img src="/images/ai-qrcode-101-non-square-example3.jpg" rounded shadow />
  <figcaption text-center>
    by <a href="https://antfu.me/" target="_blank">Anthony Fu</a>
  </figcaption>
</figure>
<figure>
  <img src="/images/ai-qrcode-101-non-square-example1.jpg" rounded shadow />
  <figcaption text-center>
    by <a href="https://antfu.me/" target="_blank">Anthony Fu</a>
  </figcaption>
</figure>
<p>To generate a non-square image, you can change the <strong>Resize Mode</strong> in ControlNet to <code>Resize and Fill</code> and change the Text to Image width or height.</p>
<p><img src="/images/ai-qrcode-101-non-square-resize.png" alt=""></p>
<p>Or in the <a href="https://qrcode.antfu.me/">Toolkit</a>, you click the <span i-carbon-chevron-down/> button on <strong>Margin</strong> to expand the option and have different margins for each side.</p>
<p><img src="/images/ai-qrcode-101-non-square-toolkit.png" alt=""></p>
<hr>
<div border="~ rounded-full base" px3 py1 inline text-sm float-right>
<span i-ri-book-2-line /> Credits to <a href="https://www.instagram.com/terryberrystudio" target="_blank">lameguy</a>
</div>
<h3>Perspective</h3>
<p>You can also try to apply some perspective transformation to the QR Code to make it more interesting.</p>
<div grid="~ cols-2 gap-2">
  <figure>
    <img src="/images/ai-qrcode-101-perspective-ep1.png" rounded shadow />
    <figcaption text-center>
      by <a href="https://www.instagram.com/terryberrystudio" target="_blank">lameguy</a>
    </figcaption>
  </figure>
  <figure>
    <img src="/images/ai-qrcode-101-perspective-ep2.png" rounded shadow />
    <figcaption text-center>
      by <a href="https://www.instagram.com/terryberrystudio" target="_blank">lameguy</a>
    </figcaption>
  </figure>
</div>
<hr>
<div border="~ rounded-full base" px3 py1 inline text-sm float-right>
<span i-ri-book-2-line /> Credits to <a href="https://space.bilibili.com/339984/" target="_blank">赛博迪克朗</a>
</div>
<h3>Multiple ControlNet</h3>
<p>Multiple ControlNet layers are mainly used to increase the recognizability of the image when the model control is insufficient. Try to avoid the result deviation caused by excessive changes in the picture, causing the ideal picture cannot be obtained.</p>
<p>Difficulties in recognition may be due to changes in prompts or due to the characteristics of the SD model, resulting in too trivial details of the picture or too bright/dark overall tone to make it impossible to recognize.</p>
<p>This method can effectively improve the automatic recognition success rate of scanning.</p>
<p>Usually, we use <strong>QR Code Monster</strong> or <strong>QR Code Pattern</strong> model as the main guidance model, and use the <strong>Brightness Model</strong> from IoC Lab as the auxiliary model to improve the local contrast.</p>
<blockquote>
<p><span i-ri-lightbulb-line text-yellow/> 赛博迪克朗: It's recommended to use the QR Monster model. The QR Pattern v2.0 still has too much interference, which may cause a great change in the style of the image.</p>
</blockquote>
<p>For example, running the same prompts as <a href="#model-comparison">the previous example</a>, when using the <strong>QR Code Monster</strong> model alone (single model), with control steps 0.0 to 1.0, we got the following results with different weights:</p>
<div grid="~ cols-2 md:cols-3 gap-2">
  <figure important-mb0 important-mt-2>
    <img src="/images/ai-qrcode-101-multi-cn-monster-w100.png" rounded shadow  />
    <figcaption text-center>
      Weight: 1.0
    </figcaption>
  </figure>
  <figure important-mb0 important-mt-2>
    <img src="/images/ai-qrcode-101-multi-cn-monster-w125.png" rounded shadow  />
    <figcaption text-center>
      Weight: 1.25
    </figcaption>
  </figure>
  <figure important-mb0 important-mt-2>
    <img src="/images/ai-qrcode-101-multi-cn-monster-w140.png" rounded shadow  />
    <figcaption text-center>
      Weight: 1.4
    </figcaption>
  </figure>
  <figure  important-mb0 important-mt-2>
    <img src="/images/ai-qrcode-101-multi-cn-monster-w150.png" rounded shadow  />
    <figcaption text-center>
      Weight: 1.5
    </figcaption>
  </figure>
  <figure important-mb0 important-mt-2>
    <img src="/images/ai-qrcode-101-multi-cn-monster-w160.png" rounded shadow  />
    <figcaption text-center>
      Weight: 1.6
    </figcaption>
  </figure>
  <figure important-mb0 important-mt-2>
    <img src="/images/ai-qrcode-101-multi-cn-monster-w170.png" rounded shadow  />
    <figcaption text-center>
      Weight: 1.7
    </figcaption>
  </figure>
</div>
<p>We notice that only Weight 1.5 and 1.7 are scannable (and do not have very good error tolerant), and we also see the compositions of them are changed a lot as the weight increases.</p>
<p>So if we want to keep the original composition but still have good enough recognizability, we could add the <strong>Brightness Model</strong> as the second model.</p>
<div grid="~ cols-1 md:cols-2 gap-4">
 <figure important-m0>
    <img src="/images/ai-qrcode-101-multi-cn-monster-w100-s00-e10-brightness-w015-s01-e10.png" rounded shadow  />
    <figcaption text-center font-mono important-text-xs>
      Monster &nbsp;&nbsp;: Weight <b>1.00</b> Start <b>0.0</b> End <b>1.0</b><br>
      Brightness: Weight <b>0.15</b> Start <b>0.1</b> End <b>1.0</b>
    </figcaption>
  </figure>
  <figure important-m0>
    <img src="/images/ai-qrcode-101-multi-cn-monster-w100-s00-e10-brightness-w025-s04-e08.png" rounded shadow  />
    <figcaption text-center font-mono important-text-xs>
      Monster &nbsp;&nbsp;: Weight <b>1.00</b> Start <b>0.0</b> End <b>1.0</b><br>
      Brightness: Weight <b>0.25</b> Start <b>0.4</b> End <b>0.8</b>
    </figcaption>
  </figure>
</div>
<p>We can see that even if we reduce the weight of the <strong>Monster Model</strong> to 1.0, the recognizability is as good as the single model with the Weight 1.5, while the composition is closer to the original image.</p>
<p>If you want to go further, it's also possible to try more models. For example, here is the result of using <strong>QR Code Monster</strong> and <strong>Brightness Model</strong> together with <strong>QR Pattern</strong>:</p>
<div grid="~ cols-1 md:cols-[1fr_2fr_1fr] gap-4 justify-center">
  <div />
  <figure important-m0>
    <img src="/images/ai-qrcode-101-multi-cn-monster-monster-w100-brightness-w010-s04-e08-pattern-w010-s04-e08.png" rounded shadow  />
    <figcaption text-center font-mono important-text-xs>
      Monster &nbsp;&nbsp;: Weight <b>1.00</b> Start <b>0.0</b> End <b>1.0</b><br>
      Brightness: Weight <b>0.10</b> Start <b>0.4</b> End <b>0.8</b><br>
      QR Pattern: Weight <b>0.10</b> Start <b>0.4</b> End <b>0.8</b>
    </figcaption>
  </figure>
  <div />
</div>
<blockquote>
<p><span i-ri-lightbulb-line text-yellow/> If you didn't see the tabs for multiple layers of ControlNet, you can go to the settings page to enable it:<br>
<img src="/images/ai-qrcode-101-multi-cn-settings.png" alt=""></p>
</blockquote>
<hr>
<div border="~ rounded-full base" px3 py1 inline text-sm float-right>
<span i-ri-book-2-line /> Credits to wangcai
</div>
<h3>OpenPose</h3>
<p>To get more control over the composition, you can also use other ControlNet models like OpenPose to generate a human pose and use it as the input of the QR Code.</p>
<p>For example, you can see the following image is generated with both QR Code and OpenPose as the input. With some tricks on the composition, you can shift the focus of the viewers to the other parts of the image and make the QR Code less obvious.</p>
<div flex="~ col items-center" py4>
  <QRCodeCompare scale-85 md:scale-100 h-80
    input="/images/ai-qrcode-101-openpose-qr.png"
    input2="/images/ai-qrcode-101-openpose-pose.png"
    output="/images/ai-qrcode-101-openpose-output1.jpg"
  />
  <QRCodeCompare scale-85 md:scale-100 h-80 mt4
    input="/images/ai-qrcode-101-openpose-qr3.png"
    input2="/images/ai-qrcode-101-openpose-pose3.png"
    output="/images/ai-qrcode-101-openpose-output4.png"
  />
  <QRCodeCompare scale-85 md:scale-100 h-80
    input="/images/ai-qrcode-101-openpose-qr2.png"
    input2="/images/ai-qrcode-101-openpose-pose2.png"
    output="/images/ai-qrcode-101-openpose-output3.png"
  />
</div>
<p>You can learn more about OpenPose in <a href="https://stable-diffusion-art.com/controlnet/">this tutorial</a>.</p>
<hr>
<div border="~ rounded-full base" px3 py1 inline text-sm float-right>
<span i-ri-book-2-line /> Credits to <a href="https://antfu.me" target="_blank">Anthony Fu</a>
</div>
<h3>Selective Multi-layer Control</h3>
<p>Look deep into the <a href="https://en.wikipedia.org/wiki/QR_code#Standards">QR Code specification</a>, you can see a QR Code is composed with different types of data and position patterns:</p>
<p><img src="/images/ai-qrcode-101-qr-struct.png" alt=""></p>
<p>Other than the position markers that are obvious to find, we can see there are also the <strong>Version and Format information</strong> around the position markers. Those information are quite important because it tells the scanner how to decode the QR Code properly. On the other hand, since the <strong>Data area</strong> has good error correction and duplications, it's actually fine for it to contain a few misalignment when needed. Now we realize that many QR Code that are not scannable are because those area are not distinguishable enough, causing the scanner to exit early before going into the actual data.</p>
<p>So, since the data points in a QR Code are <strong>not equally important</strong>, why would we control them equally? Maybe we could try to selective control different areas. Like increasing the control weight of the functional areas and decreasing the weight of the data area, to make the QR Code more scannable while being more artistic.</p>
<p>In the recent update of <a href="https://qrcode.antfu.me/">QR Toolkit</a>, we added a new option <strong>Render Type</strong> to only generate some specific areas of the QR Code, combining with a grey background, we could have:</p>
<blockquote>
<p><span i-ri-lightbulb-line text-yellow/> Both <strong>QR Pattern v2</strong> and <strong>QR Code Monster</strong> models support having grey as the hint of arbitrary content (bypass the control). Thanks for the information from <a href="https://civitai.com/user/Nacholmo">Nacholmo</a> and <a href="https://twitter.com/vyrilbareme">Cyril Bareme</a>.</p>
</blockquote>
<p><img src="/images/ai-qrcode-101-render-type.png" alt=""></p>
<p>With this, we could use two ControlNet layers:</p>
<ul>
<li>Layer 1: Full QR Code, with medium control weight.</li>
<li>Layer 2: Selective parts of QR Code, with <strong>strong weight</strong> but shorter control steps.</li>
</ul>
<p>For example, here I have two ControlNet layers, both using the <strong>QR Code Monster</strong> model:</p>
<QRCodeSelectiveLayers />
<blockquote>
<p><span i-ri-lightbulb-line text-yellow/> In the second layer of the example, I excluded the position markers as I was seeking for more blend-in image. You can also include them if you want to make the QR Code more scannable.</p>
</blockquote>
<p>After a few tweaks, the result are surprisingly good. It's able to retain the recognizability of the QR Code while being more artistic. Here are some of the results:</p>
<div flex="~ col items-center gap-8" py6>
  <QRCodeCompare scale-85 md:scale-100 h-100
    input="/images/ai-qrcode-101-selective-qr1.png"
    input2="/images/ai-qrcode-101-selective-qr2.png"
    output="/images/ai-qrcode-101-selective-example1.jpg"
  />
<p><QRCodeCompare scale-85 md:scale-100 h-100
    input="/images/ai-qrcode-101-selective-qr1.png"
    input2="/images/ai-qrcode-101-selective-qr2.png"
    output="/images/ai-qrcode-101-selective-example2.jpg"
  /></p>
<p><QRCodeCompare scale-85 md:scale-100 h-100
    input="/images/ai-qrcode-101-selective-qr1.png"
    input2="/images/ai-qrcode-101-selective-qr2.png"
    output="/images/ai-qrcode-101-selective-example3.jpg"
  /></p>
</div>
<hr>
<div border="~ rounded-full base" px3 py1 inline text-sm float-right>
<span i-ri-book-2-line /> Credits to <a href="https://huggingface.co/monster-labs/control_v1p_sd15_qrcode_monster#tips" target="_blank">QR Code Monster</a>
</div>
<h3>Image to Image Enhancement</h3>
<p>When you find a generated image is hard to scan, you can try to send the image to <code>img2img</code>, enable ControlNet with your original QR Code input and:</p>
<ul>
<li>Decrease the <strong>Denoising strength</strong> to retain more of the original image.</li>
<li>Increase the <strong>Control weight</strong> for better readability.</li>
<li>A typical workflow for &quot;saving&quot; a code would be: Max out the guidance scale and minimize the denoising strength, then bump the strength until the code scans.</li>
</ul>
<p>This tells the model to re-enhance the image by making dark areas darker and light areas lighter under the guidance of ControlNet.</p>
<hr>
<h3>Manually Editing and Inpainting</h3>
<p>The ultimate solution is indeed to manually edit the output image. You can use editing tools like Photoshop combined with inpainting to fine-tune every part of the imaged image. It might require a lot of effort, we'd generally recommend focusing on tweaking the generation first before going to this step. More details can be found in <a href="/posts/ai-qrcode-refine">this post</a>.</p>
<h2>Extra: Hidden Text in Image</h2>
<p>While the QR Code models are primarily designed for generating QR Codes, they are fundamentally brightness-contrast models. This means that they can be used to control and modify anything that exhibits distinct local contrast. So, in addition to generating QR Codes, we can utilize these models to hide text or any symbols inside the generated images. This opens up exciting possibilities for creative exploration beyond just QR Code generation.</p>
<p>For example, we could have this using the exact same methods we learned for generating QR Code:</p>
<p><img src="/images/ai-qrcode-101-text-result1.jpg" alt=""></p>
<details mt--6>
<summary op50 select-none>Input Image</summary>
<p>
<img src="/images/ai-qrcode-101-text-input1.png" rounded shadow important-mt0 />
</p>
</details>
<p>You can click the image to see the full size. When you zoom in on the QR Code image, it can become challenging to distinguish the text from the background. However, when you zoom out significantly, the text becomes much clearer and easier to scan. This observation highlights an interesting aspect of human vision—our eyes are indeed excellent scanners.</p>
<p>Similarly, we could combing with QR Code, or anything you can think of:</p>
<p><img src="/images/ai-qrcode-101-text-result2.png" alt=""></p>
<details mt--6>
<summary op50 select-none>Input Image</summary>
<p>
<img src="/images/ai-qrcode-101-text-input2.png" rounded shadow important-mt0 />
</p>
</details>
<h2>Contributing</h2>
<p>This guide is aimed to be a one-stop documentations and references for the community to learn about the QR Code models and how to use them.</p>
<p>If you are interested in contributing to this post, fixing typos, or adding new ideas, you can <a href="https://github.com/antfu/antfu.me/edit/main/pages/posts/ai-qrcode-101.md">edit this page on GitHub</a>. Or if you are not familiar with Git, you can also go to <a href="https://chat.antfu.me">Anthony's Discord server</a> and discuss with us.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Refining AI Generated QR Code]]></title>
            <link>https://antfu.me/posts/ai-qrcode-refine</link>
            <guid isPermaLink="true">https://antfu.me/posts/ai-qrcode-refine</guid>
            <pubDate>Fri, 30 Jun 2023 17:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<blockquote>
<p><strong>Update</strong>: New blog posts - <a href="/posts/ai-qrcode-101"><strong>Stable Diffusion QR Code 101</strong></a></p>
</blockquote>
<p>Last week, I wrote a <a href="/posts/ai-qrcode">blog post</a> about how I learned to generate scannable QR Codes. When doing so, I consider my goal is to find an image that looks like a QR Code as little as possible to humans, but still be recognizable by the machine.</p>
<p>We need to find a balance, tweaking the weights to try and error. It's still quite hard to find a good composition that represents the black &amp; white spots, while keeping the content meaningful to human. If you go too far, the QR Code will be unscannable, and if you don't go far enough, the image will just be like a boring QR Code.</p>
<p>Since there is quite some randomness in the process, sometimes it could be a pity when you find a good one but realize it's not scannable. To improve this, my workflow was to open up Photoshop, overlay the generated image with the original QR Code, manually check the difference, use the brush to mark those spots and send to <strong>inpaint</strong> to draw those areas. It works to some extent, but pretty inefficient as you need to go back and forth quite a few times. Meanwhile, doing this manually can also be inaccurate as the scanning algorithm might see them differently.</p>
<p><img src="https://antfu.me/images/ai-qrcode-refine-5.jpg" alt="Steps from QR Code to final image"></p>
<p>So, I need to find a way to automate this, helping me to verify and refine the generated QR Code easier. And I came up with a simple web tool to do so. Let me introduce you to a bit about it.</p>
<div i-ri-arrow-right-line /> <a href="https://qrcode.antfu.me/" target="_blank">Anthony's QR Code Toolkit</a>
<h2>Generating the Base QR Code</h2>
<p>One thing I found quite important is that the generated QR Code we put in the ControlNet affects the image quite a lot. The basic square QR Code will lead to a more square-ish and blocky image. It's worth to try with dots, rounded, or other styled QR Codes to see if they can help to generate a better image.</p>
<p><img src="/images/ai-qrcode-refine-input-compare.jpg" alt="Comparison grid between different styled QR Code as input"></p>
<p>The images above are generated with the exactly same parameters, and the same seed, except the QR Code inputs has slightly different on the styles. You can see the difference is quite significant.</p>
<p>In addition, since the distribution of QR Codes is directly affecting the image's composition. Sometimes we might find some patterns might be hard to work around. We would need to find different versions of the QR Code to find a better fit to the image we want. If you are familiar with QR Code enough, you might know there is a step in QR Code generated called <a href="https://en.wikipedia.org/wiki/QR_code#Encoding">Mask Pattern</a>. There are in total 8 different kind of patterns can apply to the QR Code that serves the same content. Sadly, most of the generators do not provide the capability to change it. Ok, I'll build it.</p>
<p>So specifically for this need, I built a QR generator based on <a href="https://www.nayuki.io/page/qr-code-generator-library">QR Code Generator Library</a>:</p>
<p><img src="/images/ai-qrcode-refine-generate-1.png" alt="QR Code Generator"></p>
<p>It offers me the full capability of the generation process. You can change the error correction level, mask pattern, version of the QR Code, and rotation to <strong>find</strong> a good distribution of the black &amp; white spots. Also, it allows you to change the styles of the dots, or add some random noise to the border making the generated image more blended-in.</p>
<p><img src="/images/ai-qrcode-refine-generate-2.png" alt="QR Code Generator with Custom Styles"></p>
<h2>Generating the Images</h2>
<p>Now we have the QR Code, we could move up to generate those images with Stable Diffusion and ControlNet. For detailed steps, please refer to <a href="/posts/ai-qrcode">my previous blog post</a>.</p>
<h2>Verify and Refine the QR Code</h2>
<p>Running overnight, I now got like 200 images generated. Say I find one quite interesting and see some potential of being a good one. I will first use my phone to try to scan it. As mentioned earlier, you may not get lucky every time. This one is unfortunately not scannable.</p>
<p align="center">
<img src="/images/ai-qrcode-refine-4.jpg" class="max-w-120!" alt="Picked one, right from the model" />
</p>
<p>From a glance, we see there are quite some QR Code-ish spots in this image, which should make it recognizable by the scanner. But why not? Let's find out why:</p>
<p>Using the <strong>Compare</strong> tab of the <a href="https://qrcode.antfu.me/">toolkit</a>, upload both the generated image and the original QR Code, tweak the grid size, and then we could see the mismatched spots and inspect the nodes.</p>
<p><img src="/images/ai-qrcode-refine-compare-1.png" alt=""></p>
<p>We can see that the image is not scannable because we have quite a lot of mismatches, saying that some parts of the image might not have enough contrast. Hover on the <strong>Highlight Mismatch</strong> button, we can see the mismatched spots highlighted:</p>
<p><img src="/images/ai-qrcode-refine-compare-2.png" alt=""></p>
<p>It seems the top half part of the image is a bit too dark and makes the scanner hard to distinguish. We can also try to increase the image contrast to see how it would look like in the scanner:</p>
<p><img src="/images/ai-qrcode-refine-compare-3.png" alt=""></p>
<p>Now it's quite clear what's the problem. Then how can we fix it? You can then try to hover on the <strong>Preview Corrected</strong> button, to see what needs to be changed:</p>
<p><img src="/images/ai-qrcode-refine-compare-4.png" alt=""></p>
<p>It will lighten the spots that are too dark, and darken the spots that are too bright. Then you see this image immediately <strong>becomes scannable</strong> now!</p>
<p>It's great but definitely not the final result we would end up with. We can download the correction overlay, or the mask from the toolkit, to use them on <strong>inpaint</strong> or fine-grained adjustment in Photoshop.</p>
<h2>Final</h2>
<p>After a few rounds of inpainting and adjustment, upscale to improve details, and now we have the final image as:</p>
<p align="center">
<img src="/images/ai-qrcode-refine-final.jpg" class="max-w-120!" alt="Final result" />
</p>
<QRNotScannable mt--2 />
<p>Put it back to the toolkit, we see that the mismatched spots are now reduced a lot! Some of the mismatches are actually made on purpose, since QR Code has the error correction capability allowing that.</p>
<p><img src="/images/ai-qrcode-refine-compare-final-1.png" alt="Tge final result in the toolkit"></p>
<p>In case you are interested, here you can see what it looks like when overlaid with the original QR Code:</p>
<p><img src="/images/ai-qrcode-refine-compare-final-2.png" alt="The final result with the original QR Code overlayed on top"></p>
<p>It's quite interesting to see how the QR Code is been distorted and blended as different parts of the image.</p>
<h2>Hide the Markers</h2>
<p>The current result is already surprisingly good to me. The only thing that is missing probably is that the position makers do not blend very well, but I guess that's kinda the limitation. When I was about to call it a day and go to bed, thinking about the possibility of making the QR Code makers less obvious, I saw in <a href="https://classic.qrbtf.com/">classic.qrbtf.com</a> (created by the creator that came up with the AI QR Code idea), there is a style call SP-1 that has a &quot;Plus shape&quot; style of the position markers. It looks much less artificial than the squared or double-circle ones. I didn't know it would also work for the scanner, so I think it might be worth a try.</p>
<p><img src="/images/ai-qrcode-refine-qrbft.png" alt="Styles in classic.qrbtf.com"></p>
<p>So I implemented it in my generator, and it looks like this:</p>
<p><img src="/images/ai-qrcode-refine-plus-sign.png" alt="QR Code generator with plus sign shaped markers"></p>
<p>As you can see, the marker looks much less distinguishable from the other data points (be aware it also make the code less scannable). It might be worth trying as the control net input to see if it can generate better images. But since we already have a pretty good one, let's use the new QR Code to redraw the markers.</p>
<p>So doing the inpainting process again using the new QR Code, and a few more editing, we have the <strong>final result</strong> as:</p>
<p align="center">
<img src="/images/ai-qrcode-refine-no-anchor.png" class="max-w-120!" alt="Final result" />
</p>
<QRNotScannable mt--2 />
<p>Even though I made it step by step, it's still mind-blowing to see the final result looks like this but still scannable! 🤯</p>
<p><a href="https://civitai.com/images/1350374">Check it on Civital</a></p>
<h2>Bonus Tip: Distort the QR</h2>
<p>Since we found the QR Code input affects the output image quite significantly. In another way of thinking, instead of refining the generated image in the post, maybe we can also try to manipulate the QR Code itself before sending it to the model.</p>
<p>For example, we could use the generator to try different patterns and configurations, to generate a better distribution of the data points. Adding some noise in the margin, making the position makers more randomized, and rounding up the hard edges to reduce the blocky feeling. We could have:</p>
<p><img src="/images/ai-qrcode-refine-distort-1.png" alt=""></p>
<p>Then I started to think about what more we could do. So I tried to play filter effects in Photoshop. I found that the <code>Distort &gt; Ripple</code> and <code>Pixelate &gt; Crystallize</code> filters have quite a balanced distortion effect. So I reimplemented the <strong>crystallize</strong> effect in the toolkit, and we have:</p>
<p><img src="/images/ai-qrcode-refine-distort-2.png" alt=""></p>
<p>This further blurs the distinction between data points in human eyes. Sending it to the model, we get surprisingly very good results! Here is one of the examples:</p>
<p align="center">
<img src="/images/ai-qrcode-refine-distort-result.png" class="max-w-120!" alt="Distorted QR Code" />
</p>
<QRNotScannable mt--2 />
<p>Since input has much more soft edges with some shades, it makes the model being able to &quot;guess&quot; with items with more freedom. Hope you'll find this tip useful! I will try to implement more useful effects in the toolkit as we go.</p>
<h2>Conclusion</h2>
<p>I hope you enjoy the walkthrough. If you just started doing AI QR Code, give a try to the tool and let me know if it helps. You can find the app and the source code below.</p>
<div i-ri-qr-code-line /> <a href="https://qrcode.antfu.me/" target="_blank">Anthony's QR Code Toolkit</a><br>
<div i-ri-github-fill /> <a href="https://github.com/antfu/qrcode-toolkit" target="_blank" font-mono>antfu/qrcode-toolkit</a>
<p>Join my <a href="https://chat.antfu.me"><span op75 i-simple-icons-discord /> Discord Server</a>, share what you are working on, and let's explore more together!</p>
<p>If you are interested in how I make such tools, I'd recommend continuing reading <a href="/posts/about-yak-shaving">About Yak Shaving</a> to learn the philosophy I follow when building tools. And if you like my work, consider sponsoring on <a href="https://github.com/sponsors/antfu"><span i-carbon-favorite /> GitHub Sponsor</a> to support me in coming up with more ideas and tools.</p>
<p>Thank you and happy hacking!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Stylistic QR Code with Stable Diffusion]]></title>
            <link>https://antfu.me/posts/ai-qrcode</link>
            <guid isPermaLink="true">https://antfu.me/posts/ai-qrcode</guid>
            <pubDate>Sun, 25 Jun 2023 05:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<blockquote>
<p><strong>Update</strong>: New blog posts</p>
<ul>
<li><a href="/posts/ai-qrcode-refine">👉 <strong>Refining AI Generated QR Code</strong></a></li>
<li><a href="/posts/ai-qrcode-101">📚 <strong>Stable Diffusion QR Code 101</strong></a></li>
</ul>
</blockquote>
<p>Yesterday, I created this image using <a href="https://stability.ai/blog/stable-diffusion-public-release">Stable Diffusion</a> and <a href="https://github.com/lllyasviel/ControlNet">ControlNet</a>, and shared on <a href="https://twitter.com/antfu7/status/1672671149698818048">Twitter</a> and <a href="https://www.instagram.com/p/Ct4fpkgtc1W/">Instagram</a> -- an illustration that also functions as a scannable QR code.</p>
<p><img src="https://antfu.me/images/ai-qrcode-final.jpg" alt=""></p>
<QRNotScannable mt--2 />
<p>The process of creating it was super fun, and I'm quite satisfied with the outcome.</p>
<p>In this post, I would like to share some insights into my learning journey and the approaches I adopted to create this image. Additionally, I want to take this opportunity to credit the remarkable tools and models that made this project possible.</p>
<h2>Get into the Stable Diffusion</h2>
<p>This year has witnessed an explosion of mind-boggling AI technologies, such as <a href="https://chat.openai.com/">ChatGPT</a>, <a href="https://openai.com/dall-e-2">DALL-E</a>, <a href="https://www.midjourney.com/">Midjourney</a>, <a href="https://stability.ai/blog/stable-diffusion-public-release">Stable Diffusion</a>, and many more. As a former photographer also with some interest in design and art, being able to generate images directly from imagination in minutes is undeniably tempting.</p>
<p>So I started by trying Midjourney, it's super easy to use, very expressive, and the quality is actually pretty good. It would honestly be my recommendation for anyone who wants to get started with generative AI art.</p>
<p>On my end, being a programmer with strong preferences, I would naturally seek for greater control over the process. This brought me to the realm of Stable Diffusion. I started with this guide: <a href="https://aituts.com/stable-diffusion-lora/"><em>Stable Diffusion LoRA Models: A Complete Guide</em></a>. The benefit of being late to the party is that there are already a lot of tools and guides ready to use. Setting up the environment quite straightforward and luckily my M1 Max's GPU is supported.</p>
<h2>QR Code Image</h2>
<p>A few weeks ago, <a href="https://www.reddit.com/r/StableDiffusion/comments/141hg9x/controlnet_for_qr_code/"><code>nhciao</code> on reddit posted a series of artistic QR codes</a> created using Stable Diffusion and <a href="https://github.com/lllyasviel/ControlNet">ControlNet</a>. The concept behind them fascinated me, and I defintely want to make one for my own. So I did some research and managed to find the original article in Chinese: <a href="https://mp.weixin.qq.com/s/i4WR5ULH1ZZYl8Watf3EPw">Use AI to Generate Scannable Images</a>. The author provided insights into their motivations and the process of training the model, although they did not release the model itself. On the other hand, they are building a service called <a href="https://qrbtf.com/">QRBTF.AI</a> to generate such QR code, however it is not yet available.</p>
<p>Until another day I found an community model <a href="https://civitai.com/models/90940/controlnet-qr-pattern-qr-codes">QR Pattern Controlnet Model</a> on <a href="https://civitai.com">CivitAI</a>. I know I got to give it a try!</p>
<h2>Setup</h2>
<p>My goal was to generate a QR code image that directs to my website while elements that reflect my interests. I ended up taking a slightly cypherpunk style with a character representing myself :P</p>
<blockquote>
<p><strong>Disclaimer</strong>: I'm certainly far from being an expert in AI or related fields. In this post, I'm simply sharing what I've learned and the process I followed. My understanding may not be entirely accurate, and there are likely optimizations that could simplify the process. If you have any suggestions or comments, please feel free to reach out using the links at the bottom of the page. Thank you!</p>
</blockquote>
<h3>1. Setup Environment</h3>
<p>I pretty much follows <a href="https://aituts.com/stable-diffusion-lora/">Stable Diffusion LoRA Models: A Complete Guide</a> to install the web ui <a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui">AUTOMATIC1111/stable-diffusion-webui</a>, download models you are interested in from <a href="https://civitai.com/models">CivitAI</a>, etc. As a side note, I found that the user experience of the web ui is not super friendly, some of them I guess are a bit architectural issues that might not be easy to improve, but luckily I found a pretty nice theme <a href="https://github.com/canisminor1990/sd-webui-kitchen-theme">canisminor1990/sd-webui-kitchen-theme</a> that improves a bunch of small things.</p>
<p>In order to use ControlNet, you will also need to install the <a href="https://github.com/Mikubill/sd-webui-controlnet">Mikubill/sd-webui-controlnet</a> extension for the web ui.</p>
<p>Then you can download the <a href="https://civitai.com/models/90940/controlnet-qr-pattern-qr-codes">QR Pattern Controlnet Model</a>, putt the two files (<code>.safetensors</code> and <code>.yaml</code>) under <code>stable-diffusion-webui/models/ControlNet</code> folder, and restart the web ui.</p>
<h3>2. Create a QR Code</h3>
<p>There are hundreds of QR Code generators full of adds or paid services, and we certainly don't need those fanciness -- because we are going to make it much more fancier 😝!</p>
<p>So I end up found the <a href="https://www.nayuki.io/page/qr-code-generator-library">QR Code Generator Library</a>, a playground of an open source QR Code generator. It's simple but exactly what I need! It's better to use medium error correction level or above to make it more easy recognizable later. Small tip that you can try with different <strong>Mask pattern</strong> to find a better color destribution that fits your design.</p>
<h3>3. Text to Image</h3>
<p>As the regular Text2Image workflow, we need to provide some prompts for the AI to generate the image from. Here is the prompts I used:</p>
<div>
  <div text-sm op60>Prompts</div>
</div>
<pre><code class="language-txt">(one male engineer), medium curly hair, from side, (mechanics), circuit board, steampunk, machine, studio, table, science fiction, high contrast, high key, cinematic light,
(masterpiece, top quality, best quality, official art, beautiful and aesthetic:1.3), extreme detailed, highest detailed, (ultra-detailed)
</code></pre>
<div>
  <div text-sm op60>Negative Prompts</div>
</div>
<pre><code class="language-txt">(worst quality, low quality:2), overexposure, watermark, text, easynegative, ugly, (blurry:2), bad_prompt,bad-artist, bad hand, ng_deepnegative_v1_75t
</code></pre>
<p><img src="/images/ai-qrcode-t2i.png" alt=""></p>
<p>Then we need to go the ControlNet section, and upload the QR code image we generated earlier. And configure the parameters as suggested in the model homepage.</p>
<p><img src="/images/ai-qrcode-controlnet-config.png" alt=""></p>
<p>Then you can start to generate a few images and see if it met your expectations. You will also need to check if the generated image is scannable, if not, you can tweak the <strong>Start controling step</strong> and <strong>End controling step</strong> to find a good balance between stylization and QRCode-likeness.</p>
<h3>4. I'm feeling lucky!</h3>
<p>After finding a set of parameters that I am happy with, I will increase the <strong>Batch Count</strong> to around 100 and let the model generate variations randomly. Later I can go through them and pick one with the best conposition and details for further refinement. This can take a lot of time, and also a lot of resources from your processors. So I usually start it before going to bed and leave it overnight.</p>
<p>Here are some examples of the generated variations (not all of them are scannable):</p>
<p><img src="/images/ai-qrcode-examples-grid.png" alt="Generation Examples"></p>
<p>From approximately one hundred variations, I ultimately chose the following image as the starting point:</p>
<p><img src="/images/ai-qrcode-original.jpg" alt="Original QR Code Image"></p>
<p>It gets pretty interesting composition, while being less obvious as a QR code. So I decided to proceed with it and add add a bit more details. (You can compare it with the final result to see the changes I made.)</p>
<h3>5. Refining Details</h3>
<blockquote>
<p>Update: I recently built a toolkit to help with this process, check my new blog post <a href="/posts/ai-qrcode-refine">👉 <strong>Refine AI Generated QR Code</strong></a> for more details.</p>
</blockquote>
<p>The generated images from the model are not perfect in every detail. For instance, you may have noticed that the hand and face appear slightly distorted, and the three anchor boxes in the corner are less visually appealing. We can use the <strong>inpaint</strong> feature to tell the model to redraw some parts of the image (it would better if you keep the same or similiar prompts as the original generation).</p>
<p>Inpainting typically requires a similar amount of time as generating a text-to-image, and it involves either luck or patience. Often, I utilize Photoshop to &quot;borrow&quot; some parts from previously generated images and utilize the spot healing brush tool to clean up glitches and artifacts. My Photoshop layers would looks like this:</p>
<img src="/images/ai-qrcode-ps-layers.png" alt="Photoshop Layers" class="w-100! mxa"/>
<p>After making these adjustments, I'll send the combined image back for inpainting again to ensure a more seamless blend. Or to search for some other components that I didn't found in other images.</p>
<p>Specifically on the QR Code, in some cases ControlNet may not have enough prioritize, causing the prompts to take over and result in certain parts of the QR Code not matching. To address this, I would overlay the original QR Code image onto the generated image (as shown in the left image below), identify any mismatches, and use a brush tool to paint those parts with the correct colors (as shown in the right image below).</p>
<p><img src="/images/ai-qrcode-overlay-inpaint.png" alt="Overlaying QR Code"></p>
<p>I then export the marked image for inpainting once again, adjusting the <strong>Denoising strength</strong> to approximately 0.7. This would ensures that the model overrides our marks while still respecting the color to some degree.</p>
<p>Ultimately, I iterate through this process multiple times until I am satisfied with every detail.</p>
<h3>6. Upscaling</h3>
<p>The recommended generation size is 920x920 pixels. However, the model does not always generate highly detailed results at the pixel level. As a result, details like the face and hands can appear blurry when they are too small. To overcome this, we can upscale the image, providing the model with more pixels to work with. The <code>SD Upscaler</code> script in the <code>img2img</code> tab is particularly effective for this purpose. You can refer to the guide <a href="https://easywithai.com/guide/how-to-use-upscalers-in-stable-diffusion/">Upscale Images With Stable Diffusion</a> for more information.</p>
<p><img src="/images/ai-qrcode-upscale.png" alt=""></p>
<h3>7. Post-processing</h3>
<p>Lastly, I use Photship and Lightroom for subtle color grading and post-processing, and we are done!</p>
<p><img src="/images/ai-qrcode-final.jpg" alt="Final QR Code Image"></p>
<p>The one I end up with not very good error tolerance, you might need to try a few times or use a more forgiving scanner to get it scanned :P</p>
<h2>Conclusion</h2>
<p>Creating this image took me a full day, with a total of 10 hours of learning, generating, and refining. The process was incredibly enjoyable for me, and I am thrilled with the end result! I hope this post can offer you some fundamental concepts or inspire you to embark on your own creative journey. There is undoubtedly much more to explore in this field, and I eager to see what's coming next!</p>
<p>Join my <a href="https://chat.antfu.me">Discord Server</a> and let's explore more together!</p>
<p>If you want to learn more about the refining process, go check my new blog post: <a href="/posts/ai-qrcode-refine"><strong>Refining AI Generated QR Code</strong></a>.</p>
<h2>References</h2>
<p>Here are the list of resources for easier reference.</p>
<h3>Concepts</h3>
<ul>
<li><a href="https://stability.ai/blog/stable-diffusion-public-release">Stable Diffusion</a></li>
<li><a href="https://github.com/lllyasviel/ControlNet">ControlNet</a></li>
</ul>
<h3>Tools</h3>
<ul>
<li><a href="https://github.com/antfu/use">Hardwares &amp; Softwares I am using</a>.</li>
<li><a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui">AUTOMATIC1111/stable-diffusion-webui</a> - Web UI for Stable Diffusion
<ul>
<li><a href="https://github.com/canisminor1990/sd-webui-kitchen-theme">canisminor1990/sd-webui-kitchen-theme</a> - Nice UI enhancement</li>
</ul>
</li>
<li><a href="https://github.com/Mikubill/sd-webui-controlnet">Mikubill/sd-webui-controlnet</a> - ControlNet extension for the webui</li>
<li><a href="https://www.nayuki.io/page/qr-code-generator-library">QR Code Generator Library</a> - QR code generator that is ad-free and customisable</li>
<li><a href="https://www.adobe.com/products/photoshop.html">Adobe Photoshop</a> - The tool I used to blend the QR code and the illustration</li>
</ul>
<h3>Models</h3>
<ul>
<li>Control Net Models for QR Code (you can pick one of them)
<ul>
<li><a href="https://civitai.com/models/90940/controlnet-qr-pattern-qr-codes">QR Pattern Controlnet Model</a></li>
<li><a href="https://huggingface.co/monster-labs/control_v1p_sd15_qrcode_monster">Controlnet QR Code Monster</a></li>
<li><a href="https://huggingface.co/ioclab/ioc-controlnet/tree/main/models">IoC Lab Control Net</a></li>
</ul>
</li>
<li>Checkpoint Model (you can use any checkpoints you like)
<ul>
<li><a href="https://civitai.com/models/36520/ghostmix">Ghostmix Checkpoint</a> - A very high quality checkpoint I use. You can use any other checkpoints you like</li>
</ul>
</li>
</ul>
<h3>Tutorials</h3>
<ul>
<li><a href="https://aituts.com/stable-diffusion-lora/">Stable Diffusion LoRA Models: A Complete Guide</a> - The one I used to get started</li>
<li><a href="https://mp.weixin.qq.com/s/i4WR5ULH1ZZYl8Watf3EPw">(Chinese) Use AI to genereate scannable images</a> - Unfortunately the article is in Chinese and I didn't find a English version of it.</li>
<li><a href="https://easywithai.com/guide/how-to-use-upscalers-in-stable-diffusion/">Upscale Images With Stable Diffusion</a> - Enlarge the image while adding more details</li>
</ul>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[DX with Nuxt DevTools]]></title>
            <link>https://antfu.me/posts/nuxt-dx-strasbourg-2023</link>
            <guid isPermaLink="true">https://antfu.me/posts/nuxt-dx-strasbourg-2023</guid>
            <pubDate>Thu, 25 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Development Experience with DevTools - StrasbourgJS 2023]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Slides: <a href="https://antfu.me/talks/2023-05-25">PDF</a> | <a href="https://talks.antfu.me/2023/nuxt-devtools-strasbourg/">SPA</a></p>
<p>Recording: <a href="https://www.youtube.com/watch?v=brXZw4HQBGY">YouTube</a></p>
<p>Made with <Slidev class="inline"/> <a href="https://github.com/slidevjs/slidev"><strong>Slidev</strong></a> - presentation slides for developers.</p>
</blockquote>
<YouTubeEmbed id="brXZw4HQBGY" />
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[How I Manage GitHub Notifications]]></title>
            <link>https://antfu.me/posts/manage-github-notifications-2023</link>
            <guid isPermaLink="true">https://antfu.me/posts/manage-github-notifications-2023</guid>
            <pubDate>Wed, 17 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Manage GitHub Notifications - GitHub Maintainer Summit 2023]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Slides: <a href="https://antfu.me/talks/2023-05-17">PDF</a> | <a href="https://talks.antfu.me/2023/github-notifications/">SPA</a></p>
<p>Recording: <a href="https://youtu.be/gu-0b6KCf80">YouTube</a> | <a href="https://www.bilibili.com/video/BV1gz4y1b7kc/">哔哩哔哩</a></p>
<p>Made with <Slidev class="inline"/> <a href="https://github.com/slidevjs/slidev"><strong>Slidev</strong></a> - presentation slides for developers.</p>
</blockquote>
<YouTubeEmbed id="gu-0b6KCf80" />
<h2>Transcript</h2>
<p>As you see, I am maintaining a range of projects, across different organizations and teams. Some are quite large with a lot of users and contributors, some are smaller, like the upstream libraries. In order to keep them maintained, I figured out a methodology, I'd call it &quot;<strong>Notification-driven Developement</strong>&quot;.</p>
<p>Since there are too many repositories to keep track of, why don't we let notifications to do the lead? At some level, I think how many notifications you have for a project is a good indicator of how active the project is. For example, some underlying libraries that has been silent for a while probably means it's relatively stable and doesn't need much maintenance, or no one is actually using them. Either way, the notifications can show you where the community is focusing on.</p>
<p>Same as many of you, I get a lot of notifications every day.</p>
<p><img src="https://talks.antfu.me/2023/github-notifications/notifications-count.png" alt=""></p>
<p>This could look quite scary. Well don't worries, the number is made up. But we know that if we leave them there, the number will only grow and eventually hit the point that would make you feel overwhelmed. Which is something I believe we all want to avoid.</p>
<p>So, my approach is that - I do <strong>Inbox-Zero</strong>, everyday. Well, at least, I <strong>try</strong> to achieve that everyday.</p>
<h2>Why Inbox-Zero?</h2>
<p>You must be curious what's the benefit of doing Inbox-Zero. Here are some of my thoughts.</p>
<p>The first point is to be responsive. Providing feedback on time is one of the important ways to maintain a healthy community and make contributors feel welcomed. You will see people sending appreciation messages when you reply to them in a timely manner. It feels good, and also encourages them to contribute more.</p>
<p>The second point is to keep your maintenance work in control. If we are able to tackle most of incoming notifications everyday, we don't accumulate too much work that might beyond our capacity.</p>
<p>Then naturally, you will know better of what to focus on since now we have smaller maintenance scope.</p>
<h2>0. Reduce Notifications Created</h2>
<p>Before we start to talk about how to manage notifications, let's first talk about how to reduce the number of notifications created beforehand. Like writing good contribritions guide, have good issue forms and templates, and so on. These could help contributors have more information, and at some level, reduce the number of duplications or low quality issues. I won't dive into details here, but instead I'd like to share a interesting tip that could help you manage those information better.</p>
<p>GitHub supports a magic repository called <code>.github</code>. You can create one under your organization or personal namespace. Put the contributions guides and the templates in that repository, and they will be automatically applied to all the repositories under the organization or your account. This could greatly save your time and effort to maintenance those community health especially when you have a lot of repositories.</p>
<h2>1. Seek for Notifications</h2>
<p>Now let's talk about how I manage the notifications. The first tip is to seek for notifications, and don't let them seek for you. I would highly recommand you to turn off your email notifications or the push notifications on your phone, if you haven't. You don't want to being interrupted by notifications when you are working on something else, or in the middle of sleeping. Instead, I would look for notifications proactively, at the beginning of the day, or when I finished my current task.</p>
<p>You can either use GitHub Notifications Inbox to do so, or you can also give to try on <a href="https://volta.net/"><img src="https://volta.net/logo-dark.svg" alt="Volta Logo" class="h-1.1em! w-1.1em! inline m0! translate-y--2px mr1!" />Volta</a>, project management tool our team is building on top of GitHub. Allowing you to track your notifications and milestones in a more intractive way. It's free for open source, so I would definitely recommand you to give it a try to see if it fits your workflow. Today, I will focus more on using GitHub's native notifications inbox.</p>
<h2>2. Group Notifications</h2>
<p>The second tip I would love to share is to group your notifications by repository, instead of solely on time. When you looking at a list of notifications like this, it's easy to get lost of which repository they are from, or that is happening in which project.</p>
<p>To do so, you will see there is a &quot;Group by repository&quot; button on the top right corner of the notifications inbox. Click it, it will now divide the notifications into different groups, and you can see the repository name on the top of each group.</p>
<p>I found this is a huge time saver, as I can now focus on one repository at a time, and avoid doing context switching too often. You can then filter out the notifications for each repository easier.</p>
<h2>3. What to Focus</h2>
<p>Then the last point to to know what to focus, and set priority. I will try to filter out the noises, dismiss the unrelated notifications, so I can keep the notifications inbox as my temporary todo list.</p>
<p>In practice, I will first dismiss issues and PRs that are closed or merged, when I didn't participant in. In the other words, I trust the decision made by my team. A closed issue usually means the conversion is over, or we would encourage people to create new issues if it's still relevant. This along already saved me a lot of time.</p>
<p>Then I will dismiss a range of notifications immediately as well. For example, the notifications from bots. We might have some deployment bots, or the bots automatically ask for reproductions or missing informations, for those, I will dismiss until here are human interactions.</p>
<p>Then also smaller things like new commits pushed to a working-in-progress PRs, or GitHub Actions been canceled, etc.</p>
<p>Following these rules, I have been manually dismiss them for a long while, until someday I feel it's still a lot of work to do so. So, I wrote a userscript, a piece of JavaScript you injected into GitHub on your local to automate this. It's called <GitHubLink repo="antfu/refined-github-notifications" />. Be aware it's quite hacky and opinionated, but I wish it could be a good starting point for you to build your own automation.</p>
<p><img src="https://talks.antfu.me/2023/github-notifications/notifications-refined.png" alt=""></p>
<p>It does a few things, that I see a huge improvement to my workflow. First, it automatically dismiss the &quot;unrelated&quot; notifications I just mentioned.</p>
<p>Then it colorize the notifications by types. As you see, <code>review request</code> becomes purple, and <code>metion</code> and <code>author</code> becomes green and so on. This would help to determind which issue to check first when you scanning the list.</p>
<p>Then it keep only single instance of the notification tab. I commonly found myself opening multiple tabs of the notifications inbox, and they are usually opened in different time and has different state. It could be quite confusing as you will see the notifications that you might already handled. So this userscript will keep only one instance of the notifications tab, and when you open a new one, it will close the previous one.</p>
<p>And lastly, it automatically refresh the page to keep the notifications always update to date.</p>
<h2>Wrapping Up</h2>
<p>So to wrap it up, there are a few finally points I would love to share. The goal is indeed not the make the Inbox-Zero itself, but just one way I use to keep the maintenance scope managable and avoid to let it grow out of control.</p>
<p>Applying to notifications, I will also do <strong>Reply and forget</strong>. I will usually reply to the issue or PR, and then dismiss the notification and &quot;forget&quot; about it. As we know the new notifications will come up once you get replied. This would help to reduce the frustration of long-distance back-and-forth conversations.</p>
<p>And of course, use the tools to help you focus and manage them.</p>
<p>Finally, the most important point to enjoy what you are doing and keep good work-life balance.</p>
<p>Thank you!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Sliding Enter Animation]]></title>
            <link>https://antfu.me/posts/sliding-enter-animation</link>
            <guid isPermaLink="true">https://antfu.me/posts/sliding-enter-animation</guid>
            <pubDate>Sun, 07 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Adding an elegant sliding enter animation to your blog.]]></description>
            <content:encoded><![CDATA[<script setup lag="ts">
import { useRouter } from 'vue-router/auto'

const router = useRouter()
</script>
<p>As you might notice, I recently added a sliding enter effect to almost all the pages in my blog. And I quite like it. If you missed it, &lt;a @click=&quot;router.go(0)&quot;&gt;refresh the page</a> to see it in action.</p>
<p>This effect is inspired by <a href="https://paco.me/">paco.me</a> - the portfolio of <a href="https://twitter.com/pacocoursey">Paco Coursey</a>, one of my favorite developer-designers.</p>
<p>In this blog post, let's break it down and implement one our own.</p>
<h2>Breakdown</h2>
<p>If you go inspecting the source code of <a href="https://paco.me/">paco.me</a>, you will find that it's implemented with CSS animation, and it's quite concise:</p>
<pre><code class="language-css">@keyframes enter {
  0% {
    opacity: 0;
    transform: translateY(10px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

[data-animate] {
  --stagger: 0;
  --delay: 120ms;
  --start: 0ms;
}

@media (prefers-reduced-motion: no-preference) {
  [data-animate] {
    animation: enter 0.6s both;
    animation-delay: calc(var(--stagger) * var(--delay) + var(--start));
  }
}

[data-animation-controller='false'] [data-animate] {
  animation: none;
}
</code></pre>
<p>And in the HTML usage, we have:</p>
<pre><code class="language-html">&lt;p style=&quot;--stagger: 1&quot; data-animate&gt;Block 1&lt;/p&gt;
&lt;p style=&quot;--stagger: 2&quot; data-animate&gt;Block 2&lt;/p&gt;
</code></pre>
<p>It defines a keyframe animation <code>enter</code> that slides the element up by 10px and fades, creating the gentle &quot;floating&quot; effect. The key point is the <code>animation-delay</code> property - assigning a different delay to each element/block, making them have the effect of enter one by one. Then, some CSS variables are used to make the the delay sequence easier to control.</p>
<h2>Applying to Contents</h2>
<p>Now with it, we should be able to add nice sliding enter animation to our layout and homepage, etc. Even though it can be a bit verbose to add style to each element, it gives you full control of what and when those animations take place.</p>
<p>However, when it comes to content like a Markdown page, it will not be that pleasant to wrap each paragraph with a <code>&lt;p&gt;</code> tag and add the <code>data-animate</code> attribute. So I begin to wonder if there is an easier way to apply to all my posts.</p>
<p>I started messing up with the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Counter_Styles/Using_CSS_counters">CSS Counters</a>, an interesting way allowing you to store some numeric variables inside of CSS.</p>
<p>So it should be something like this, where you can add <code>slide-enter-content</code> class wrapping the generated content of a Markdown page:</p>
<pre><code class="language-css">.slide-enter-content {
  counter-reset: enter-count;
}

.slide-enter-content &gt; p {
  --stagger: 0;
  --delay: 150ms;
  --start: 0ms;
  animation: slide-enter 1s both 1;
  animation-delay: calc(var(--start) + var(--stagger) * var(--delay));
}

.slide-enter-content &gt; p {
  counter-increment: enter-count;
  --stagger: counter(enter-count);
}
</code></pre>
<p>It all seems to make sense, but it actually <strong>doesn't work</strong>. The reason is that the <code>counter()</code> function returns a string instead of a number and currently there is no way to convert it to a number, in which the <code>calc()</code> function will fail to compute. There are some <a href="https://github.com/w3c/csswg-drafts/issues/1026">discussions &amp; proposals</a> about this, but it seems not going to happen very soon.</p>
<p>So as a workaround, which also been posted in the previous link, we can use the <code>nth-child()</code> selector to achieve the same effect, manually:</p>
<!-- eslint-skip -->
<pre><code class="language-css">.slide-enter-content &gt; * {
  --stagger: 0;
  --delay: 150ms;
  --start: 0ms;
  animation: slide-enter 1s both 1;
  animation-delay: calc(var(--start) + var(--stagger) * var(--delay));
}

.slide-enter-content &gt; *:nth-child(1) { --stagger: 1; }
.slide-enter-content &gt; *:nth-child(2) { --stagger: 2; }
.slide-enter-content &gt; *:nth-child(3) { --stagger: 3; }
.slide-enter-content &gt; *:nth-child(4) { --stagger: 4; }
.slide-enter-content &gt; *:nth-child(5) { --stagger: 5; }
.slide-enter-content &gt; *:nth-child(6) { --stagger: 6; }
.slide-enter-content &gt; *:nth-child(7) { --stagger: 7; }
.slide-enter-content &gt; *:nth-child(8) { --stagger: 8; }
.slide-enter-content &gt; *:nth-child(9) { --stagger: 9; }
.slide-enter-content &gt; *:nth-child(10) { --stagger: 10; }
.slide-enter-content &gt; *:nth-child(11) { --stagger: 11; }
.slide-enter-content &gt; *:nth-child(12) { --stagger: 12; }
.slide-enter-content &gt; *:nth-child(13) { --stagger: 13; }
.slide-enter-content &gt; *:nth-child(14) { --stagger: 14; }
.slide-enter-content &gt; *:nth-child(15) { --stagger: 15; }
.slide-enter-content &gt; *:nth-child(16) { --stagger: 16; }
.slide-enter-content &gt; *:nth-child(17) { --stagger: 17; }
.slide-enter-content &gt; *:nth-child(18) { --stagger: 18; }
.slide-enter-content &gt; *:nth-child(19) { --stagger: 19; }
.slide-enter-content &gt; *:nth-child(20) { --stagger: 20; }
</code></pre>
<p>The limitation is clear, that the animation only applies to a finite number of elements. You could add more CSS rules to support more, but in practice, I think 20 elements are already quite enough as we don't usually have that many paragraphs fit in one screen.</p>
<p>So that's it, I have applied this effect to most of the pages as well as the blog posts. Let me know what do you think! And you can find the source code <a href="https://github.com/antfu/antfu.me/blob/b9f54c9421ae94e37d4cd598c20e02c4f3ed8db4/src/styles/main.css#L87">here</a>.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Introducing Nuxt Devtools]]></title>
            <link>https://antfu.me/posts/introducing-nuxt-devtools</link>
            <guid isPermaLink="true">https://antfu.me/posts/introducing-nuxt-devtools</guid>
            <pubDate>Mon, 27 Mar 2023 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><a href="https://nuxt.com/blog/introducing-nuxt-devtools">Go to nuxt.com and read the full announcement</a>.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Break Lines in JS]]></title>
            <link>https://antfu.me/posts/break-lines-in-js</link>
            <guid isPermaLink="true">https://antfu.me/posts/break-lines-in-js</guid>
            <pubDate>Fri, 10 Feb 2023 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>You probably don't need it usually, but in case you want to break lines programmatically in JavaScript, here is my lazy man's solution:</p>
<!-- eslint-skip -->
<pre><code class="language-js">// break lines at space with maximum 25 characters per line
text.split(/(.{0,25})(?:\s|$)/g).filter(Boolean)
</code></pre>
<p>A quick example:</p>
<pre><code class="language-js">const text = 'A quick brown fox jumps over the lazy dog.'
const lines = text.split(/(.{0,16})(?:\s|$)/).filter(Boolean)

console.log(lines)
// ['A quick brown', 'fox jumps over', 'the lazy dog.']
</code></pre>
<p>You probably see a few edge cases already. But come on, it's just one line of code :P</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Development Experience with Nuxt]]></title>
            <link>https://antfu.me/posts/nuxt-dx-2023</link>
            <guid isPermaLink="true">https://antfu.me/posts/nuxt-dx-2023</guid>
            <pubDate>Thu, 09 Feb 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Development Experience with Nuxt - Vue Amsterdam 2023]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Slides: <a href="https://antfu.me/talks/2023-02-09">PDF</a> | <a href="https://talks.antfu.me/2023/nuxt-devtools/">SPA</a></p>
<p>Recording: <a href="https://youtu.be/W6hhU-Ws2Rg">YouTube</a> | <a href="https://www.bilibili.com/video/BV1vx4y1V7VD">Bilibili (中文)</a></p>
<p>Made with <Slidev class="inline"/> <a href="https://github.com/slidevjs/slidev"><strong>Slidev</strong></a> - presentation slides for developers.</p>
</blockquote>
<YouTubeEmbed id="W6hhU-Ws2Rg" />
<h2>Transcript</h2>
<p>Let's talk about Developer Experience. These years we have heard about Developer Experience more and more often. Frameworks have put a lot of effort into improving Developer Experience, to make our work more efficient and productive, and of course, a better experience. Here I'd like to divide the big concept into different parts and see what we have done to really make a difference from a framework's perspective.</p>
<hr>
<p>The first thing I am going to pick is &quot;Responsiveness&quot;.</p>
<p>In Nuxt 3, we switched our default bundler to Vite, the tool well-known for its instant hot module replacement, or so call HMR. It allows you to see the change from your code to the app in nearly no time, and creates a great workflow and feedback loop.</p>
<p>On the server-side rendering, we use <code>vite-node</code>, the same engine that powers Vitest, to do the HMR on the server-side.</p>
<p>And finally, we introduced Nitro along with Nuxt 3, apart from many awesome features it provides, it also offers hot reload for server APIs on dev time. Remember the time you need to restart your node process every time you change to your backend API? It's no longer the case with Nitro!</p>
<p>Combining all these tools, we are able to make your app reactive for any changes you make, no matter whether it's client code, ssr, or server-side APIs.</p>
<hr>
<p>As a framework, Nuxt offers common practices built-in.</p>
<p>TypeScript and ESM are supported out-of-box, thanks to Vite.</p>
<p>Nuxt also makes it simple to build single-page application, server-side rendering, static site generation, or hybrid them per routes - using the same codebase isomorphically without any explicit setup.</p>
<p>Then we provided the layout system, plugins, route middlewares, etc., to make the app creation easier and your codebase better organized.</p>
<p>On top of that, we also provided a few composable utilities like <code>useState</code> and <code>useAsyncData</code>, and SEO utilities like <code>useHead</code> and <code>useSeoMeta</code> to make states accessible across the server and client sides.</p>
<p>Not to mention we also have one of the best backend integrations. With Nitro, you can deploy your Nuxt app to any hosting service like Vercel, Netlify, Cloudflare, etc., with zero-config!</p>
<p>All of these features are trying to provide the common practice and sensible defaults that you might need, out-of-box. And save you time going down the rabbit hole configuring them.</p>
<hr>
<p>And then to the cool part, we also introduced some conventions.</p>
<p>The first one is file-based routing, which allows you to have a multi-page app by simply creating the Vue component with the same structure in the filesystem.</p>
<p>Similarly, with the power of Nitro, we also have file-based server APIs, where you can create your serverless APIs in the same way as routing.</p>
<p>Then we add components auto-imports, components under the components folder will be directly available in any Vue file with the same name as their file name. And also, they will are code-splitted well.</p>
<p>And in Nuxt 3, we introduced compostables auto-import. It means you no longer need to type <code>import { ref } from 'vue'</code> in every component. APIs from Vue are directly available to you. 3rd party modules could also provide their custom composables to be auto-imported, and the same works for your local composables.</p>
<p>We also introduced client and server-only components. Making it easy, you can directly do it by adding <code>.client</code> and <code>.server</code> at the end of your component filename.</p>
<p>And finally, all those conventions are fully typed. You can even have type autocomplete when doing route navigation or fetching data from the APIs.</p>
<p>Conventions are introduced to greatly reduce the boilerplates you need to write and avoid duplications in your codebase. Which I see have significant benefits to boost your productivity.</p>
<hr>
<p>When it comes to the ecosystem, Nuxt has a large community to build modules around it. Look at these on our site, we have hundreds of high-quality modules for you to pick from, and all of them here are available to Nuxt 3. With modules, getting integrations for features you want is effortless. And they are taking care of the details and best practices for you.</p>
<hr>
<p>So, with so many great features we would have from a framework, there is, unfortunately, one problem - Transparency.</p>
<p>This might be considered a trade-off of having a framework or actually any tools. Every time we build some cool new features, we add a bit of abstraction to the framework. The abstraction is indeed a great thing to hide some implementation complexity from the users, but it sometimes could also create some extra burden for users to understand. And the conventions could sometimes lead to implicitness, where it's not clear where a component is from, or who is using a certain component, etc. And of course, sometimes it can make things hard to debug.</p>
<p>So how can we improve this?</p>
<hr>
<p>To solve the same issue I had in Vite. I made the package called <code>vite-plugin-inspect</code>. It provides a UI for you to inspect the intermediated state of each plugin transformation of Vite. This makes the Vite pipeline transparent, and you can see how your code has been transformed step by step. If there is anything goes wrong, you can spot which plugin is causing that. (Demo a bit)</p>
<p>Since <code>vite-plugin-inspect</code> is for Vite, it can actually work with any framework or tools built on top of Vite, including Nuxt. However, because Vite is framework agnostic, the inspect feature is relatively low-level. It can be helpful in some cases, but it can also be quite limited.</p>
<hr>
<p>So, by having the context of Nuxt, let's take one step forward -</p>
<p><strong>Introducing Nuxt DevTools!</strong></p>
<hr>
<p>Nuxt DevTools is a set of visual tools that help you to know your app better. It will enhance your overall developer experience with Nuxt. Providing more transparency to the conventions. We also wish it to be able to help you monitor the performance and find the bottleneck. It should be interactive and playful, and it would be great if it could be personalized documentation when you need it.</p>
<p>So that's the plan. And it's indeed a big plan to achieve. Today I am going to showcase to you a bit preview of the things we have been working on.</p>
<hr>
<p>Let's go demo time!</p>
<p>So, here is a dev server of Elk, a Mastodon client built with Nuxt. Daniel already gave a great explanation in his talk. With Nuxt DevTools enabled, here we have a small Nuxt icon on the bottom to open up the DevTools. Click it we see it pop up on a panel right inside our app. Just be aware that this is a very early preview, we have quite a lot of features are not yet been implemented and many things might be changed.</p>
<p>Let's get started. First, we will see a quick overview of your app, like which version you are using, and how many pages, components and composables you have. This is a barebone page for now but we will make it better.</p>
<p>So let's quickly go through the features we have.</p>
<p>The first tab we have pages, here you can see your current route of your app, and all the routes available. You can quickly navigate between pages by simply clicking them. You can also use the text box to see how the route is matched. When it's orange, it means you don't have a page for that route. When it's green, you can navigate to them by pressing enter.</p>
<p>Let's go to the next components tabs. Here it lists all the components you have in your app, and either they are from the user components, registered at runtime, from Nuxt, or from 3rd-party libraries. You can search from them, can click to go the source file in your editor.</p>
<p>You can also see the components graph by clicking the button here. You see Elk is indeed a complex project that has a lot of components. You can click one component and filter the graph to see how this component is using the others.</p>
<p>Nuxt DevTools also integrated components inspector, where you can click the arrow button here and goes to your app, to know where an element is from. Click it, it will open the source code to the exact line of that component.</p>
<p>Then we have imports tabs to show all the auto-imported entries you have from different sources. For example, here you can see VueUse offers many functions and we are using some of them. Click into, you will see a short description of what it does, and a link to the documentation page of that specific function, and how many files are referencing it.</p>
<p>Go to the modules tab, you will see the modules you have installed. With their informations and links to the documentation. In the future, we plan to have some nice UI for you to install or even manage your modules with one-click.</p>
<p>Here we have all the plugins executed in order. This page is working in progress.</p>
<p>Then we have runtime configs and payload. Where you can see the data you have from <code>useRuntimeConfig()</code>, or the state you have from <code>useState()</code>, <code>useAsyncData()</code> etc. And they are reactive and editable. You can change the color mode by editing this here.</p>
<p>We have a hooks tab, to show you how Nuxt hooks been executed in both client side and server side, and how much time they cost. This could be helpful to find the bottleneck of your app or even help us to find bugs in Nuxt core.</p>
<p>Then we have virtual files from Nitro, the generated-code to support Nuxt's convention. This is a bit advanced mostly for module authors.</p>
<p>And if you are already using vite-plugin-inspect, here for sure we have it built-in as well!</p>
<p>Alright, so that's the feature we had for Nuxt DevTools right now. We hope you like them.</p>
<hr>
<p>Oh, almost forgot, we have one more thing!</p>
<hr>
<p>Nuxt DevTools is also designed to be flexible and extensible. That means modules can actually contribute to DevTools to present interactive information for their integrations. Here let me show you a few modules that support Nuxt DevTools right now.</p>
<p>The first one is VS Code, let's click the &quot;Start&quot; button first. Thanks to VS Code Server, we are able to embed a fully featured VS Code into the DevTools, where you can sync with your vscode settings as your local, and all the extensions are available. With this, you are now able to edit your file without even leaving your app, for example, let's change the title of Elk (edit NavTitle.vue). You see, it's instant! You can also close the DevTools and open it back at any time.</p>
<p>Let's go to the next one. When you have the VueUse module installed, the module will contribute a new tab to the DevTools, and this shows all functions of VueUse with instant search.</p>
<p>Similarly, we also have UnoCSS inspector, where you can see how each file uses the atomic CSS, and how CSS is generated.</p>
<p>And finally, with the <code>nuxt-vitest</code> module, that Daniel and I have been working on recently, allows you to run your tests alongside your dev server, using the exact same pipeline as your Nuxt app. Whenever you update your file, the test will automatically reruns so you can see the client get updated and the test result at the same time!</p>
<p>This is only something we have right now as a MVP. We see great potential on this and it would be hard to imagine how it would end up being. We would like to invite you to join us for brainstorming and bringing an even better developer experience to Nuxt.</p>
<hr>
<p>And so, the preview of Nuxt DevTools is open-sourced, right now! You can give it a star at <a href="https://github.com/nuxt/devtools" target="_blank"><span i-logos-nuxt-icon /> Nuxt Devtools</a> and find the instructions there for trying it in your Nuxt apps.</p>
<p>That's all for my talk. Thank you!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Bonjour Paris!]]></title>
            <link>https://antfu.me/posts/bonjour-paris</link>
            <guid isPermaLink="true">https://antfu.me/posts/bonjour-paris</guid>
            <pubDate>Sat, 10 Dec 2022 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Bonjour Paris!</p>
<p>I am so excited to share with you that I have <strong>moved to Paris</strong>!</p>
<p>Paris is always my dream city, it's still a bit unreal to see that I am actually here! Here I am going to share with you some of my life updates and what would they mean to me.</p>
<h2>Thanks</h2>
<p>At the very first, I need to give a <strong>HUGE thanks</strong> to my company <a href="https://nuxtlabs.com/"><strong>NuxtLabs</strong></a> and my colleagues. They made all this possible and helped us a lot along the way.</p>
<p>And also thanks to every one of you. It's your support that helps me find my work valuable and continue working on them. You couldn't imagine my mom and my friends' reactions when I first told them my journey of getting a career by typing on the keyboard at home - the magic of Open Source and the communities! More than that, I got to know so many of you - my friends, colleagues, teams, contributors, followers or fans. All of these really changed our life, thank you!</p>
<h2>Life</h2>
<p>Before coming to France, I was a digital nomad for 1.5 years in China. It was a really special experience for me, that I also been through quite a lot of ups and downs. It's fun and fresh to wake up in different places every day, but it also comes with a lot of challenges and trade-offs. You got to get familiar with the new environment every day, and meet new friends but also need to say goodbye very soon.</p>
<p>This time I am going to stay in Paris for a long while. Learning the language, exploring the city, meeting new people and getting a cozy home. It's a new chapter for me, I am so excited about it!</p>
<h2>Photography</h2>
<p>I was quite passionate about photography before I started working on Open Source. Since my focus shifted to programming, I took fewer photos, which is the thing I always feel a bit pity. I think this could be a good chance for me to get back to it.</p>
<figure>
  <img src="https://antfu.me/images/a-paris-2.jpg" alt="Le métro" />
  <figcaption>Le métro</figcaption>
</figure>
<p>Instead of carrying a heavy DSLR with lenses, I got a Ricoh GR3x that can easily fit in my pocket. Using it for a few weeks, I found myself becoming more willing to carry it and take photos every day.</p>
<h2>Travel</h2>
<p>So happy the pandemic is close to the end, and I can finally join you in conferences and meetups!</p>
<p>Oh by the way, <strong>I am going to <a href="https://vuejs.amsterdam/">Vue Amsterdam on February 9-10</a> in person</strong>! It would be my first-ever in-person speaking. I am so excited to meet you there!</p>
<p>Once I am settled down, I plan to travel around Europe and visit some of my friends. Let's have a cup of coffee if you are around!</p>
<figure>
  <img src="/images/a-paris-3.jpg" alt="Saint-Antoine des Quinze-Vingts" />
  <figcaption>Saint-Antoine des Quinze-Vingts</figcaption>
</figure>
<figure>
  <img src="/images/a-paris-4.jpg" alt="Rue de Charenton" />
  <figcaption>Rue de Charenton</figcaption>
</figure>
<p>I have been in Paris for only a week, and I am already in love with it! I am so excited about what is coming next to share with you!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Patterns of VueUse]]></title>
            <link>https://antfu.me/posts/patterns-of-vueuse-vuefes-japan-2022</link>
            <guid isPermaLink="true">https://antfu.me/posts/patterns-of-vueuse-vuefes-japan-2022</guid>
            <pubDate>Sun, 16 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Patterns of VueUse - Vue Fes Japan 2022]]></description>
            <content:encoded><![CDATA[<blockquote>
<p><a href="https://vuefes.jp/2022/"><strong>Vue Fes Japan 2022</strong></a></p>
<p>Slides: <a href="https://antfu.me/talks/2022-10-16">PDF</a> | <a href="https://talks.antfu.me/2022/patterns-vueuse/">SPA</a></p>
<p>Made with <Slidev class="inline"/> <a href="https://github.com/slidevjs/slidev"><strong>Slidev</strong></a> - presentation slides for developers.</p>
</blockquote>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Vite, the on-demand DX]]></title>
            <link>https://antfu.me/posts/vite-on-demand-dx-viteconf-2022</link>
            <guid isPermaLink="true">https://antfu.me/posts/vite-on-demand-dx-viteconf-2022</guid>
            <pubDate>Tue, 11 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Vite, the on-demand DX - ViteConf 2022]]></description>
            <content:encoded><![CDATA[<blockquote>
<p><a href="https://viteconf.org/"><strong>ViteConf</strong></a></p>
<p>Slides: <a href="https://antfu.me/talks/2022-10-11">PDF</a> | <a href="https://talks.antfu.me/2022/vite-on-demand-dx/">SPA</a></p>
<p>Recording: <a href="https://youtu.be/qXZzXvyqPpc">YouTube</a></p>
<p>Made with <Slidev class="inline"/> <a href="https://github.com/slidevjs/slidev"><strong>Slidev</strong></a> - presentation slides for developers.</p>
</blockquote>
<YouTubeEmbed id="qXZzXvyqPpc" />
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Dev SSR on Nuxt with Vite]]></title>
            <link>https://antfu.me/posts/dev-ssr-on-nuxt</link>
            <guid isPermaLink="true">https://antfu.me/posts/dev-ssr-on-nuxt</guid>
            <pubDate>Tue, 04 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[The journey of how we made development time SSR working on Nuxt with Vite]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<p>In <a href="https://github.com/nuxt/framework">Nuxt 3</a>, <a href="https://v3.nuxtjs.org/getting-started/introduction#why-nuxt">we introduced first-class support for Vite</a> as bundler, interchangeable with Webpack (also available in <a href="https://v3.nuxtjs.org/getting-started/bridge">Nuxt 2 with Bridge</a>). Since Vite provides an incredibly fast developer experience, we had to make sure SSR works as fast.</p>
<p>Since <a href="https://github.com/nuxt/framework/releases/tag/v3.0.0-rc.9">Nuxt 3 RC.9</a>, we shipped a new development-time SSR approach that is as fast as Vite's HMR with on-demand capability. Here is writeup on how we iterated to make it possible.</p>
<h2>SSR</h2>
<p>In case you are not familiar with SSR already, let me do a brief introduction.</p>
<p>Server-side rendering (SSR) is a popular tech to improve SEO and initial page rendering speed. When using frameworks like Vue, the components are written purely in JavaScript. This means your distribution app's <code>index.html</code> would look like this:</p>
<pre><code class="language-html">&lt;html&gt;
  &lt;body&gt;
    &lt;div id=&quot;app&quot;&gt;
      &lt;!-- Empty. And will be rendered by JavaScript in the client browser --&gt;
    &lt;/div&gt;
    &lt;script src=&quot;/assets/app.js&quot;&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>When a user visits your website, the browser fetches and evaluates the JavaScript to let the framework render the content of your app (Client-side Rendering, CSR). Compared to the old age where all the content were directly written in HTML and made available directly when visiting, the modern way to build websites make the time between users hitting the Enter and seeing the content much longer (we call it <a href="https://web.dev/first-meaningful-paint/">First Meaningful Paint (FMP)</a> or <a href="https://web.dev/lcp/">LCP</a> in performance measurement).</p>
<p>SSR is introduced to solve this. By rendering the app on the server side before the page is served, the content can be delivered directly as HTML. With SSR, the example above will be served as:</p>
<pre><code class="language-html">&lt;html&gt;
  &lt;head&gt;
    &lt;!-- Manifests injected by SSR --&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;/assets/app.css&quot; /&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id=&quot;app&quot;&gt;
      &lt;!-- SSR --&gt;
      &lt;h1&gt;Your title&lt;/h1&gt;
      &lt;p&gt;These contents are server-rendered and shipped with the HTML&lt;/p&gt;
    &lt;/div&gt;
    &lt;script src=&quot;/assets/app.js&quot; async&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Once the JavaScript has been downloads and evaluated, the client app will <a href="https://blog.somewhatabstract.com/2020/03/16/hydration-and-server-side-rendering/">hydrate</a> the static DOM to provide client-side interactivity.</p>
<p>If you are interested in learning more details about SSR and CSR, I recommend reading <a href="https://developers.google.com/web/updates/2019/02/rendering-on-the-web">Rendering on the Web</a> by <a href="https://twitter.com/_developit">Jason Miller</a> and <a href="https://twitter.com/addyosmani">Addy Osmani</a>.</p>
<h2>SSR in Development</h2>
<p>In general, when talking about SSR, we commonly refer to a web server in production that can render the page into HTML upon every incoming request. While the performance gain from SSR seems to only matter to the end users, it's also vital to have SSR running in development.</p>
<p>SSR improves the performance and UX and allows you to run server-specific logic, like fetching internal states or accessing databases. SSR in development ensures your app works consistently across development and production, helping identify bugs earlier.</p>
<h2>The Challenge</h2>
<p>The main challenge of making dev SSR is that, unlike production, <strong>development code is addressed to be changed quite often</strong>. Whenever you make some changes to your source code, you expect the dev server to grab the changes for the SSR.</p>
<p>In addition, the environments of Node.js and browsers are also different (e.g. you don't have <code>window</code> in Node.js). Libraries might have different builds and logic targeting Node and browsers; frameworks might compile components into different outputs for CSR and SSR. This usually means for client build and SSR build, we need two pipelines for handling the transformation and bundling.</p>
<h3>Approach 1: Rebuild</h3>
<p>To do SSR, we have to render our app on the Node side. However, Node won't understand TypeScript <code>.ts</code> files we used, nor the Vue SFC <code>.vue</code>. It does not apply our custom configurations like alias and plugins either. A straightforward solution is to bundle our code into plain JavaScript for Node to consume. With the programmatic APIs Vite provided, we can do this:</p>
<pre><code class="language-ts">import { build } from 'vite'

async function buildSSREntry() {
  await build({
    ssr: true,
    // config for Vite
  })
}
</code></pre>
<p>Then on the server side, we could import the bundle entry in Node to send the rendered result:</p>
<pre><code class="language-ts">// need to first invalidate the renderer, here we skipped that part
const { default: ssrRenderer } = await import('./dist/entry.mjs')

// render HTML on request
const html = await ssrRenderer(req.url)
</code></pre>
<p>To make it reflects user changes, we need to use a filesystem watcher to trigger the rebuild upon each change.</p>
<pre><code class="language-ts">fsWatcher.on('change', async () =&gt; {
  // rebuild the entire app in SSR on file change
  // in real-world this will be debounced
  await buildSSREntry()
})
</code></pre>
<p>Or instead, we could directly use Vite's Watch mode:</p>
<pre><code class="language-ts">import { build } from 'vite'

function buildSSREntry(watchOptions) {
  build({
    watch: watchOptions,
    // ...
  })
}
</code></pre>
<p>It's powered by Rollup's watch mode, which will only re-transform the changed files to output the bundle more efficiently.</p>
<p>This approach is also how we handle the SSR build in Webpack. However, as you can see, Vite's bundleless approach makes the client-side app almost instant in development. The full bundling of SSR has now become the bottleneck of the developer experience.</p>
<h3>Approach 2: Dev Bundler</h3>
<p>Having the client in dev mode and SSR in the production bundle would inevitably introduce some inconsistencies as they go into different pipelines. To solve it, <a href="https://github.com/nuxt/vite/pull/201">Pooya Parsa came out with a brilliant idea</a> to use the Vite dev server &quot;constructing&quot; the app in SSR:</p>
<pre><code class="language-ts">import { createViteServer } from 'vite'

const server = createViteServer()

// transform module `id` for SSR
const result = await server.transformRequest(id, { ssr: true })
</code></pre>
<p>Vite's dev server provides a method called <code>transformRequest()</code>, which is the same as you requesting a module from the browser but in the programmatic style. The extra <code>{ ssr: true }</code> could tell Vite to transform the ESM import/export syntax so it can work without relying on browsers resolving logic.</p>
<p>For example, the following code will be transformed by Vite:</p>
<pre><code class="language-ts">import { ref } from 'vue'

export function foo() {
  return ref(0)
}
</code></pre>
<p>into</p>
<pre><code class="language-ts">const __vite_ssr_import_0__ = await __vite_ssr_import__('vue')

function foo() {
  return __vite_ssr_import_0__.ref(0)
}
Object.defineProperty(__vite_ssr_exports__, 'foo', { value: foo })
</code></pre>
<p>Oops, that looks quite complex! But no worries, you don't have to understand it. All it does is transform the reserved ESM keywords <code>import</code> / <code>export</code> into function calls. The reason why this is needed is that Vite uses a different module resolution algorithm than Node, it was made for the browser's resolution. Since we can't interop the native ESM's resolving logic directly, transforming them into function calls would allow us to provide our custom resolving logic.</p>
<p>With this API, we can now get the SSR code for each module. Our task now becomes how we could chain them together. In our first <a href="https://github.com/nuxt/vite/pull/201">proof of concept at <code>nuxt/vite</code></a>, we implemented our own dev-bundler using the <code>transformRequest()</code>. Here is a simplified example:</p>
<pre><code class="language-ts">const __modules__ = {
  '/foo': () =&gt; {
    /* output of transformRequest for `/foo` */
    const __vite_ssr_exports__ = {}
    const bar = await __vite_ssr_import__('/bar')
    // ...
    // return exports
    return __vite_ssr_exports__
  },
  '/bar': () =&gt; {
    /* output of transformRequest for `/bar` */
  },
  // ...other modules
}

function __vite_ssr_import__(id) {
  return await __modules__[id]()
}

export default __vite_ssr_import__('/foo')
</code></pre>
<div text-right>
<a href="https://github.com/nuxt/framework/blob/47b5baa362bd9a14e7942503a373aec959875eff/packages/vite/src/dev-bundler.ts#L229-L260" target="_blank" op50 font-serif>production code reference</a>
</div>
<p>We wrap the transformed modules as functions and store them in an object <code>__modules__</code> for indexing. Then we can provide a custom import function <code>__vite_ssr_import__</code> to evaluate the modules we want.</p>
<p>We call this approach <strong>Dev Bundler</strong>. With it, we don't actually bundle things, but concatenate code transformed by Vite's dev server. We are using the same pipeline and internal cache as the client-side modules, making the SSR build more efficient and consistent with the client build. It then became our approach in Nuxt 3, and works great across our beta period.</p>
<p>But indeed, there are some things we could optimize.</p>
<p>First is that the approach is not really &quot;on-demand&quot;. We are transforming all the modules in your app regardless of if it has been used for certain requests. And that makes this approach less &quot;Vite&quot;.</p>
<p>Second is the source map support. Since all the modules have been concatenated into a single file and lost the source map, whenever you get an error, the stack trace will show you the error happens in the bundled file instead of the real source. This will hurt the DX as it might make it hard to locate the actual error.</p>
<h3>Approach 3: Vite Node</h3>
<p>So to address the drawbacks of the Dev Bundler, we need to have the &quot;on-demand&quot; philosophy in mind. Instead of transforming all the modules and then evaluating them, we'd better do it upon the module that has been requested. Then we could use the <code>vm</code> module from Node to this:</p>
<pre><code class="language-ts">import vm from 'node:vm'
import { createDevServer } from 'vite'

const server = createDevServer()

async function importModule(id) {
  // get the transform code on import
  // could be a request if the server is in another thread
  const result = await server.transformRequest(id, { ssr: true })

  // to provide vite ssr utils
  const wrappedCode = `(async function (__vite_ssr_exports__, __vite_ssr_import__) {
   ${result.code}
  })`

  // execute the code to get the function
  const wrappedFn = vm.runInNewContext(wrappedCode, {
    // with the file name we could have better stacktrace
    filename,
  })

  // passing the ssr utils (in wrappedCode)
  return wrappedFn({}, importModule)
}

export default await importModule('/foo')
</code></pre>
<div text-right>
<a href="https://github.com/vitest-dev/vitest/blob/40862a2f88b8a0f6aaabe6e490538a85c8993adb/packages/vite-node/src/client.ts#L300-L331" target="_blank" op50 font-serif>production code reference</a>
</div>
<p>Using the <a href="https://nodejs.org/api/vm.html">Node <code>vm</code></a> allows us to execute modules in a safer and isolated context. With inline sourcemap and the filename argument to the <code>runInNewContext</code>, it makes the stacktrace directly point to the correct location of the source file. And most importantly, moving the transform request inside the importing function makes it fully on-demand (caching and sourcemap are simplified in the example).</p>
<p>We ended up extracting this logic into a more general package called <a href="https://github.com/vitest-dev/vitest/tree/main/packages/vite-node#readme"><code>vite-node</code></a>, so it could also be used outside Nuxt.</p>
<p>Not only does <code>vite-node</code> make it on-demand by only requesting modules it needs, but it also makes it possible to control the module cache to provide hot module replacement. We implemented the HMR logic in <code>vite-node</code>, similiar to how Vite handles it on the client side -- making the SSR reloads surprisingly fast.</p>
<figure>
<img src="https://antfu.me/images/nuxt-vite-node-hmr.png" rounded-lg>
<figcaption>Nuxt Dev SSR with HMR</figcaption>
</figure>
<p>From a rough testing with a real-world Nuxt 3 app with 15 routes and 7 modules, we see the SSR reload time by changing a single Vue file goes <strong>from ~200ms to &lt;0.01ms with <code>vite-node</code></strong>. Since it switched a full build to module level on-demand HMR, the difference will be more significant as the project grows.</p>
<h2>Opening Up Possibility</h2>
<p>The work on making <code>vite-node</code> not only benefits the Nuxt's dev SSR but also opened up a lot of possibilities for the tooling around Vite. Since <code>vite-node</code> uses Vite for transformaing the modules, it inherits the great power of Vite. Like out-of-box TypeScript and JSX, the powerful plugin API and ecosystem - all that would just work on <code>vite-node</code> same as your client code.</p>
<p>For example, <a href="https://vitest.dev/">Vitest</a> was only made possible because of the <code>vite-node</code>. It provides the infrastructure to run the tests on Node efficiently with the shared pipeline as your client app consistently. It also made possible to have the HMR support for the tests, making the test-driven development experience much better.</p>
<p>In addition, <code>vite-node</code> powers <a href="https://histoire.dev/">Histoire</a>, a interactive component playgrounds for Vite. <a href="https://github.com/vue-terminal/vue-termui"><code>vue-termui</code></a>, a terminal UI framework for Vue, is using <code>vite-node</code> to do developement HMR.</p>
<div flex justify-evenly gap-3 items-center>
  <a
    href="https://github.com/nuxt/framework"
    title="Nuxt3"
    target="_blank"
    class="border-0! hover:scale-105 transition-all duration-500"
  >
    <img src="/images/logo-nuxt3.svg" class="w-15!" />
  </a>
  <a
    href="https://github.com/vitest-dev/vitest/tree/main/packages/vite-node#readme"
    title="Vite Node"
    target="_blank"
    class="border-0! hover:scale-105 transition-all duration-500"
  >
    <img src="/images/logo-vite-node.svg" class="w-15!" />
  </a>
  <a
    href="https://github.com/vitest-dev/vitest"
    title="Vitest"
    target="_blank"
    class="border-0! hover:scale-105 transition-all duration-500"
  >
    <img src="/images/logo-vitest.svg" class="w-15!" />
  </a>
  <a
    href="https://histoire.dev/"
    title="Histoire"
    target="_blank"
    class="border-0! hover:scale-105 transition-all duration-500"
  >
    <img src="/images/logo-histoire.svg" class="w-15!" />
  </a>
  <a
    href="https://github.com/vue-terminal/vue-termui"
    title="Vue Termui"
    target="_blank"
    class="border-0! hover:scale-105 transition-all duration-500"
  >
    <img src="/images/logo-termui.svg" class="w-15!" />
  </a>
  <div i-ri-question-line w-15 h-15 title="Yours?" op50 />
</div>
<p>We are happy to see our work on Nuxt inspires and pushes the Vite ecosystem for more innovations and better tools. We are also eager to see what is comming next for the tools and integrations that built with &quot;on-demand&quot; philosophy in mind, providing better performance and developer experience.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Why I don't use Prettier]]></title>
            <link>https://antfu.me/posts/why-not-prettier</link>
            <guid isPermaLink="true">https://antfu.me/posts/why-not-prettier</guid>
            <pubDate>Sat, 01 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[The reason why I don't use Prettier in my projects.]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<blockquote>
<p><a href="/posts/why-not-prettier-zh">中文 Chinese Version</a></p>
</blockquote>
<p>I have started writing this post multiple times but never ended up posting it. I wasn't able to figure out a proper way to express my position about Prettier. But this time, I think I should try harder to explain that for future reference.</p>
<p>First of all, I am not against Prettier. Actually, <strong>I love it</strong>.</p>
<h2>I Love Prettier</h2>
<p><a href="https://prettier.io/">Prettier</a> is a great tool, and it saved me a lot of time. I appreciated the efforts of the maintainers and contributors to make it possible and formed a great foundation of how the clean code would look for the community. I have to confess that the title is a bit clickbait. I use Prettier a lot for interactive documentation and playgrounds, like <a href="https://github.com/vueuse/vueuse/blob/c7dd1a48471d0a8b4f2b5a567baa12c24504eaee/scripts/utils.ts#L36-L46">VueUse</a> and <a href="https://github.com/unocss/unocss/blob/7c332f235aff2045addb60c2668331a3ccfd1359/packages/inspector/client/composables/usePrettify.ts">UnoCSS</a>. I love it for the out-of-box support of a lot of languages. I could make the generated code pretty with less than 5 mins of integrating Prettier.</p>
<h2>But, Why Not?</h2>
<p>If you have ever come across my open source projects, you might find that I rarely use Prettier to format the source code. In this post, I would try to explain the reason why I made this decision:</p>
<h3>It's Opinionated</h3>
<p>Prettier describes itself to be <a href="https://github.com/prettier/prettier">&quot;an opinionated code formatter&quot;</a>.</p>
<p><strong>Opinionated</strong> essentially means it's not for everyone. Prettier makes a lot of hard-coded decisions to provide a minimal configuration interface. That makes it very easy to use (it's excellent!) and the code consistent across projects. However, on the other hand, it also means you are losing the ability to have fine-grained tweaks to how your code should look like.</p>
<p>While I love most of Prettier's decisions, it sometimes makes you upset when you find something you don't want and don't have a workaround.</p>
<h3>The Line Wrapping Noise</h3>
<p>The main thing that itches me a lot is the auto wrapping / unwrapping based on the length of the code. Prettier has the concept of <a href="https://prettier.io/docs/en/options.html#print-width"><code>printWidth</code></a>, which constrains each line to fit with a certain width (by default, it's 80 characters). It's generally great to have the code fitting in one screen and avoid horizontal scrolls.</p>
<p>However, I often found it hurting the readability and git diffing.</p>
<p><a href="https://twitter.com/patak_dev">@patak_dev</a> recently brought up an example of that in PR reviewing:</p>
<Tweet>
<p lang="en" dir="ltr">Formatters are awesome, especially when doing PR reviews. They also introduce issues though, for example when an addition triggers a line break. The diff isn&#39;t showing what changed here. It would be great if diff viewers would be Prettier-aware, counting line breaks as spacing. <a href="https://t.co/ZuApmctllU">pic.twitter.com/ZuApmctllU</a></p>&mdash; patak (@patak_dev) <a href="https://twitter.com/patak_dev/status/1575784199767859200?ref_src=twsrc%5Etfw">September 30, 2022</a>
</Tweet>
<p>Sometimes when you modify a string literal in JavaScript that may make the line a bit longer than the value of <code>printwidth</code>, Prettier will force wrapping the line. It breaks the inline diffing and make the changes hard to review. Imagine in another pull request, we might reduce the string a bit shorter, Prettier will then unwrap the lines back to one line. Back and forth, it creates a lot of unnecessary noises.</p>
<p>The following image shows another example:</p>
<a href="https://prettier.io/playground/#N4Igxg9gdgLgprEAuc0DOMAEBXNcBOamAvJgNoA6UmmwOe+AkgCZKYCMANPQVAIYBbOGwogAggBsAZgEs4mAMJ98QiTJh9RmAL6cqNOrgIs2AJm5H8-ISJABxGf0wAlCGgAWfKFt37aPJlZMAGYLBmthTFEAZXdsAHNMADk+ACNsHz1qf0sTTAAWMN5BSNFnPncBL0wAMXw+Bky-QwY8gFYiqxLbABU3d3kAGQBPbFSEJuyW4yCANk6I22iCeJkIZJkJCCllSYBdAG4qEE4QCAAHGDWoNGRQZXwIAHcABWUEW5Q+CSe+YdvTql6mAANZwGDREqDRxwZA7CR4QHAsEQ858MCOeLIGD4bBwU5wATjZjMODMQZeeLYPjxOA1CAqPgwK5QLFfbAwCAnEDuGACCQAdXc6jgaDRYDgyxu6hkADd1MNkOA0ACQI4GDAXvV4lU4d9ESAAFZoAAe0UxEjgAEVsBB4HqEfiQGjCAQlak0nAJNzzvhHDABTJmDB3Mh8uZnY88AL6uclb7RQRZbDTgBHW3wLUXT4gBoAWigcDJZO5+Dg6ZkZa1NN1SHhBrwAhk2NxTrQFutGdhdf1To0qUDwdDSAjOL4m0xCggAlrIFFbW5Rh6aU+9adsrxjCgpNg0TAfsuYm30Rgw0tDrw2m0QA" target="_blank">
<img src="https://antfu.me/images/prettier-print-width.png" scale-110 block m="b--5!" />
</a>
<p><sup><em>The 42 of <code>printWidth</code> is made up for demonstration, but it happens in any <code>printWidth</code></em></sup></p>
<p>On the left is the input code and on the right is the output of Prettier. I don't need to point out but you probably already have the answer of which one is &quot;more pretty&quot;. From my point of view, Prettier follows the rule too strict. And in fact it makes the code much harder to read and modify, violating the initial goal of formatting - to make the code more readable.</p>
<p>The real pain point is that this behavior is not optional. <strong>You can't disable it completely</strong>(<a href="https://github.com/prettier/prettier/issues/3468">#3468</a>). Increasing the <code>printWidth</code> only delays the circumstance and will affect other files that you didn't touch. The only workaround you can do is to use <code>// prettier-ignore</code>, which to me, the &quot;all or nothing&quot; choice loses the point of using Prettier in the first place.</p>
<h3>Mess with ESLint</h3>
<p>Prettier as a code formatter, only cares about code styles but not the logic. Thus we see it's quite common for projects to use ESLint along with Prettier to also check the logic. If you have ever configured that yourself, you might notice there are some functionality overlaps between them - ESLint can also lint for code styles. The common practice is to use <a href="https://github.com/prettier/eslint-config-prettier"><code>eslint-config-prettier</code></a> to disable those overlaps rules in ESLint (there are also <a href="https://prettier.io/docs/en/integrating-with-linters.html">a few other solutions</a>).</p>
<p>However, the approach creates quite a lot of mess to me:</p>
<Tweet conversation="none">
<p lang="en" dir="ltr">My points here:<br><br>1. Prettier Only is cool - It&#39;s out-of-box.<br>2. If you need to use ESLint, it can do the formatting as good as Prettier - and more configurable</p>&mdash; Anthony Fu (@antfu7) <a href="https://twitter.com/antfu7/status/1279149211523538944?ref_src=twsrc%5Etfw">July 3, 2020</a>
</Tweet>
<Tweet conversation="none">
<p lang="en" dir="ltr">3. Prettier + ESLint still needs a lot of configs - It doesn&#39;t make your life easier.<br>4. You can have full control in ESLint but not in Prettier, mixing them together feels weird.<br>5. I don&#39;t think parsing two times can be faster (maybe I am wrong)</p>&mdash; Anthony Fu (@antfu7) <a href="https://twitter.com/antfu7/status/1279149212974776320?ref_src=twsrc%5Etfw">July 3, 2020</a>
</Tweet>
<p><a href="https://developer.ibm.com/articles/auto-fix-and-format-your-javascript-with-eslint/">ESLint's auto fix</a> could also do the formatting just as well as Prettier - with much more freedom of choices.</p>
<h2>Alternatives</h2>
<p>ESLint is essential to my workflow to ensure the code quality. If ESLint is already capable of doing formatting, the best solution for me is to use it in one go.</p>
<p>I spent some time configuring my ESLint and made it a config preset:</p>
<GitHubLink repo="antfu/eslint-config" name="@antfu/eslint-config" />
<p>It turns out, the setup configuration can also be very minimal:</p>
<pre><code class="language-bash">npm i -D @antfu/eslint-config
</code></pre>
<pre><code class="language-js">// eslint.config.js
import antfu from '@antfu/eslint-config'

export default antfu({
  // customizations
})
</code></pre>
<p>That's all you need. With the IDE extensions, it's also possible to trigger auto fixing on save. It works similarly to Prettier but respects your choices when to break the lines, with many best practices of linting. It's also quite opinionated, but thanks to the <a href="https://eslint.org/docs/latest/use/configure/configuration-files-new">new Flat Config</a>, it gives you <a href="https://github.com/antfu/eslint-config#customization">the full control of customizations</a> over every single bit that you want to change. Moreover, you can always fork it to make your own versions.</p>
<blockquote>
<p>Sidenote: You might hear so voicing saying &quot;Don't use ESLint for formatting&quot; - <a href="https://github.com/eslint/eslint.org/issues/435">here are some discussions and a response from the ESLint team</a>, for you to make your own judgement.</p>
</blockquote>
<h2>Wrapping Up</h2>
<p>This post is only trying to explain my personal experience and opinions. Of course, you can have different views and don't need to agree with me at all. I am not blaming Prettier for this. Different tools have different purposes and focuses, and there is no better or worse. It's just about using the right tools for the right circumstances. I will still be a happy user of Prettier in usages that I don't need the maximum customizability, and using ESLint exclusively for my projects' source code.</p>
<p>Hope this could make myself clear and maybe give you some insights. Cheers!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Why Reproductions are Required]]></title>
            <link>https://antfu.me/posts/why-reproductions-are-required</link>
            <guid isPermaLink="true">https://antfu.me/posts/why-reproductions-are-required</guid>
            <pubDate>Mon, 30 May 2022 16:00:00 GMT</pubDate>
            <description><![CDATA[My thoughts on why reproductions are important in open source.]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<blockquote>
<p><a href="/posts/why-reproductions-are-required-zh">中文 Chinese Version</a></p>
</blockquote>
<p>If you have ever browsed the issue lists in my repos or created one, you might sometimes see I reply with the following comment and then close the issue:</p>
<figure>
<img src="https://antfu.me/images/issue-close-without-repro-light.png" img-light rounded-lg>
<img src="/images/issue-close-without-repro-dark.png" img-dark rounded-lg>
<figcaption>We temporarily close this due to the lack of enough information. Please provide a <a href="https://stackoverflow.com/help/minimal-reproducible-example" target="_blank">minimal reproduction</a> to reopen the issue. Thanks.</figcaption>
</figure>
<p>I'd first say sorry if it ever makes you feel unpleasant. In this post, let me try to explain the reason behind it.</p>
<h2>Open Source Software is served &quot;as-is&quot;</h2>
<p>First of all, let's reach a consensus about Open Source Software(OSS). If you look into the MIT License, one of the most popular licenses, you will see it contains the following statement:</p>
<blockquote>
<p>The software is provided &quot;AS IS&quot;, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement.</p>
</blockquote>
<p>&quot;As is&quot; stands that you are free to use the code, fork it, or modify it as you want with the <strong>current state</strong>. Meaning the authors are not responsible to fix or improve anything you might have now or in the future. Since it's free and open source, the authors or maintainers gain nothing regardless if you use it or not. There is no such thing as 24-7 customer service. Theoretically, you as a user of open source code, should be responsible to the code you use.</p>
<p>Yeah, that sounds scary. But luckily, things are not that bad in real practice. In many open source projects, we build trust between users and maintainers. Users contribute to projects by reporting issues they faced, or sharing solutions via discussions and pull requests. Maintainers spend their time reviewing, making decisions, managing releases, and doing distributions. Both users and maintainers work <strong>voluntarily</strong> toward the same wish - to make the software better, for everyone.</p>
<h2>Maintainance takes effort, a lot</h2>
<p>Software once written, is never finished. Maintainance plays a crucial role to keep a project &quot;alive&quot;, getting bug or security fixes on time, and being sustainable in the long run. Things like triaging issues, reviewing PRs, and discussions could take a lot of effort from maintainers. While in open source projects, the ratios of user-to-maintainer are commonly unbalanced. Many popular projects might only have one or two maintainers behind the scene. As a project grows and gains more users, the number of tasks required to maintain the project may easily go beyond one’s capacity.</p>
<figure>
<img src="/images/github-inbox-light.png" img-light rounded-lg>
<img src="/images/github-inbox-dark.png" img-dark rounded-lg>
<figcaption>My inbox of GitHub notifications</figcaption>
</figure>
<h2>Why Reproduction</h2>
<p>A good <a href="https://stackoverflow.com/help/minimal-reproducible-example">minimal reproduction</a> of a potential issue could greatly help maintainers to identify the root cause and land the fix quickly. Without a minimal reproduction, merely from the issue description or a screenshot, maintainers won't even know whether it's a real issue of the project or it's caused by some other factors. To identify that, maintainers might need to spend extra time to find a reproduction themselves, or dive into the giant project people shared as a &quot;non-minimal reproduction&quot;. Hours might be spent, but what if it turns out a non-issue or unreproducible? What if there are hundreds of such issues you need to deal with?</p>
<p>In my opinion, <strong>asking for minimal reproduction is asking for equity of the effort spent</strong>. If everyone could take some time to create a minimal reproduction before opening issues, it would save maintainers hundreds of hours (or even help themselves to find user-land solutions/mistakes, then they don't even need to create the issue). A well-investigated and well-explained issue would also make maintainers more willing to spend their time and effort in return.</p>
<blockquote>
<p>I'd say <strong>&quot;The best reproductions are the reproductions never sent&quot;</strong> - because you've already found the solutions and solved them during the process of trying to reproduce those issues, without need to submit and wait for responses from maintainers. You're effort gets paid off in a much better way.</p>
</blockquote>
<h2>How to Create a Minimal Reproduction</h2>
<h3>Failing Test Cases</h3>
<p>If you are a developer and familiar with the testing process. <strong>The best reproductions are pull requests that add failing test cases</strong>. This approach doesn't just highlight the problem, but also clearly depicts the expected behavior. Leveraging the Continuous Integration (CI) system, it also allows verifying the fix upon landing and provides a safeguard against future regressions.</p>
<p>To proceed with this method, initiate by cloning the project's source code and setting up the development environment. Then, create a new branch and navigate to the test folder to introduce a failing test case that mirrors the discrepancy you've observed. Upon successfully making the new test fail -- indicating the bug -- commit the changes, then establish a pull request detailing the issue in its description.</p>
<p>Here are some real world examples:</p>
<ul>
<li><a href="https://github.com/vuejs/language-tools/pull/2113">vuejs/language-tools #2113</a>
<ol>
<li><a href="https://github.com/vuejs/language-tools/pull/2113/commits/eba91fdc0e35389f495ecb7fe144e301e5ccbd58">PR created by adding failing test</a></li>
<li><a href="https://github.com/vuejs/language-tools/pull/2113/commits/6b712b22b442184ce6a6abe3052db7d5a3cb5ac4">The maintainer later pushed a fix to make the test pass</a></li>
<li>PR gets merged, and the tests get improved to cover more cases</li>
</ol>
</li>
<li><a href="https://github.com/unjs/magicast/pull/62">unjs/magicast #62</a>
<ol>
<li><a href="https://github.com/unjs/magicast/pull/62/commits/7d3bb7c7955ce2eb697014700771e94795682f89">PR created by adding failing test</a></li>
<li><a href="https://github.com/unjs/magicast/pull/62/commits/6a27de93b73861eb0750873105fd8c5d51f8912c">The maintainer later pushed a fix to make the test pass</a></li>
<li>PR gets merged with the improved test case</li>
</ol>
</li>
</ul>
<p>Please note that this method may not always applicable. If the bug is not been able to be reproduced with a test case, you can try to create a repository reproductions instead, as described below.</p>
<h3>Reproducible Projects or Playgrounds</h3>
<blockquote>
<p>This section is ported from <a href="https://gist.github.com/Rich-Harris/88c5fc2ac6dc941b22e7996af05d70ff"><em>Please include a repro</em></a> by <a href="https://github.com/Rich-Harris">Rich Harris</a>. Also recommand watching <a href="https://youtu.be/dB_YjuAMH3o?t=1376">a more detailed explanation by Rich Harris</a>.</p>
</blockquote>
<p>In some cases, there will be a project-specific way to demonstrate problems – for example, <a href="http://rollupjs.org">Rollup</a>, <a href="https://svelte.technology/repl">Svelte</a> and <a href="https://sfc.vuejs.org/">Vue</a> all have dedicated REPLs. Use them!</p>
<p>Often, it's not possible to illustrate the problem with a REPL. Here's what you do:</p>
<ol>
<li>Create a sample repo on GitHub or Stackblitz (or wherever)</li>
<li>Demonstrate the problem, and nothing but the problem. Whittle it down to the <em>bare minimum</em> of code that reliably demonstrates the issue. Get rid of any dependencies that aren't <em>directly</em> related to the problem.</li>
<li>Install all your dependencies to <code>package.json</code>. If the maintainer can't clone the repo and do <code>npm install &amp;&amp; npm run build</code> (or similar – see point 4) to see the problem, because the maintainer needs some globally installed CLI tool or whatever, that would make it harder to get to the bottom of the issue.</li>
<li>Include instructions in the repo, along with a description of the expected and actual behaviour. Obviously the issue should include information about the bug as well, but it's really helpful if <code>README.md</code> includes that information, plus a link back to the issue. If there are any instructions beyond <code>npm install &amp;&amp; npm run build</code>, they should go here.</li>
</ol>
<h2>Wrapping Up</h2>
<p>As a maintainer, I appreciate all the issues and pull requests opened. And I believe it's true that some of the issues we closed without reproduction might still have real bugs that need to be fixed. But to not be overwhelmed by the notifications, maintainers need to set the priorities for handling the tasks. Keeping the number of issues in a manageable manner is one of the ways to keep the project healthy in the long run.</p>
<p>I believe open source is about building great stuff together, not solely on maintainers' shoulders. Wish we could make a better and healthier community together. Thanks for reading :)</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Introduction to Vitest]]></title>
            <link>https://antfu.me/posts/introduction-to-vitest-vue-nation-2022</link>
            <guid isPermaLink="true">https://antfu.me/posts/introduction-to-vitest-vue-nation-2022</guid>
            <pubDate>Wed, 26 Jan 2022 08:00:00 GMT</pubDate>
            <description><![CDATA[Introduction to Vitest - Vue.js Nation 2022]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Slides: <a href="https://antfu.me/talks/2022-01-26">PDF</a> | <a href="https://talks.antfu.me/2022/vue-nation">SPA</a></p>
<p>Recording: <a href="https://www.youtube.com/watch?v=CW9uTys0li0">YouTube</a></p>
<p>Made with <Slidev class="inline"/> <a href="https://github.com/slidevjs/slidev"><strong>Slidev</strong></a> - presentation slides for developers.</p>
</blockquote>
<YouTubeEmbed id="CW9uTys0li0" />
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Ship ESM & CJS in one Package]]></title>
            <link>https://antfu.me/posts/publish-esm-and-cjs</link>
            <guid isPermaLink="true">https://antfu.me/posts/publish-esm-and-cjs</guid>
            <pubDate>Mon, 29 Nov 2021 16:00:00 GMT</pubDate>
            <description><![CDATA[A short tutorial of shipping both ESM and CJS dual formats in a single NPM package.]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<h2>ESM &amp; CJS</h2>
<ul>
<li>ESM - <a href="https://nodejs.org/api/esm.html">ECMAScript modules</a></li>
<li>CJS - <a href="https://nodejs.org/api/modules.html#modules-commonjs-modules">CommonJS</a></li>
</ul>
<p>In the past decade, due to the lack of a standard module system of JavaScript, CommonJS (a.k.a the <code>require('xxx')</code> and <code>module.exports</code> syntax) has been the way how Node.js and NPM packages work. Until 2015, when ECMAScript modules finally show up as the standard solution, the community start migrating to native ESM gradually.</p>
<pre><code class="language-js">// CJS
const circle = require('./circle.js')

console.log(`The area of a circle of radius 4 is ${circle.area(4)}`)
</code></pre>
<pre><code class="language-js">// ESM
import { area } from './circle.mjs'

console.log(`The area of a circle of radius 4 is ${area(4)}`)
</code></pre>
<p>ESM enables named exports, better static analysis, tree-shaking, browsers native support, and most importantly, as a standard, it's basically the future of JavaScript.</p>
<p>Experimental support of native ESM is introduced in Node.js v12, and stabilized in v12.22.0 and v14.17.0. As the end of 2021, many packages now ship in pure-ESM format, or CJS and ESM dual formats; meta-frameworks like <a href="https://github.com/nuxt/framework">Nuxt 3</a> and <a href="https://github.com/sveltejs/kit">SvelteKit</a> are now recommending users to use ESM-first environment.</p>
<p>The overall migration of the ecosystem is still in progress, for most library authors, shipping dual formats is a safer and smoother way to have the goods from both worlds. In the rest of this blog post, I will show you why and how.</p>
<h2>Compatibility</h2>
<p>If ESM is the better and the future, why don't we all move to ESM then? Even though Node.js is smart enough to allow CJS and ESM packages to work together, the main blocker is that <strong>you can't use ESM packages in CJS</strong>.</p>
<p>If you do:</p>
<pre><code class="language-ts">// in CJS
const pkg = require('esm-only-package')
</code></pre>
<p>you will receive the following error</p>
<pre><code class="language-bash">Error [ERR_REQUIRE_ESM]: require() of ES Module esm-only-package not supported.
</code></pre>
<p>The root cause is that ESM is asynchronous by nature, meaning you can't import an async module in synchronous context that <code>require</code> is for. This commonly means <strong>if you want to use ESM packages, you have to use ESM as well</strong>. Only one exception is that you can use ESM package in CJS using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports">dynamic <code>import()</code></a>:</p>
<pre><code class="language-ts">// in CJS
const { default: pkg } = await import('esm-only-package')
</code></pre>
<p>Since dynamic import will return a Promise, meaning all the sub-sequential callee need to be async as well (so call <a href="https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/">Red Functions</a>, or I prefer call it Async Infection). In some case it might work, but generally I won't think this to be an easy approachable solution for users.</p>
<p>On the other hand, if you are able to go with ESM directly, it would be much easier as <code>import</code> supports both ESM and CJS.</p>
<pre><code class="language-ts">import cjs from 'cjs-package'
// in ESM
import { named } from 'esm-package'
</code></pre>
<p>Some packages now ship <a href="https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c">pure-ESM packages</a> advocating the ecosystem to move from CJS to ESM. While this might be the &quot;right thing to do&quot;, however, giving the fact that that majority of the ecosystem are still on CJS and the migration is not that easy, I found myself more lean to ship both CJS and ESM formats to make the transition smoother.</p>
<h3><code>package.json</code></h3>
<p>Luckily, Node allows you to have those two formats in a single package at the same time. With the new <a href="https://nodejs.org/api/packages.html#conditional-exports"><code>exports</code></a> field in <code>package.json</code>, you can now specify multiple entries to provide those formats conditionally. Node will resolve to the version based on user's or downstream packages environment.</p>
<pre><code class="language-json">{
  &quot;name&quot;: &quot;my-cool-package&quot;,
  &quot;exports&quot;: {
    &quot;.&quot;: {
      &quot;require&quot;: &quot;./index.cjs&quot;, // CJS
      &quot;import&quot;: &quot;./index.mjs&quot; // ESM
    }
  }
}
</code></pre>
<h2>Bundling</h2>
<p>So now we have two copies of code with slightly different module syntax to maintain, duplicating them is of course not an ideal solution. At this point you might need to consider introducing some build tools or bundling process to build your code into multiple formats. This might remind you the nightmare of configuring complex Webpack or Rollup, well don't worry, my mission today is to introduce you two awesome tools that make your life so much easier.</p>
<ul>
<li><a href="#tsup"><code>tsup</code></a></li>
<li><a href="#unbuild"><code>unbuild</code></a></li>
</ul>
<h3>tsup</h3>
<p><a href="https://github.com/egoist/tsup"><code>tsup</code></a> by <a href="https://github.com/egoist">@egoist</a> is one of my most used tools. The features zero-config building for TypeScript project. The usage is like:</p>
<pre><code class="language-bash">$ tsup src/index.ts
</code></pre>
<p>And then you will have <code>dist/index.js</code> file ready for you to publish.</p>
<p>To support dual formats, it's just a flag away:</p>
<pre><code class="language-bash">$ tsup src/index.ts --format cjs,esm
</code></pre>
<p>Two files <code>dist/index.js</code> and <code>dist/index.mjs</code> will be generated with it and you are good to go. Powered by <a href="https://github.com/evanw/esbuild"><code>esbuild</code></a>, <code>tsup</code> is not only super easy to use but also incredible fast. I highly recommend to give it a try.</p>
<p>Here is my go-to template of <code>package.json</code> using <code>tsup</code>:</p>
<pre><code class="language-json">{
  &quot;name&quot;: &quot;my-cool-package&quot;,
  &quot;main&quot;: &quot;./dist/index.js&quot;,
  &quot;module&quot;: &quot;./dist/index.mjs&quot;,
  &quot;types&quot;: &quot;./dist/index.d.ts&quot;,
  &quot;exports&quot;: {
    &quot;.&quot;: {
      &quot;require&quot;: &quot;./dist/index.js&quot;,
      &quot;import&quot;: &quot;./dist/index.mjs&quot;,
      &quot;types&quot;: &quot;./dist/index.d.ts&quot;
    }
  },
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;tsup src/index.ts --format cjs,esm --dts --clean&quot;,
    &quot;watch&quot;: &quot;npm run build -- --watch src&quot;,
    &quot;prepublishOnly&quot;: &quot;npm run build&quot;
  }
}
</code></pre>
<h3>unbuild</h3>
<p>If we say <code>tsup</code> is a minimal bundler for TypeScript, <a href="https://github.com/unjs/unbuild"><code>unbuild</code></a> by the <a href="https://github.com/unjs">@unjs org</a> is a more generalized, customizable and yet powerful. <code>unbuild</code> is being used to bundle Nuxt 3 and it's sub packages.</p>
<p>To use it, we create <code>build.config.ts</code> file in the root</p>
<pre><code class="language-ts">// build.config.ts
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
  entries: [
    './src/index'
  ],
  declaration: true, // generate .d.ts files
})
</code></pre>
<p>and run the <code>unbuild</code> command:</p>
<pre><code class="language-bash">$ unbuild
</code></pre>
<p><code>unbuild</code> will generate both ESM and CJS for you by default!</p>
<h4>Stubbing</h4>
<p>This is one of the most incredible things that I have found when I first looked into <a href="https://github.com/nuxt/framework">Nuxt 3's codebase</a>. <code>unbuild</code> introduced a new idea called Stubbing. Instead of firing up a watcher to re-trigger the bundling every time you made changes to the source code, the stubbing in <code>unbuild</code> (so call Passive watcher) does not require you are have another process for that at all. By calling the following command <strong>only once</strong>:</p>
<pre><code class="language-bash">$ unbuild --stub
</code></pre>
<p>You are able to play and test out with your library with the up-to-date code!</p>
<p>Want to know the magic? After running the stubbing command, you can check out the generated distribution files:</p>
<pre><code class="language-ts">// dist/index.mjs
import jiti from 'jiti'

export default jiti(null, { interopDefault: true })('/Users/antfu/unbuild-test/src/index')
</code></pre>
<pre><code class="language-ts">// dist/index.cjs
module.exports = require('jiti')(null, { interopDefault: true })('/Users/antfu/unbuild-test/src/index')
</code></pre>
<p>Instead of the distribution of your code bundle, the dist files are now redirecting to your source code with a wrap of <a href="https://github.com/unjs/jiti"><code>jiti</code></a> - another treasure hidden in the <a href="https://github.com/unjs">@unjs</a> org. <code>jiti</code> provides the runtime support of TypeScript, ESM for Node by transpiling the modules on the fly. Since it directly goes to your source files, there won't be a misalignment between your source code and bundle dist - thus there is no watcher process needed! This is a huge DX bump for library authors, if you still not getting it, you shall definitely grab it down and play with it yourself.</p>
<h4>Bundleless Build</h4>
<p>Powered by <a href="https://github.com/unjs/mkdist"><code>mkdist</code></a> - another <a href="https://github.com/unjs">@unjs</a> package - <code>unbuild</code> also handles static assets and file-to-file transpiling. Bundleless build allows you to keep the structure of your source code, made easy for importing submodules on-demand to optimizing performance and more.</p>
<p>Config in <code>unbuild</code> will look like:</p>
<pre><code class="language-ts">// build.config.ts
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
  entries: [
    // bundling
    'src/index',
    // bundleless, or just copy assets
    { input: 'src/components/', outDir: 'dist/components' },
  ],
  declaration: true,
})
</code></pre>
<p>One of the coolest features on this is that it handles <code>.vue</code> file out-of-box. For example, if I have a component <code>MyComponent.vue</code> under <code>src/components</code> with following content:</p>
<pre><code class="language-html">&lt;!-- src/components/MyComponent.vue --&gt;
&lt;template&gt;
  &lt;div&gt;{{ count }}&lt;/div&gt;
&lt;/template&gt;

&lt;script lang=&quot;ts&quot;&gt;
  const count: number | string = 0

  export default {
    data: () =&gt; ({ count }),
  }
&lt;/script&gt;
</code></pre>
<p>Notice that we are using TypeScript in the Vue file, when we do the build, the component will be copied over but with the TypeScript annotation removed along with a <code>MyComponent.vue.d.ts</code> generated.</p>
<pre><code class="language-html">&lt;!-- dist/components/MyComponent.vue --&gt;
&lt;template&gt;
  &lt;div&gt;{{ count }}&lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
  const count = 0
  export default {
    data: () =&gt; ({ count }),
  }
&lt;/script&gt;
</code></pre>
<pre><code class="language-ts">// dist/components/MyComponent.vue.d.ts
declare const _default: {
  data: () =&gt; {
    count: number | string
  }
}
export default _default
</code></pre>
<p>This way this allows you to use TypeScript in development while not requiring consumers to also have TypeScript in their setup.</p>
<p>P.S. <code>unbuild</code> is working on providing better out-of-box experience by auto infering the entries in <code>package.json</code>, <a href="https://github.com/unjs/unbuild/issues/3">learn more</a>.</p>
<h2>Context Misalignment</h2>
<p>With either of the tools mentioned above, now we are able to write TypeScript as the single source of truth and made the overall codebase easier to maintain. However, there are still some caveats that you will need to keep an eye on it.</p>
<p><strong>In ESM, there is NO <code>__dirname</code>, <code>__filename</code>, <code>require</code>, <code>require.resolve</code></strong>. Instead, you will need to use <code>import.meta.url</code> and also do some convertion to get the file path string.</p>
<p>So since our code will be compiled to both CJS and ESM, it's better to avoiding using those environment specific context whenever possible. If you do need them, you can refer to my note about <a href="/posts/isomorphic-dirname">Isomorphic <code>__dirname</code></a>:</p>
<pre><code class="language-ts">import { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'

const _dirname = typeof __dirname !== 'undefined'
  ? __dirname
  : dirname(fileURLToPath(import.meta.url))
</code></pre>
<p>For <code>require</code> and <code>require.resolve</code>, you can use</p>
<pre><code class="language-ts">import { createRequire } from 'node:module'

const require = createRequire(import.meta.url)
</code></pre>
<p>Some good news, if you are using <code>unbuild</code>, you can turn on the <code>cjsBridge</code> flag and <code>unbuild</code> will shims those CJS context in ESM automatically for you!.</p>
<pre><code class="language-ts">import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
  cjsBridge: true, // &lt;--
})
</code></pre>
<p>On the other hand, if you are using <code>tsup</code>, it will shims ESM's <code>import.meta.url</code> for you in CJS instead.</p>
<h2>Verify your Packages</h2>
<p>Once your published your package, you can verify if it follows the best practices using <a href="https://publint.dev/">publint.dev</a> made by <a href="https://github.com/bluwy">@bluwy</a>. It will also give you suggestions of how to improve them further.</p>
<h2>Final words</h2>
<p>This blog post showcased you only a few features of both tools. Do check their docs for more details. And hope you find these setups useful for building your own libraries. If you have any comments or suggestions, ping me on Twitter <a href="https://twitter.com/antfu7">@antfu7</a>. Happy hacking!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Icons in Pure CSS]]></title>
            <link>https://antfu.me/posts/icons-in-pure-css</link>
            <guid isPermaLink="true">https://antfu.me/posts/icons-in-pure-css</guid>
            <pubDate>Sun, 31 Oct 2021 16:00:00 GMT</pubDate>
            <description><![CDATA[The icon solution in pure CSS.]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<blockquote>
<p><a href="/posts/icons-in-pure-css-zh">中文 Chinese Version</a></p>
</blockquote>
<p>In my previous post about <a href="/posts/reimagine-atomic-css#pure-css-icons">Reimagine Atomic CSS</a>, I introduced a preset of <a href="https://github.com/antfu/unocss">UnoCSS</a> that provides the ability to <strong>use any icons on-demand in purely CSS</strong>. Today in this post, I'd like to share with you how we made it possible.</p>
<h2>My Icon Explorations</h2>
<p>If you are interested in how I get here, there is an index of my previous post about the stories of my icon explorations and experiments.</p>
<ul>
<li>Aug. 2020 - <a href="/posts/journey-with-icons">Journey with Icons</a></li>
<li>Sep. 2021 - <a href="/posts/journey-with-icons-continues">Journey with Icons Continues</a></li>
<li>Oct. 2021 - <a href="/posts/reimagine-atomic-css#pure-css-icons">Reimagine Atomic CSS (The CSS Icons Preset)</a></li>
<li>Nov. 2021 - Icons in Pure CSS - <em>you are here!</em></li>
</ul>
<h2>Prior Arts</h2>
<p>I know there is a Pure CSS icon solution called <a href="https://github.com/astrit/css.gg"><code>css.gg</code></a>, which is a great idea to use pseudo-elements (<code>::before</code>, <code>::after</code>) to construct the icons. However, that could require some expert knowledge of how CSS works, but I imagine that approach could be hard to create more complex icons. Instead of the limited choices in a specific set, I am seeking <strong>a more general solution that could apply to any icons</strong>.</p>
<h2>The Idea</h2>
<p>The idea come from <a href="https://github.com/antfu/unplugin-icons/issues/88">this feature request</a> created by <a href="https://github.com/husayt">@husayt</a> to <code>unplugin-icons</code> and the initial implementation in <a href="https://github.com/antfu/unplugin-icons/pull/90">this pull request</a> by <a href="https://github.com/userquin">@userquin</a>. The idea here is quite straightforward - to generate CSS with the icons in <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs">DataURI</a> as the background image.</p>
<pre><code class="language-css">.my-icon {
  background: url(data:...) no-repeat center;
  background-color: transparent;
  background-size: 16px 16px;
  height: 16px;
  width: 16px;
  display: inline-block;
}
</code></pre>
<p>With that, we could use any images inlined in CSS with a single class.</p>
<div grid="~ cols-2">
<pre><code class="language-html">&lt;div class=&quot;my-icon&quot;&gt;&lt;/div&gt;
</code></pre>
<div i-twemoji-grinning-face text-5xl my-auto mx-4 />
</div>
<p>It's indeed an interesting idea. However, this is more like an image instead of an icon. To me, an icon has to be scalable and colorable (if it's monochrome).</p>
<h2>Make it Work</h2>
<h3>DataURI</h3>
<p>Thanks again to <a href="https://iconify.design/">Iconify</a>, which unified 100+ icon sets with 10,000+ icons into <a href="https://github.com/iconify/collections-json">the consistent JSON format</a>. It allows us to get the SVG of any icon set by simply providing the collection and icon ids. The usage is like this:</p>
<pre><code class="language-ts">import { getIconData, iconToSVG } from '@iconify/utils'

const svg = iconToSVG(getIconData('mdi', 'alarm'))
// (this is not the exact API, simplified here for demo)
</code></pre>
<p>Once we got the SVG string, we could convert the it to DataURI:</p>
<pre><code class="language-ts">const dataUri = `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`
</code></pre>
<p>Talking about DataURI, it's almost the default choice to use <a href="https://developer.mozilla.org/en-US/docs/Glossary/Base64">Base64</a> until I read <a href="https://css-tricks.com/probably-dont-base64-svg/">Probably Don't Base64 SVG</a> by Chris Coyier. Base64 is needed to encode binary data like images to be used in plain text files like CSS, while for SVG, since it's already in text format, the extra encoding to Base64 actually makes the file size larger.</p>
<p>Combine the technique mentioned in <a href="https://codepen.io/Tigt/post/optimizing-svgs-in-data-uris">Optimizing SVGs in data URIs</a> by Taylor Hunt to improve the output size, further, here is the solution we end up with.</p>
<pre><code class="language-ts">// https://bl.ocks.org/jennyknuth/222825e315d45a738ed9d6e04c7a88d0
function encodeSvg(svg: string) {
  return svg.replace('&lt;svg', (~svg.indexOf('xmlns') ? '&lt;svg' : '&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot;'))
    .replace(/&quot;/g, '\'')
    .replace(/%/g, '%25')
    .replace(/#/g, '%23')
    .replace(/\{/g, '%7B')
    .replace(/\}/g, '%7D')
    .replace(/&lt;/g, '%3C')
    .replace(/&gt;/g, '%3E')
}

const dataUri = `data:image/svg+xml;utf8,${encodeSvg(svg)}`
</code></pre>
<h3>Scalable</h3>
<p>The first step of making the &quot;image&quot; more like an icon, we need to make it scalable to the context.</p>
<p>Luckily we have the first-class support scaling support - the <code>em</code> unit.</p>
<pre><code class="language-css">.my-icon {
  background: url(data:...) no-repeat center;
  background-color: transparent;
  background-size: 100% 100%;
  height: 1em;
  width: 1em;
}
</code></pre>
<p>By changing the <code>height</code> and <code>width</code> to <code>1em</code>, and the <code>background-size</code> to <code>100%</code>, we made the image scales based on the parent's font size.</p>
<ul>
<li><span text-sm>Small <span inline-block vertical-text-bottom i-ri-bike-line></span></span></li>
<li><span text-base>Normal <span inline-block vertical-text-bottom i-ri-bike-line></span></span></li>
<li><span text-xl>Large <span inline-block vertical-text-bottom i-ri-bike-line></span></span></li>
</ul>
<h3>Colorable</h3>
<p>In inlined SVG, we could use <a href="https://www.w3.org/TR/css-color-3/#currentcolor"><code>fill=&quot;currentColor&quot;</code></a> to make the color of the SVG matches with the current text color. However, when we use it as a background image, it becomes a flat image. The dynamic parts of the SVG are lost, so is the <code>currentColor</code> magic (it's just like you can't override the color of a PNG).</p>
<p>If you do a quick search, you will find that most people are telling you that you can't. Some might offer you the option to assign the colors in the SVG before converting to DataURI, which could solve the specific problem that you want the icon to have color, but not the root cause that the color is not reactive to the context.</p>
<p>Then you might come up with the idea of using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/filter">CSS filters</a>, like Una Kravets mentioned in <a href="https://css-tricks.com/solved-with-css-colorizing-svg-backgrounds/">Solved with CSS! Colorizing SVG Backgrounds</a>. That sounds valid, but only that you need to calculate the matrix of how to transform the color to the desired ones. Probably feasible by introducing some runtime JavaScript for that? Maybe, if so, we lost the whole point of trying icons in pure CSS.</p>
<p>This sounds like a dead-end to me. Until I accidentally found the article <a href="https://codepen.io/noahblon/post/coloring-svgs-in-css-background-images">Coloring SVGs in CSS Background Images</a> by Noah Blon. In the article, Noah mentioned a brilliant idea of using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/mask">CSS masks</a> - a property that I have never heard of before.</p>
<pre><code class="language-css">.my-icon {
  background-color: red;
  mask-image: url(icon.svg);
}
</code></pre>
<p>Instead of using the icon as a background image and figuring out a way to color it, we could actually use the icon as a mask to clip the filled background color. Furthermore, we could now use the <code>currentColor</code> magic to have the icon matching with the parent text color!</p>
<pre><code class="language-css">.my-icon {
  background-color: currentColor;
  mask-image: url(icon.svg);
}
</code></pre>
<div pt-4 />
<div text-sky text-xl>This is a blue text, with the blue icon <div i-uil-cloud-showers-heavy /><div i-uil:wind /></div>
<div text-lime text-xl>Green <div i-uil:trees /><div i-uil:desert /></div>
<div text-orange text-xl>Orange <div i-uil:restaurant /><div i-uil:store-alt /></div>
<h3>Icons with Colors</h3>
<p>We made the monochrome icons colorable but now it problem comes to the icons with colors. With the mask approach, the colors and content of the icons got lost, for example:</p>
<div text-4xl inline-flex gap-2 py-4 px-8 bg-gray-400:15 rounded>
<div text-base my-auto>Icon:</div>
<div i-twemoji:astonished-face />
<div text-base my-auto ml-4>Masked:</div>
<div i-ph:circle-fill style="transform: scale(1.3)" />
</div>
<p>Yes, I might say it's hard for one approach to cover all the cases.</p>
<p>Unless - you could <strong>blend two approaches into one</strong>! Remember we just talked about the background image approach serving the icons as images? Isn't that just what we want for colored icons? - We don't need to change the colors after all!</p>
<p>So the solution is actually pretty simple, we just need to find a way to distinguish the monochrome and colored icons smartly. Luckily, since we had access the the SVG content, we could have:</p>
<pre><code class="language-ts">// if an SVG icon have the `currentColor` value,
// it's very likely to be a monochrome icon
const mode = svg.includes('currentColor')
  ? 'mask'
  : 'background-img'

const uri = `url(&quot;data:image/svg+xml;utf8,${encodeSvg(svg)}&quot;)`

// monochrome
if (mode === 'mask') {
  return {
    'mask': `${uri} no-repeat`,
    'mask-size': '100% 100%',
    'background-color': 'currentColor',
    'height': '1em',
    'width': '1em',
  }
}
// colored
else {
  return {
    'background': `${uri} no-repeat`,
    'background-size': '100% 100%',
    'background-color': 'transparent',
    'height': '1em',
    'width': '1em',
  }
}
</code></pre>
<p>And it works surprisingly well! You know, it's now behavior similar to the thing we are using daily - system's native emojis. The color of texts changes based on the context, while emojis stay the colors of their own.</p>
<p>Here are some showcases of what we end up with:</p>
<div text-xl all:mx-1 all:my-2 all:vertical-middle>
<p><span op60 text-sm inline-block w-40 text-right>Material Design</span> <div i-ic:baseline-account-circle /> <div i-ic:baseline-card-membership /> <div i-ic:baseline-verified text-green5 /> <div i-ic:outline-explore text-sky5 /><br>
<br><span op60 text-sm inline-block w-40 text-right>Carbon</span> <div i-carbon:chart-multitype /> <div i-carbon:network-4 /> <div i-carbon:wind-gusts /> <div i-carbon:collaborate /><br>
<br><span op60 text-sm inline-block w-40 text-right>Tabler</span> <div i-tabler:building-carousel /> <div i-tabler:circle-square /> <div i-tabler:color-swatch /> <div i-tabler:cut /><br>
<br><span op60 text-sm inline-block w-40 text-right>Twemoji</span> <div i-twemoji:grinning-face-with-smiling-eyes /> <div i-twemoji:face-in-clouds /> <div i-twemoji:weary-cat /> <div i-twemoji:teacup-without-handle /><br>
<br><span op60 text-sm inline-block w-40 text-right>Logos</span> <div i-logos:vue /> <div i-logos:blender /> <div i-logos:chrome /> <div i-logos:codepen-icon /></p>
</div>
<p>To see and find all the icons available, you can check out my other project <a href="https://icones.js.org/">Icônes</a>.</p>
<h2>Use It</h2>
<p>If you want to try this icons solution in your project, you can install <a href="https://github.com/antfu/unocss">UnoCSS</a> and the icons preset:</p>
<pre><code class="language-bash">npm i -D unocss @unocss/preset-icons @iconify/json
</code></pre>
<p><code>@iconify/json</code> is the package that stores the icon data from Iconify. Alternatively, you could install per icon set, for example, <code>@iconify-json/mdi</code> for Material Design Icons or <code>@iconify-json/carbon</code> for Carbon Icons and so on.</p>
<p>Then in your <code>vite.config.js</code></p>
<pre><code class="language-ts">import UnocssIcons from '@unocss/preset-icons'
import UnoCSS from 'unocss'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    UnoCSS({
      // when `presets` is specified, the default preset will be disabled
      // so you could only use the pure CSS icons in addition to your
      // existing app without polluting other CSS
      presets: [
        UnocssIcons({
          // options
          prefix: 'i-',
          extraProperties: {
            display: 'inline-block'
          }
        }),
        // presetUno() - if you want to use other atomic CSS as well
      ],
    }),
  ],
})
</code></pre>
<p>And that's it for today. Hope you enjoy this icons solution from UnoCSS, or get some inspiration from it for your own projects.</p>
<p>Thanks for reading, and see you :)</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Reimagine Atomic CSS]]></title>
            <link>https://antfu.me/posts/reimagine-atomic-css</link>
            <guid isPermaLink="true">https://antfu.me/posts/reimagine-atomic-css</guid>
            <pubDate>Tue, 26 Oct 2021 16:00:00 GMT</pubDate>
            <description><![CDATA[Let's take a step back and reimagine what's atomic CSS could be in the best.]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<blockquote>
<p>This post will be a bit longer than usual. It's quite a big announcement to me, and there are many things I want to talk about. I'll be appreciated if you take the time to read through it. The table of contents is hidden on the left if you are on a desktop. Hope you enjoy :)</p>
</blockquote>
<blockquote>
<p><a href="/posts/reimagine-atomic-css-zh">中文 Chinese Version</a></p>
</blockquote>
<h2>What is Atomic CSS?</h2>
<p>Let's first give a proper definition to <strong>Atomic CSS</strong>:</p>
<p>From <a href="https://css-tricks.com/lets-define-exactly-atomic-css/">this article</a> by John Polacek:</p>
<blockquote>
<p>Atomic CSS is the approach to CSS architecture that favors small, single-purpose classes with names based on visual function.</p>
</blockquote>
<p>Some might also call it Functional CSS, or CSS utilities. Basically, you can say an Atomic CSS framework is a collection of the CSS like these:</p>
<pre><code class="language-css">.m-0 {
  margin: 0;
}
.text-red {
  color: red;
}
/* ... */
</code></pre>
<p>We have quite a few utilities-first CSS framework like <a href="https://tailwindcss.com/">Tailwind CSS</a>, <a href="https://windicss.org/">Windi CSS</a> and <a href="https://tachyons.io/">Tachyons</a>, etc. And there are also some UI libraries that come with some CSS utilities as the complement to the framework, for example <a href="https://getbootstrap.com/docs/5.1/utilities/api/">Bootstrap</a> and <a href="https://chakra-ui.com/docs/features/style-props">Chakra UI</a>.</p>
<p>We are not going to talk about the pros and cons of using atomic CSS here, as you might hear them many times already. Today, we are going to use a framework author's perspective to see how we make the trade-off building those frameworks you love, their limitations, what we can do better to eventually benefits your daily work.</p>
<h2>The Background</h2>
<p>Before we start, let's talk a bit about the background. If you don't know me, my name is Anthony Fu, and I am a <a href="https://vitejs.dev/">Vite</a> team member and the creator of <a href="https://github.com/antfu/vitesse">Vitesse</a>, one of the most popular starter templates for Vite. I enjoy the speedy development experience of atomic CSS (or CSS utilities), so I chose to use <a href="https://tailwindcss.com/">Tailwind CSS</a> as the default UI framework for Vitesse. While Vite should be incredibly fast compared to Webpack and others, Tailwind, which generates megabytes of utility CSS, makes the start-up and HMR on Vite slow as the old days. I once thought this was some kind of trade-off for using atomic CSS solutions - until I discovered <a href="https://windicss.org">Windi CSS</a>.</p>
<img src="https://antfu.me/images/discover-windicss.png" class="transform scale-110 py-2"/>
<p><a href="https://windicss.org">Windi CSS</a> was a Tailwind CSS alternative that was written from scratch. It has zero dependencies and does not rely on PostCSS and Autoprefixer. More importantly, it features <strong>on-demanded usage</strong>. Instead of generating all the combinations of utilities that you rarely used to purge later, Windi CSS only generates those actually presented in your codebase. This fits perfectly well with Vite's on-demanded philosophy, and theoretically, it should be way much faster than Tailwind. So I wrote <a href="https://github.com/windicss/vite-plugin-windicss">the Vite plugin</a> for it, and it turned out to be <a href="https://twitter.com/antfu7/status/1361398324587163648">20~100x faster</a> than Tailwind.</p>
<p>It went pretty well, Windi CSS grown into a team, we made many more innovations like <a href="https://windicss.org/features/value-auto-infer.html">Value Infering</a>, <a href="https://windicss.org/features/variant-groups.html">Variant Groups</a>, <a href="https://windicss.org/features/shortcuts.html">Shortcuts</a>, <a href="https://twitter.com/antfu7/status/1372244287975387145">Design in DevTools</a>, <a href="https://twitter.com/windi_css/status/1387460661135896577">Attributify Mode</a>, etc. As the result, Tailwind was <a href="https://twitter.com/adamwathan/status/1371542711086559237?s=20">ass kicked</a> to introduce their own on-demand <a href="https://tailwindcss.com/docs/just-in-time-mode">JIT engine</a>.</p>
<h2>Breakdown Atomic CSS</h2>
<p>Back to the topic, let's take a look at how atomic CSS works first.</p>
<h3>Traditional Way</h3>
<p>The traditional way of making Atomic CSS is to provide all the CSS utilities you might possibly want, for example, here is something you could generate your own with a preprocessor (SCSS in this case):</p>
<pre><code class="language-scss">// style.scss

@for $i from 1 through 10 {
  .m-#{$i} {
    margin: $i / 4 rem;
  }
}
</code></pre>
<p>It will be compiled to:</p>
<!-- eslint-skip -->
<pre><code class="language-css">.m-1 { margin: 0.25 rem; }
.m-2 { margin: 0.5 rem; }
/* ... */
.m-10 { margin: 2.5 rem; }
</code></pre>
<p>Great, now you can use <code>class=&quot;m-1&quot;</code> to set the margin. But as you might see, with this approach, you can't set the margin outside of 1 to 10, and also, you need to pay the cost of shipping 10 CSS rules even if you have only used one. Later if you want to support different margin directions like <code>mt</code> for <code>margin-top</code>, <code>mb</code> for <code>margin-bottom</code>. With those 4 directions, you are multiplying your CSS size by 5. Then when it comes to variants like <code>hover:</code> and <code>focus:</code> - you know the story. At that point, adding one more utility often means you are going to introduce a few extra kilobytes. Thus, this is also why the traditional Tailwind ships megabytes of CSS.</p>
<p>To solve this, Tailwind came up with the solution by using <a href="https://purgecss.com/">PurgeCSS</a> to scan your dist bundle and remove the rules you don't need. Now you have only a few KBs of CSS in production. However, note that the purging would only work in the production build, meaning you are still working with the tremendous CSS in development. It wasn't that prominent in Webpack, but it becomes a pain in Vite, given the rest are now coming blazing fast.</p>
<p>While generating and purging approach have its limitations, could we have a better solution?</p>
<h3>On-demand Way</h3>
<p>The &quot;on-demand&quot; idea introduces a brand new way of thinking. Let's make a comparison of the approaches here.</p>
<img src="/images/unocss-traditional-way.png" class="filter dark:invert" />
<p>The traditional way not only costs you unnecessary computation (generated but not in use) but is also unable to satisfy your needs that are not included in the first place.</p>
<img src="/images/unocss-on-demand-way.png" class="filter dark:invert" />
<p>By flipping the order of &quot;generating&quot; and &quot;usage scanning&quot;, the &quot;on-demand&quot; approach saves you the wasted computational and transferring cost, while being flexible to provide the dynamic needs that pre-generating can't possibly be covered. Meanwhile, this approach could be used in both development and production, provide more confidence about the consistency and make HMR more efficient.</p>
<p>To achieve this, both Windi CSS and Tailwind JIT take the approach of pre-scanning your source code. Here is a simple example of that:</p>
<pre><code class="language-ts">import { promises as fs } from 'node:fs'
import glob from 'fast-glob'

// this usually comes from user config
const include = ['src/**/*.{jsx,tsx,vue,html}']

async function scan() {
  const files = await glob(include)

  for (const file of files) {
    const content = await fs.readFile(file, 'utf8')
    // pass the content to the generator and match for class usages
  }
}

await scan()
// scanning is done before the build / dev process
await buildOrStartDevServer()
</code></pre>
<p>To provide HMR during development, a <a href="https://github.com/paulmillr/chokidar">file watcher</a> is usually needed:</p>
<pre><code class="language-ts">import chokidar from 'chokidar'

chokidar.watch(include).on('change', (event, path) =&gt; {
  // read the file again
  const content = await fs.readFile(file, 'utf8')
  // pass the content to the generator again
  // invalidate the css module and send HMR event
})
</code></pre>
<p>As a result, with the on-demand approach, Windi CSS is able to provide about <a href="https://twitter.com/antfu7/status/1361398324587163648">100x faster performance</a> than the traditional Tailwind CSS.</p>
<h2>The Itches</h2>
<p>I am now using Windi CSS on almost all my apps, and it works pretty well. The performance is sweet, and the HMR is unnoticeable. <a href="https://windicss.org/features/value-auto-infer.html">Value Auto Infering</a> and <a href="https://twitter.com/windi_css/status/1387460661135896577">Attributify Mode</a> makes my development even faster. I could really take a good sleep and dream about other things then. However, it sometimes itches me from my sweet dream.</p>
<p>The one I found annoying is the unclearness of what I am getting and what to do to make it work. To me, the best ideally atomic CSS should be invisible. Once learned, it should be intuitive and analogous to know the others. It's invisible when it works as you expect and could become frustrating when it doesn't.</p>
<p>For example, you know that in Tailwind's <code>border-2</code> means <code>2px</code> of border width, <code>4</code> for <code>4px</code>, <code>6</code> for <code>6px</code>, <code>8</code> for <code>8px</code>, but guess what, <code>border-10</code> <strong>does NOT work</strong> (it could also take your time to figure it out!). You might say this is designed on purpose by Tailwind to make the design system consistent and limited. Ok fine, but here is a quick quiz, <strong>let's say if you want <code>border-10</code> to work, how would you do that?</strong></p>
<p>Write your own utility somewhere in your global styles?</p>
<pre><code class="language-css">.border-10 {
  border-width: 10px;
}
</code></pre>
<p>That's pretty fast and straightforward. And importantly, it works. But honestly, if I need to do this manually myself, why would I need Tailwind in the first place?</p>
<p>If you know Tailwind a bit more, you might know it can be configured. So you spend 5 minutes searching for their docs, here is what you <a href="https://tailwindcss.com/docs/border-width#border-widths">end up with</a>:</p>
<pre><code class="language-js">// tailwind.config.js
module.exports = {
  theme: {
    borderWidth: {
      DEFAULT: '1px',
      0: '0',
      2: '2px',
      3: '3px',
      4: '4px',
      6: '6px',
      8: '8px',
      10: '10px' // &lt;-- here
    }
  }
}
</code></pre>
<p>Ah, fair enough, now we could list them all and get back to work... wait, where was I? The original task you are working on gets lost, and it takes time to get back to the context again. Later on, if we want to set border colors, we'd need to look up the docs again to see how it could be configured and so on. Maybe someone would enjoy this workflow, but it's not for me. I don't enjoy being interrupted by something that should intuitively work.</p>
<p>Windi CSS is more relaxed to the rules and will try to provide the corresponding utilities whenever possible. In the previous case, <code>border-10</code> will work out-of-box on Windi (thank you!). But due to the fact that Windi is compatible with Tailwind, it also has to use the exact same configuration interface as Tailwind. While the number inferring works in Windi, it would still be a nightmare if you want to add custom utilities. Here is an example from <a href="https://tailwindcss.com/docs/plugins#escaping-class-names">Tailwind's docs</a>:</p>
<pre><code class="language-ts">// tailwind.config.js
const _ = require('lodash')
const plugin = require('tailwindcss/plugin')

module.exports = {
  theme: {
    rotate: {
      '1/4': '90deg',
      '1/2': '180deg',
      '3/4': '270deg',
    }
  },
  plugins: [
    plugin(({ addUtilities, theme, e }) =&gt; {
      const rotateUtilities = _.map(theme('rotate'), (value, key) =&gt; {
        return {
          [`.${e(`rotate-${key}`)}`]: {
            transform: `rotate(${value})`
          }
        }
      })

      addUtilities(rotateUtilities)
    })
  ]
}
</code></pre>
<p>That alone is to generate these:</p>
<pre><code class="language-css">.rotate-1\/4 {
  transform: rotate(90deg);
}
.rotate-1\/2 {
  transform: rotate(180deg);
}
.rotate-3\/4 {
  transform: rotate(270deg);
}
</code></pre>
<p>The code to generate the CSS is even longer than the outcome. It could be hard to read and maintain, and meanwhile, it breaks the on-demand ability.</p>
<p>Tailwind's API and plugin system is designed with the traditional mindset and does not really match the new on-demand approach. Core utilities are baked in the generator, and the customization is quite limited. So, I started wondering if we could abandon those debts and redesign it ground-up with the on-demand approach in mind, what would we get?</p>
<h2>Introducing UnoCSS</h2>
<p><a href="https://github.com/antfu/unocss"><strong>UnoCSS</strong></a> - the instant atomic CSS engine with maximum performance and flexibility.</p>
<p>It started with some random experiments during my national holiday. With the mind of on-demand and the flexibility that I would expect as a user, the experiments turned out to be very good to me in many ways.</p>
<h3>The Engine</h3>
<p>UnoCSS is an <strong>engine</strong> instead of a <strong>framework</strong> because there are <strong>no core utilities</strong> - all the functionalities are provided via presets or inline configurations.</p>
<p>We are imagining UnoCSS being able to simulate the functionalities of most of the existing atomic CSS frameworks. And possibly have been used as the engine to create some new atomic CSS frameworks! For example:</p>
<pre><code class="language-ts">import PresetAntfu from '@antfu/oh-my-cool-unocss-preset'

import PresetBootstrap from '@unocss/preset-bootstrap'
// the following presets do not exist at this moment,
// contribution welcome!
import PresetTachyons from '@unocss/preset-tachyons'
import PresetTailwind from '@unocss/preset-tailwind'
import PresetWindi from '@unocss/preset-windi'
import UnocssPlugin from '@unocss/vite'

export default {
  plugins: [
    UnocssPlugin({
      presets: [
        // PresetTachyons,
        PresetBootstrap,
        // PresetTailwind,
        // PresetWindi,
        // PresetAntfu

        // pick one... or multiple!
      ]
    })
  ]
}
</code></pre>
<p>Let's take a look at how it made them possible:</p>
<h3>Intuitive &amp; Fully Customizable</h3>
<p>The main goals of UnoCSS are intuitiveness and customization. It allows you to define your own utilities literally in seconds.</p>
<p>Here is a quick guide through:</p>
<h6>Static Rules</h6>
<p>Atomic CSS might come huge in terms of the amount. It's important to have the rules definition straightforward and easy to read. To create a custom rule for UnoCSS, you can write it as follows:</p>
<pre><code class="language-ts">rules: [
  ['m-1', { margin: '0.25rem' }]
]
</code></pre>
<p>Whenever <code>m-1</code> is detected in users' codebase, the following CSS will be generated:</p>
<!-- eslint-skip -->
<pre><code class="language-css">.m-1 { margin: 0.25rem; }
</code></pre>
<h6>Dynamic Rules</h6>
<p>To make it dynamic, change the matcher to a RegExp and the body to a function:</p>
<pre><code class="language-ts">rules: [
  [/^m-(\d+)$/, ([, d]) =&gt; ({ margin: `${d / 4}rem` })],
  [/^p-(\d+)$/, match =&gt; ({ padding: `${match[1] / 4}rem` })],
]
</code></pre>
<p>The first argument of the body function is the match result, so you can destructure it to get the RegExp matched groups.</p>
<p>For example, with the usage:</p>
<pre><code class="language-html">&lt;div class=&quot;m-100&quot;&gt;
  &lt;button class=&quot;m-3&quot;&gt;
    &lt;icon class=&quot;p-5&quot; /&gt;
    My Button
  &lt;/button&gt;
&lt;/div&gt;
</code></pre>
<p>the corresponding CSS will be generated:</p>
<!-- eslint-skip -->
<pre><code class="language-css">.m-100 { margin: 25rem; }
.m-3 { margin: 0.75rem; }
.p-5 { padding: 1.25rem; }
</code></pre>
<p>That's it. You only need to add more utilities using the same pattern, and now you got your own atomic CSS running!</p>
<h3>Variants</h3>
<p><a href="https://windicss.org/utilities/variants.html#variants">Variants</a> in UnoCSS are also simple yet powerful. Here are a few examples:</p>
<pre><code class="language-ts">variants: [
  // support `hover:` for all rules
  {
    match: s =&gt; s.startsWith('hover:') ? s.slice(6) : null,
    selector: s =&gt; `${s}:hover`,
  },
  // support `!` prefix to make the rule important
  {
    match: s =&gt; s.startsWith('!') ? s.slice(1) : null,
    rewrite: (entries) =&gt; {
      // append ` !important` to all css values
      entries.forEach(e =&gt; e[1] += ' !important')
      return entries
    },
  }
]
</code></pre>
<p>The configurations of variants could be a bit advanced. Due to the length of the post, I will skip the detailed explanation here, you can refer to <a href="https://github.com/antfu/unocss#custom-variants">the docs</a> for more details.</p>
<h3>Presets</h3>
<p>Now you can pack your custom rules and variants into presets and share them with others - or create even your own atomic CSS framework on top of UnoCSS!</p>
<p>Meanwhile, we ship with <a href="https://github.com/antfu/unocss#presets">a few presets</a> for you to get your hands on quickly.</p>
<p>One thing worth mentioning is the default <a href="https://github.com/antfu/unocss/tree/main/packages/preset-uno"><code>@unocss/preset-uno</code></a> preset (<strong>still experimental</strong>) is a common superset of the popular utilities-first framework, including Tailwind CSS, Windi CSS, Bootstrap, Tachyons, etc.</p>
<p>For example, both <code>ml-3</code> (Tailwind), <code>ms-2</code> (Bootstrap), <code>ma4</code> (Tachyons), <code>mt-10px</code> (Windi CSS) are valid.</p>
<!-- eslint-skip -->
<pre><code class="language-css">.ma4 { margin: 1rem; }
.ml-3 { margin-left: 0.75rem; }
.ms-2 { margin-inline-start: 0.5rem; }
.mt-10px { margin-top: 10px; }
</code></pre>
<p><a href="https://github.com/antfu/unocss/tree/main/packages/preset-uno">Learn more about the default preset</a>.</p>
<h3>Flexibility</h3>
<p>Till now, we are all showcasing how you can use UnoCSS to mimic the behavior of Tailwind - while we made it really easy to mimic Tailwind by your own, that alone probably won't make much difference on the user side.</p>
<p>Let's unleash the true power of UnoCSS:</p>
<h6>Attributify Mode</h6>
<p><a href="https://windicss.org/posts/v30.html#attributify-mode">The Attributify Mode</a> is one of the beloved features of Windi CSS. It helps you better organize and group your utilities by using attributes.</p>
<p>It turns your Tailwind code from this:</p>
<!-- eslint-skip -->
<pre><code class="language-html">&lt;button class=&quot;bg-blue-400 hover:bg-blue-500 text-sm text-white font-mono font-light py-2 px-4 rounded border-2 border-blue-200 dark:bg-blue-500 dark:hover:bg-blue-600&quot;&gt;
  Button
&lt;/button&gt;
</code></pre>
<p>to:</p>
<pre><code class="language-html">&lt;button
  bg=&quot;blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600&quot;
  text=&quot;sm white&quot;
  font=&quot;mono light&quot;
  p=&quot;y-2 x-4&quot;
  border=&quot;2 rounded blue-200&quot;
&gt;
  Button
&lt;/button&gt;
</code></pre>
<p>Not only this provide better organization by the categories, but also saves you the repetitive typing of the same prefixes.</p>
<p>In UnoCSS, we implemented the Attributify Mode by using only <a href="https://github.com/antfu/unocss/blob/main/packages/preset-attributify/src/variant.ts"><strong>one variant</strong></a> and <a href="https://github.com/antfu/unocss/blob/main/packages/preset-attributify/src/extractor.ts"><strong>one extractor</strong></a> with <strong>less than 100 lines of code</strong> in total! More importantly, it directly works for any custom rules you have defined!</p>
<p>In addition Windi's Attributify Mode, we also support valueless attributes with a few lines of changes:</p>
<pre><code class="language-html">&lt;div class=&quot;m-2 rounded text-teal-400&quot; /&gt;
</code></pre>
<p>now can be</p>
<pre><code class="language-html">&lt;div m-2 rounded text-teal-400 /&gt;
</code></pre>
<p>Attributify Mode is provided via preset <a href="https://github.com/antfu/unocss/blob/main/packages/preset-attributify"><code>@unocss/preset-attributify</code></a>, refer to its docs for detailed usages.</p>
<h6>Pure CSS Icons</h6>
<p>If you've ever read my previous post <a href="/posts/journey-with-icons-continues">Journey with Icons Continues</a> you must know that I am very enthusiastic about icons and actively researching for icons solutions. This time with UnoCSS's flexibility, we could even have pure CSS icons! Yes, you read me, <strong>it's purely in CSS and zero JavaScript</strong>! Let's just see how it looks like:</p>
<pre><code class="language-html">&lt;!-- A basic anchor icon from Phosphor icons --&gt;
&lt;div class=&quot;i-ph-anchor-simple-thin&quot; /&gt;
&lt;!-- An orange alarm from Material Design Icons --&gt;
&lt;div class=&quot;i-mdi-alarm text-orange-400 hover:text-teal-400&quot; /&gt;
&lt;!-- A large Vue logo --&gt;
&lt;div class=&quot;i-logos-vue text-3xl&quot; /&gt;
&lt;!-- Sun in light mode, Moon in dark mode, from Carbon --&gt;
&lt;button class=&quot;i-carbon-sun dark:i-carbon-moon&quot; /&gt;
&lt;!-- Twemoji of laugh, turns to tear on hovering --&gt;
&lt;div class=&quot;i-twemoji-grinning-face-with-smiling-eyes hover:i-twemoji-face-with-tears-of-joy&quot; /&gt;
</code></pre>
<div flex gap-2 text-4xl p-2 mt4>
  <!-- A basic anchor icon from Phosphor icons -->
  <div class="i-ph-anchor-simple-thin" />
  <!-- An orange alarm from Material Design Icons -->
  <div class="i-mdi-alarm text-orange-400 hover:text-teal-400" />
  <!-- A large Vue logo -->
  <div class="i-logos-vue transform transition-800 hover:rotate-180" />
  <!-- Sun in light mode, Moon in dark mode, from Carbon -->
  <button class="i-carbon-sun dark:i-carbon-moon" @click="toggleDark()"/>
  <!-- Twemoji of laugh, turns to tear on hovering -->
  <div class="i-twemoji-grinning-face-with-smiling-eyes hover:i-twemoji-face-with-tears-of-joy" />
  <div text-base my-auto flex><div i-carbon-arrow-left my-auto mr-1 /> Hover it</div>
</div>
<script setup>
import { isDark } from '~/logics'

function toggleDark() {
  isDark.value = !isDark.value
}
</script>
<p>Combining with variants, you can even switch icons based on hovering state or even color schema. Play with the demo above and see. Thanks to the awesome <a href="https://iconify.design/">Iconify</a> project, you have access to over <strong>10,000 icons</strong> from over 100 popular icon sets <strong>on-demand</strong>.</p>
<p>Once again, this feature is written with less than 100 lines of code. Check out the preset's implementation <a href="https://github.com/antfu/unocss/blob/main/packages/preset-icons"><code>@unocss/preset-icons</code></a> to learn the magic!</p>
<blockquote>
<p>Update: Read my new post <a href="/posts/icons-in-pure-css">Icons in Pure CSS</a> to learn more about it!</p>
</blockquote>
<p>I hope these presets can give you a general idea of how flexible UnoCSS is. Given it's still in a very early stage, there are many possibilities for us to explore.</p>
<h3>Scoping</h3>
<p>One other problem that I have faced when using Tailwind/Windi is the preflight. Preflight resets the native elements and provide some fallback for CSS variables, it's great when developing a new app that uses Tailwind/Windi solely, but when you want to have them work with other UI frameworks, or share some components using Tailwind utilities, the preflight often introduce many conflicts that break your existing UI.</p>
<p>So UnoCSS took another aggressive step by not supporting preflights. Instead, it left the control of CSS resetting fully to users (or frameworks on top of UnoCSS) to use the one that fits their needs (Normalize.css, Reset.css, or UI frameworks' resetting, etc.)</p>
<p>This also allows UnoCSS to have more possibilities on CSS Scoping. For example, we have an experimental <code>scoped-vue</code> mode on the Vite plugin to generate scoped styles for each component so you can safely ship them as a component library using atomic CSS without worry about conflicting with users' CSS. For example:</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;div class=&quot;m-2 rounded&quot;&gt;
    &lt;slot /&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;!-- the following will be inject in the bundler --&gt;
&lt;style scoped&gt;
.m-2 {
  margin: 0.5rem;
}
.rounded {
  border-radius: 0.25rem;
}
&lt;/style&gt;
</code></pre>
<p>We are also experimenting with more possibilities like Web Components support, CSS code-splitting for MPA, module-level CSS scoping, etc.</p>
<h2>Performance</h2>
<p>Given all the flexibility and imagination UnoCSS brings, I would frankly think performance can be a less important thing to care about. Just out of curiosity, I wrote <a href="https://github.com/antfu/unocss/tree/main/bench">a simple benchmark</a> to compare the performances. And surprisingly, here is the result:</p>
<pre><code class="language-yaml">10/21/2021, 2:17:45 PM
1656 utilities | x50 runs

none                            8.75 ms /    0.00 ms
unocss       v0.0.0            13.72 ms /    4.97 ms (x1.00)
windicss     v3.1.9           980.41 ms /  971.66 ms (x195.36)
tailwindcss  v3.0.0-alpha.1  1258.54 ms / 1249.79 ms (x251.28)
</code></pre>
<p>It turns out, UnoCSS could be <strong>200x faster than Tailwind's JIT and Windi CSS</strong>. To be honest, with the on-demand approach, both Windi and Tailwind JIT are already super-fast, the performance gain in UnoCSS might not be very perceivable. However, the nearly zero overhead means you can integrate UnoCSS into your existing project to work with other frameworks as an incremental solution without worrying about the performance loss.</p>
<p>Deep down, UnoCSS did many performance optimizations. In case you wonder, here is a few of them to take away:</p>
<h3>No Parsing, No AST</h3>
<p>Internally, Tailwind relied on modifying PostCSS's AST, while Windi wrote a custom parser and AST. Given the fact that changes in utilities are not commonly expected during the development, UnoCSS generates the utilities by the very cheap string concatenation instead of introducing a whole parser and generating process. Meanwhile, UnoCSS aggressively caches to the class names with their generated CSS string, allowing it to bypass the entire matching and generating process when seeing the same utilities again.</p>
<h3>Single Pass</h3>
<p>As mentioned in the previous section, both Windi CSS and Tailwind JIT rely on the pre-scanning for the file system and use fs watcher for HMR. File IO inevitably introduces some overhead, while your build tools are actually needed to load them once again. So why don't we leverage the content that has already been read by the dev tools directly?</p>
<p>Other than the independent generator core, UnoCSS intentionally only provides Vite plugin which allows it to focus on the best possible integration with Vite.</p>
<blockquote>
<p>Updates: Now it also provides <a href="https://github.com/antfu/unocss/tree/main/packages/webpack">a Webpack plugin</a> and <a href="https://github.com/antfu/unocss/tree/main/packages/runtime">a CSS-in-JS runtime</a></p>
</blockquote>
<p>In Vite, the <code>transform</code> hook will be iterated over with all the files with their content. So we can write a plugin to collect them like:</p>
<pre><code class="language-ts">export default {
  plugins: [
    {
      name: 'unocss',
      transform(code, id) {
        // filter out the files you don't want to scan
        if (!filter(id))
          return

        // scan the code (also handles invalidate on dev)
        scan(code, id)

        // we just want the content, so we don't transform the code
        return null
      },
      resolveId(id) {
        return id === VIRTUAL_CSS_ID ? id : null
      },
      async load(id) {
        // generated css is provide as a virtual module
        if (id === VIRTUAL_CSS_ID)
          return { code: await generate() }
      }
    }
  ]
}
</code></pre>
<p>Given Vite also handles the HMR and will involve the <code>transform</code> hook again of upon file changes, this allows UnoCSS to finish everything in a single pass with no duplication of file IO and fs watcher. In addition to that, with this approach, the scanning relies on the module graph instead of file globing. Meaning that only the modules that been bundled into your app will affect the generated CSS instead of any files under your folders.</p>
<p>There are a few more tricks we have done to squeeze out even more performance. I might do another post about them later, but before that, you can read the code to figure out :)</p>
<h2>Can I Use it Now?</h2>
<p>For the short answer: Yes, but with cautions.</p>
<p>UnoCSS is still in experiments. But given its simplicity, the generation result is already quite reliable. One thing you need to care about is the APIs are not finalized yet. We will indeed follow semver on releasing, but please expect changes.</p>
<p>It's not designed to be a replacement of Windi CSS or Tailwind (consider waiting for Windi CSS v4). We don't recommend migrating existing projects to UnoCSS completely. You can try it on new projects or use it as a complement along with your existing CSS framework (for example, disable default preset and use the icon preset solely for pure CSS icons, or make your custom rules).</p>
<p>Oh btw, <a href="https://github.com/antfu/antfu.me">the site you are reading</a> is now solely on UnoCSS, for you to reference :P.</p>
<p>Meanwhile, please feel free to share the presets you are making or help contribute to our default presets. We can't wait to see what you can come up with!</p>
<h2>Thanks</h2>
<p>Appreciate the early review and feedback provided by (A-Z):</p>
<ul>
<li><a href="https://github.com/alexanderniebuhr">@alexanderniebuhr</a></li>
<li><a href="https://github.com/ElMassimo">@ElMassimo</a></li>
<li><a href="https://github.com/harlan-zw">@harlan-zw</a></li>
<li><a href="https://github.com/QC-L">@QC-L</a></li>
<li><a href="https://github.com/userquin">@userquin</a></li>
<li><a href="https://github.com/voorjaar">@voorjaar</a></li>
<li><a href="https://github.com/wheatjs">@wheatjs</a></li>
</ul>
<h2>Wrapping Up</h2>
<p>Thanks a lot for reading through! If it ever got you interested, do remember to check out the repo <a href="https://github.com/unocss/unocss"><code>unocss/unocss</code></a> for more details and play with it on the <a href="https://unocss.antfu.me/"><strong>Online Playground</strong></a>.</p>
<p>Please feel free to comment or retweet <a href="https://twitter.com/antfu7/status/1452802545118711812">this tweet</a> letting me know what you think! 🙌</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[New Ways to Vue]]></title>
            <link>https://antfu.me/posts/new-ways-to-vue-london-2021</link>
            <guid isPermaLink="true">https://antfu.me/posts/new-ways-to-vue-london-2021</guid>
            <pubDate>Wed, 20 Oct 2021 08:00:00 GMT</pubDate>
            <description><![CDATA[New Ways to Vue - Vue.js Live London 2021]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Slides: <a href="https://antfu.me/talks/2021-10-20">PDF</a> | <a href="https://talks.antfu.me/2021/vue-london">SPA</a></p>
<p>Recording: <a href="https://youtu.be/CyaJLrqE9tc">YouTube</a></p>
<p>Made with <Slidev class="inline"/> <a href="https://github.com/slidevjs/slidev"><strong>Slidev</strong></a> - presentation slides for developers.</p>
</blockquote>
<YouTubeEmbed id="CyaJLrqE9tc" />
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Range in JavaScript]]></title>
            <link>https://antfu.me/posts/range-in-javascript</link>
            <guid isPermaLink="true">https://antfu.me/posts/range-in-javascript</guid>
            <pubDate>Mon, 13 Sep 2021 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Credit to <a href="https://copilot.github.com/">GitHub Copilot</a>.</p>
<p>I didn't know you could provide a map function to <code>Array.from</code> as a second argument until today.</p>
<pre><code class="language-js">Array.from({ length: 10 }, (_, i) =&gt; i)
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
</code></pre>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Journey with Icons Continues]]></title>
            <link>https://antfu.me/posts/journey-with-icons-continues</link>
            <guid isPermaLink="true">https://antfu.me/posts/journey-with-icons-continues</guid>
            <pubDate>Fri, 10 Sep 2021 18:00:00 GMT</pubDate>
            <description><![CDATA[My journey with icons and the solutions I made along the way]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<div class="flex gap-3 text-lg py-2">
  <tabler:code />
  <tabler:bolt />
  <tabler:triangle-square-circle />
  <tabler:confetti />
</div>
<p>About one year ago, I wrote a blog post <a href="/posts/journey-with-icons">Journey with Icons</a>, sharing the tools I have made for solving my needs on using icons in frontend projects.</p>
<p>During this period, the Vite along its community has evolved quite a lot. The mindsets of Vite have inspired many projects to come up with efficient and innovative solutions.</p>
<p>In this post, I will share the continuation of my journey with icons and the tools I have ended up with so far.</p>
<h2>PurgeIcons &amp; Its Limitations</h2>
<p><a href="https://github.com/antfu/purge-icons">PurgeIcons</a> is my first attempt to improve the loading speed of <a href="https://iconify.design/">Iconify</a> - a united icon library that allows you to use any icons for any framework. The main problem is that it's purely client-side. Even it's flexible to work with any framework, the client-side requests inevitably introduce the flash of missing icons. To solve that, I made PurgeIcons by statically scanning your icon usages and bundle them together with your app, so the Iconify runtime could load them without additional requests.</p>
<p>This solution works, but it only solves the problem partially. As the icons are bundled within JavaScript and functions outside the frameworks, it's not ideal for working with framework-specific features like server-side rendering/generation, props passing, etc. We need to find a better way of doing it.</p>
<h2>The New Solution</h2>
<p>One of the core-concept of Vite is that everything is <strong>on-demand</strong>. Modules get transpiled only when they are being requested. In this way, the Vite server starts immediately without the need to bundle your entire app. Additionally, <a href="https://vitejs.dev/guide/api-plugin.html">Vite's plugin API</a> is an extension on top of <a href="https://rollupjs.org/guide/en/#plugin-development">Rollup's plugin system</a>, which allows you to do some <a href="https://rollupjs.org/guide/en/#transform">custom transformations</a> to the modules.</p>
<p>So, if we think in Vite's way - maybe we could solve this at compile-time instead of client-side! By using <a href="https://vitejs.dev/guide/api-plugin.html#importing-a-virtual-file">virtual modules</a>, I was able to serve the icons as components <strong>on-the-fly</strong> and made it as<br>
<a href="https://github.com/antfu/unplugin-icons"><code>vite-plugin-icons</code></a> (renamed to <code>unplugin-icons</code> later on).</p>
<pre><code class="language-ts">// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    IconsPlugin()
  ]
})

function IconsPlugin() {
  return {
    name: 'vite-plugin-icons',
    // tell Vite that ids start with `~icons/` are virtual files
    resolveId(id) {
      if (id.startsWith('~icons/'))
        return id
      return null
    },
    // custom logic to load the module
    load(id) {
      if (!id.startsWith('~icons/'))
        return
      const [prefix, collection, name] = id.split('/')
      // get icon data from Iconify
      const svg = getIconSVG(collection, name)
      // we compile the SVG as a Vue component
      return Vue3Compiler(svg)
    }
  }
}
</code></pre>
<p>And the usage will be like this:</p>
<pre><code class="language-vue">&lt;script setup&gt;
import FaBeer from '~icons/fa/beer'
import MdiAlarm from '~icons/mdi/alarm'
import TearsOfJoy from '~/icons/twemoji/face-with-tears-of-joy'
&lt;/script&gt;

&lt;template&gt;
  &lt;MdiAlarm /&gt;
  &lt;FaBeer style=&quot;color: orange&quot; /&gt;
  &lt;TearsOfJoy /&gt;
&lt;/template&gt;
</code></pre>
<div class="flex gap-3 p-3">
<MdiAlarm />
<FaBeer style="color: orange"/>
<twemoji:face-with-tears-of-joy />
</div>
<p>You might notice the usages are pretty similar to existing solutions like <a href="https://react-icons.github.io/react-icons/">React Icons</a>. However, most of them approaching this by compiling all the icons into multiple files and distribute them as npm packages. Not only does it ships additional bytes for every icon and increases the time for compilers to parsing them, that also means you are limited to what they have offered exclusively.</p>
<p>With <code>unplugin-icons</code>, you can use any icons available in <a href="https://icones.js.org/">Iconify</a> (which is 100+ icon sets with over 10,000 icons and continue growing) by the following convention:</p>
<pre><code class="language-ts">import Icon from '~icons/[collection]/[name]'
</code></pre>
<p>You can learn more about the installation and usage on <GitHubLink repo="antfu/unplugin-icons" /></p>
<h2>Universal</h2>
<h3>Universal on Icons</h3>
<p>The unification or Icons are already done in Iconify by providing the icons in the same, normalized <a href="https://github.com/iconify/collections-json">JSON format</a>, so what if we could have it more universally available for the tools we loved?</p>
<GitHubLink repo="iconify/collections-json" />
<h3>Universal on Frameworks</h3>
<p>Initially, I was made this plugin only for Vue 3 on Vite. But since we are doing the complication on-demand, I figured out that we could actually apply for different compilers based on the frameworks users use. With that idea, now it supports using icons as components for Vue 3, Vue 2, React, Preact and Solid! (Contributions to add more is great welcome!)</p>
<pre><code class="language-ts">function Vue3Compiler(svg) { /* ... */ }
function Vue2Compiler(svg) { /* ... */ }
function JSXCompiler(svg) { /* ... */ }
function SolidCompiler(svg) { /* ... */ }
// ...add more!

function IconsPlugin({ compiler }) {
  return {
    name: 'vite-plugin-icons',
    resolveId(id) { /* ... */ },
    load(id) {
      /* ... */
      // we could apply different compilers here as needed
      return compiler(SVG)
    }
  }
}
</code></pre>
<p>With this, you can have it working in React like:</p>
<pre><code class="language-jsx">import FaBeer from '~icons/fa/beer'
import MdiAlarm from '~icons/mdi/alarm'
import TearsOfJoy from '~/icons/twemoji/face-with-tears-of-joy'

export function MyComponent() {
  return (
    &lt;&gt;
      &lt;MdiAlarm /&gt;
      &lt;FaBeer style=&quot;color: orange&quot; /&gt;
      &lt;TearsOfJoy /&gt;
    &lt;/&gt;
  )
}
</code></pre>
<h3>Universal on Build Tools</h3>
<p>In the past few weeks, I have joined <a href="https://nuxtlabs.com/">NuxtLabs</a> and worked on a universal plugin layer for our various bundling tools - <GitHubLink repo="unjs/unplugin" />. It allows you to use a unified plugin API to write plugins for Vite, Webpack, Rollup, Nuxt, Vue CLI, and more only once. To make it work, all we need to do is to change our code like:</p>
<pre><code class="language-ts">export function VitePluginIcons() {
  return {
    name: 'vite-plugin-icons',
    resolveId(id) { /* ... */ },
    load(id) { /* ... */ }
  }
}
</code></pre>
<pre><code class="language-ts">import { createUnplugin } from 'unplugin'

const unplugin = createUnplugin(() =&gt; {
  return {
    name: 'unplugin-icons',
    resolveId(id) { /* ... */ },
    load(id) { /* ... */ }
  }
})

// Use unplugin to generate plugins for different build tools
export const VitePluginIcons = unplugin.vite
export const WebpackPluginIcons = unplugin.webpack
export const RollupPluginIcons = unplugin.rollup
</code></pre>
<p>That's cool. With it, you don't need to learn each frameworks' plugin API and publish them in multiple packages - now you got one package for all of them!</p>
<GitHubLink repo="unjs/unplugin" />
<h3>Universal Solution</h3>
<p>With all the effort above, I converted my <code>vite-plugin-icons</code>, a Vite + Vue 3 specific icon plugin, to <code>unplugin-icons</code> as a universal icons solution.</p>
<p>For what I mean universal, I mean literally, you can use:</p>
<ul class="children:my-auto">
<li><span><logos:vue class="inline"/> Vue 3 + <vite-logo class="inline"/> Vite + <carbon:carbon class="inline"/> <a href="https://carbondesignsystem.com/guidelines/icons/library/" target="_blank">Carbon Icons</a></span></li>
<li><span><logos:react class="inline"/> React + <logos:nextjs-icon class="inline filter dark:invert"/> Next.js + <mdi:material-design class="inline"/> <a href="https://materialdesignicons.com/" target="_blank">Material Design Icons</a></span></li>
<li><span><logos:vue class="inline"/> Vue 2 + <logos:nuxt-icon class="inline"/> Nuxt.js + <uim:circle-layer class="inline"/> <a href="https://iconscout.com/unicons" target="_blank">Unicons</a></span></li>
<li><span><logos:preact class="inline"/> React + <logos:webpack class="inline"/> Webpack + <twemoji:star-struck class="inline"/> <a href="https://github.com/twitter/twemoji" target="_blank">Twemoji</a></span></li>
<li><span><solid-logo class="inline"/> Solid + <vite-logo class="inline"/> Vite + <tabler-writing-sign class="inline"/> <a href="https://tabler-icons.io/" target="_blank">Tabler</a></span></li>
<li><span><logos:javascript class="inline"/> Vanila + <logos:rollup class="inline"/> Rollup + <bx:bx-planet class="inline"/> <a href="https://github.com/atisawd/boxicons" target="_blank">BoxIcons</a></span></li>
<li><span><logos:webcomponents class="inline"/> Web Components + <vite-logo class="inline"/> Vite + <ant-design:carry-out-twotone class="inline"/> <a href="https://github.com/ant-design/ant-design-icons" target="_blank">Ant Design Icons</a></span></li>
<li><span><logos:svelte-icon class="inline"/> Svelte + <logos:svelte-icon class="inline"/> SvelteKit + <eos-icons:installing class="inline"/> <a href="https://gitlab.com/SUSE-UIUX/eos-icons" target="_blank">EOS Icons</a></span></li>
<li><span><line-md:question-circle class="inline"/> Any + <mdi:progress-question class="inline"/> Any + <ph:circle-wavy-question-duotone class="inline"/> Any</span></li>
</ul>
<p>...really, you made the combinations!</p>
<p>Get it now 👇</p>
<GitHubLink repo="antfu/unplugin-icons" />
<h2>One More Thing</h2>
<p>Oh, you are still here. So I guess you are looking for something even further.</p>
<p>As you might notice, whenever you want to use an icon, you need to import it first, name it, and then use it. In this case, the icon name has been repeated at least three times. For example:</p>
<h6>Vue</h6>
<pre><code class="language-vue">&lt;script setup&gt;
import MdiAlarm from '~icons/mdi/alarm'
&lt;/script&gt;

&lt;template&gt;
  &lt;MdiAlarm /&gt;
&lt;/template&gt;
</code></pre>
<h6>React</h6>
<pre><code class="language-jsx">import MdiAlarm from '~icons/mdi/alarm'

export function MyComponent() {
  return (
    &lt;div&gt;
      &lt;MdiAlarm /&gt;
    &lt;/div&gt;
  )
}
</code></pre>
<p>So yes, we might need a better way to do this.</p>
<h3>Auto-importing</h3>
<p>Inspired by <GitHubLink repo="nuxt/components" /> which registers components under your <code>./components</code> directory automatically, I made <GitHubLink repo="antfu/unplugin-vue-components" /> (yes, another unplugin!) do to compile-time components auto-importing on-demand. With the on-demand natural, we could even make the components resolving on-demand. What a perfect complement for our icon solution!</p>
<p><code>unplugin-vue-components</code> provide the options <code>resolvers</code> to provide custom functions to resolve where the components should be imported from.</p>
<p>Here is an example configuration for Vite (since both of them are unplugins, you can also use them for Webpack and Rollup):</p>
<pre><code class="language-ts">import IconsResolver from 'unplugin-icons/resolver'
import Icons from 'unplugin-icons/vite'
import Components from 'unplugin-vue-components/vite'
// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    /* ... */
    Icons(),
    Components({
      resolvers: [
        IconsResolver({
          // to avoid naming conflicts
          // a prefix can be specified for icons
          prefix: 'i'
        })
      ]
    })
  ]
})
</code></pre>
<p>Then we can use them directly in our templates, no more imports and repeats (and you can change the icons much easier as you don't need to update in three places):</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;!-- both PascalCase and dash-case are supported by Vue --&gt;
  &lt;IMdiAlarm /&gt;
  &lt;i-fa-beer style=&quot;color: orange&quot; /&gt;
&lt;/template&gt;
</code></pre>
<p>Isn't it perfect?!</p>
<p>Learn more: <GitHubLink repo="antfu/unplugin-vue-components" /></p>
<blockquote>
<p>Auto-import integrations for <code>@nuxt/components</code> is in progress.</p>
</blockquote>
<h4>Auto-importing for JSX</h4>
<p>Oh, I almost forgot about it. Since JSX is more like plain JavaScript in some ways and JSX components are just functions or classes, the thing is actually a bit simpler. For that, we can use another unplugin I made - <GitHubLink repo="antfu/unplugin-auto-import" />.</p>
<p>For some background here, <code>unplugin-auto-import</code> is a compile-time successor of <GitHubLink repo="antfu/vue-global-api" /> to improve DX of Vue Composition API (directly use of <code>ref</code>, <code>computed</code>, etc.).</p>
<p>With the expansion to a general auto-importing solution for any API sets, it's also possible to do auto-importing for JSX components. in <code>unplugin-auto-import</code>, we implement the same resolver interface for it.</p>
<pre><code class="language-ts">import AutoImport from 'unplugin-auto-import/vite'
import IconsResolver from 'unplugin-icons/resolver'
import Icons from 'unplugin-icons/vite'
// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    /* ... */
    Icons({
      compiler: 'jsx'
    }),
    AutoImport({
      imports: [
        'react' // preset for react
      ],
      resolvers: [
        IconsResolver({
          prefix: 'Icon',
          extension: 'jsx'
        })
      ]
    })
  ]
})
</code></pre>
<p>Here is your React component, and you are welcome :)</p>
<pre><code class="language-tsx">export function MyComponent() {
  return (
    &lt;&gt;
      &lt;IconMdiAlarm /&gt;
      &lt;IconFaBeer style=&quot;color: orange&quot; /&gt;
    &lt;/&gt;
  )
}
</code></pre>
<h2>Recap</h2>
<p>For a quick summary, here is the list of projects mentioned for these solutions:</p>
<ul>
<li><GitHubLink repo="unjs/unplugin" /> - Unified plugin system for Vite, Rollup, Webpack, and more.</li>
<li><GitHubLink repo="antfu/unplugin-icons" /> - Access thousands of icons as components on-demand.</li>
<li><GitHubLink repo="antfu/unplugin-vue-components" /> - On-demand components auto importing.</li>
<li><GitHubLink repo="antfu/unplugin-auto-import" /> - Auto import APIs on-demand.</li>
</ul>
<p>Meanwhile, you might also find these tools from my last journey helpful:</p>
<ul>
<li><GitHubLink repo="antfu/icones" /> - Icon Explorer for Iconify with Instant searching and exporting.</li>
<li><GitHubLink repo="antfu/vscode-iconify" /> - Iconify IntelliSense for VS Code.</li>
</ul>
<p>If you enjoy them, you might also want to check my Vue + Vite starter template with them configured in-box.</p>
<ul>
<li><GitHubLink repo="antfu/vitesse" /> - Opinionated Vite Starter Template.</li>
<li><GitHubLink repo="antfu/vitesse-lite" /> - Lightweight version of Vitesse.</li>
<li><GitHubLink repo="antfu/vitesse-webext" /> - WebExtension Vite Starter Template.</li>
<li><GitHubLink repo="antfu/vitesse-nuxt" /> - Vitesse experience for Nuxt 2 and Vue 2.</li>
</ul>
<p>Again, thanks for reading through :)</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Isomorphic `__dirname`]]></title>
            <link>https://antfu.me/posts/isomorphic-dirname</link>
            <guid isPermaLink="true">https://antfu.me/posts/isomorphic-dirname</guid>
            <pubDate>Mon, 30 Aug 2021 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In ESM, you might found your old friends <code>__dirname</code> and <code>__filename</code> are no longer available. When you search for <a href="https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-when-using-the-experimental-modules-flag">solutions</a>, you will find that you will need to parse <code>import.meta.url</code> to get the equivalents. While most of the solutions only show you the way to get them in ESM only, If you like me, who write modules in TypeScript and transpile to both CJS and ESM at the same time using tools like <a href="https://tsup.egoist.dev/"><code>tsup</code></a>. Here is the isomorphic solution:</p>
<pre><code class="language-js">import { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'

const _dirname = typeof __dirname !== 'undefined'
  ? __dirname
  : dirname(fileURLToPath(import.meta.url))
</code></pre>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[GitHub Co-authors]]></title>
            <link>https://antfu.me/posts/github-co-authors</link>
            <guid isPermaLink="true">https://antfu.me/posts/github-co-authors</guid>
            <pubDate>Tue, 24 Aug 2021 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>You might found GitHub sometimes shows you a commit with multiple authors. This is commonly happening in squashed pull requests when multiple people are involved with the reviewing and made suggestions or changes. In that situation, GitHub will automatically inject the <a href="https://docs.github.com/en/github/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors"><code>Co-authored-by:</code></a> to the commit message. This is a great way to give contributors credits while keeping the commit history clean.</p>
<p>Note that the format is like <code>Co-authored-by: name &lt;name@example.com&gt;</code>, normally GitHub will fill that for you so you don't need to worry about that, but if you want to add it manually, you have to get the email addresses of the contributors. But how do you know their emails?</p>
<p>Well, technically you can indeed find their email by multiple ways, but actually, you don't need to. The easiest way is to copy their user id and append with <a href="https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-user-account/managing-email-preferences/setting-your-commit-email-address#about-commit-email-addresses"><code>@users.noreply.github.com</code></a> that provided by GitHub automatically, for example:</p>
<pre><code>Co-authored-by: antfu &lt;antfu@users.noreply.github.com&gt;
</code></pre>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Async with Composition API]]></title>
            <link>https://antfu.me/posts/async-with-composition-api</link>
            <guid isPermaLink="true">https://antfu.me/posts/async-with-composition-api</guid>
            <pubDate>Fri, 16 Jul 2021 08:00:00 GMT</pubDate>
            <description><![CDATA[Notes about the caveat when using async functions in Vue Composition API.]]></description>
            <content:encoded><![CDATA[<p>There is a major caveat when working with asynchronous functions in Vue Composition API, that I believe many of you have ever come across. I have acknowledged it for a while from somewhere, but every time I want to have a detailed reference and share to others, I can't find it's documented anywhere. So, I am thinking about writing one, with a detailed explanation while sorting out the possible solutions for you.</p>
<ul>
<li><a href="#the-problem">The Problem</a></li>
<li><a href="#the-mechanism">The Mechanism</a></li>
<li><a href="#the-limitation">The Limitation</a></li>
<li><a href="#the-solutions">The Solutions</a></li>
</ul>
<h2>The Problem</h2>
<p>When using asynchronous <code>setup()</code>, <strong>you have to use effects and lifecycle hooks before the first <code>await</code> statement.</strong> (<a href="https://github.com/vuejs/rfcs/discussions/234">details</a>)</p>
<p>For example:</p>
<pre><code class="language-ts">import { onMounted, onUnmounted, ref, watch } from 'vue'

export default defineAsyncComponent({
  async setup() {
    const counter = ref(0)

    watch(counter, () =&gt; console.log(counter.value))

    // OK!
    onMounted(() =&gt; console.log('Mounted'))

    // the await statement
    await someAsyncFunction() // &lt;-----------

    // does NOT work!
    onUnmounted(() =&gt; console.log('Unmounted'))

    // still works, but does not auto-dispose
    // after the component is destroyed (memory leak!)
    watch(counter, () =&gt; console.log(counter.value * 2))
  }
})
</code></pre>
<p>After the <code>await</code> statement,</p>
<p>the following functions will be <strong>limited</strong> (no auto-dispose):</p>
<ul>
<li><code>watch</code> / <code>watchEffect</code></li>
<li><code>computed</code></li>
<li><code>effect</code></li>
</ul>
<p>the following functions will <strong>not work</strong>:</p>
<ul>
<li><code>onMounted</code> / <code>onUnmounted</code> / <code>onXXX</code></li>
<li><code>provide</code> / <code>inject</code></li>
<li><code>getCurrentInstance</code></li>
<li>...</li>
</ul>
<h2>The Mechanism</h2>
<p>Let's take the <code>onMounted</code> API as an example. As we know, <code>onMounted</code> is a hook that registers a listener when the current component gets mounted. Notice that <code>onMounted</code> (along with other composition APIs) are <strong>global</strong>, for what I mean &quot;global&quot; is that it can be imported and called anywhere - there is <strong>no local context</strong> bound to it.</p>
<pre><code class="language-ts">// local: `onMounted` is a method of `component` that bound to it
component.onMounted(/* ... */)

// global: `onMounted` can be called without context
onMounted(/* ... */)
</code></pre>
<p>So, how does <code>onMounted</code> know what component is being mounted?</p>
<p>Vue takes an interesting approach to solve this. It uses an internal variable to record the current component instance. There is a simplified code:</p>
<p>When Vue mounts a component, it stores the instance in a global variable. When hooks been called inside the setup function, it will use the global variable to get the current component instance.</p>
<pre><code class="language-js">let currentInstance = null

// (pseudo code)
export function mountComponent(component) {
  const instance = createComponent(component)

  // hold the previous instance
  const prev = currentInstance

  // set the instance to global
  currentInstance = instance

  // hooks called inside the `setup()` will have
  // the `currentInstance` as the context
  component.setup()

  // restore the previous instance
  currentInstance = prev
}
</code></pre>
<p>A simplified <code>onMounted</code> implementation would be like:</p>
<pre><code class="language-js">// (pseudo code)
export function onMounted(fn) {
  if (!currentInstance) {
    warn(`&quot;onMounted&quot; can't be called outside of component setup()`)
    return
  }

  // bound listener to the current instance
  currentInstance.onMounted(fn)
}
</code></pre>
<p>With this approach, as long as the <code>onMounted</code> is called inside the component <code>setup()</code>, it will be able to get the instance of the current component.</p>
<h2>The Limitation</h2>
<p>So far so good, but what's wrong with asynchronous functions?</p>
<p>The implementation would work based on the fact that JavaScript is <strong>single-threaded</strong>. Single thread makes sure the following statements will be executed right next to each other, which in other words, there is no one could accidentally modify the <code>currentInstance</code> at the same time (a.k.a. it's <a href="https://stackoverflow.com/questions/52196678/what-are-atomic-operations-for-newbies">atomic</a>).</p>
<pre><code class="language-ts">currentInstance = instance
component.setup()
currentInstance = prev
</code></pre>
<p>The situation changes when the <code>setup()</code> is asynchronous. Whenever you <code>await</code> a promise, you can think the engine paused the works here and went to do another task. If we <code>await</code> the function, during the time period, multiple components creation will change the global variable unpredictably and end up with a mess.</p>
<pre><code class="language-ts">currentInstance = instance
await component.setup() // atomic lost
currentInstance = prev
</code></pre>
<p>If we don't use <code>await</code> to check the instance, calling the <code>setup()</code> function will make it finish the tasks before the first <code>await</code> statement, and the rest will be executed whenever the <code>await</code> statement is resolved.</p>
<div class="grid grid-cols-2 gap-2 lt-sm:grid-cols-1">
<pre><code class="language-ts">async function setup() {
  console.log(1)
  await someAsyncFunction()
  console.log(2)
}

console.log(3)
setup()
console.log(4)
</code></pre>
<!-- eslint-skip -->
<pre><code class="language-ts">// output:
3
1
4
(awaiting)
2
</code></pre>
</div>
<p>This means, there is no way for Vue to know when will the asynchronous part been called from the outside, so there is also no way to bound the instance to the context.</p>
<h2>The Solutions</h2>
<p>This is actually a limitation of JavaScript itself, unless we have some new proposal to open the gate on the language level, we have to live with it.</p>
<p>But to work around it, I have collected a few solutions for you to choose from based on your needs.</p>
<h3>Remember the Caveat and Avoid It</h3>
<p>This is, of course, an obvious &quot;solution&quot;. You can try to move your effect and hooks before the first <code>await</code> statement and carefully remember not to have them after that again.</p>
<p>Luckily, if you are using ESLint, you can have the <a href="https://eslint.vuejs.org/rules/no-watch-after-await.html"><code>vue/no-watch-after-await</code></a> and <a href="https://eslint.vuejs.org/rules/no-lifecycle-after-await.html"><code>vue/no-lifecycle-after-await</code></a> rules from <a href="https://eslint.vuejs.org/"><code>eslint-plugin-vue</code></a> enabled so it could warn you whenever you made some mistakes (they are enabled by default within the plugin presets).</p>
<h3>Wrap the Async Function as &quot;Reactive Sync&quot;</h3>
<p>In some situations, your logic might be relying on the data that fetched asynchronously. In this way, you could consider using the <a href="/posts/composable-vue-vueday-2021#async-to-sync">trick I have shared on VueDay 2021</a> to <strong>turn your async function into a sync reactive state</strong>.</p>
<pre><code class="language-ts">const data = await fetch('https://api.github.com/').then(r =&gt; r.json())

const user = data.user
</code></pre>
<pre><code class="language-ts">const data = ref(null)

fetch('https://api.github.com/')
  .then(r =&gt; r.json())
  .then(res =&gt; data.value = res)

const user = computed(() =&gt; data?.user)
</code></pre>
<p>This approach make the &quot;connections&quot; between your logic to resolve first, and then reactive updates when the asynchronous function get resolved and filled with data.</p>
<p>There is also some more general utilities for it from <a href="https://vueuse.org/">VueUse</a>:</p>
<h4><a href="https://vueuse.org/useAsyncState"><code>useAsyncState</code></a></h4>
<pre><code class="language-ts">import { useAsyncState } from '@vueuse/core'

const { state, ready } = useAsyncState(async () =&gt; {
  const { data } = await axios.get('https://api.github.com/')
  return { data }
})

const user = computed(() =&gt; state?.user)
</code></pre>
<h4><a href="https://vueuse.org/useFetch"><code>useFetch</code></a></h4>
<pre><code class="language-ts">import { useFetch } from '@vueuse/core'

const { data, isFetching, error } = useFetch('https://api.github.com/')

const user = computed(() =&gt; data?.user)
</code></pre>
<h3>Explicitly Bound the Instance</h3>
<p>Lifecycle hooks actually accept a second argument for setting the instance explicitly.</p>
<pre><code class="language-ts">export default defineAsyncComponent({
  async setup() {
    // get and hold the instance before `await`
    const instance = getCurrentInstance()

    await someAsyncFunction() // &lt;-----------

    onUnmounted(
      () =&gt; console.log('Unmounted'),
      instance // &lt;--- pass the instance to it
    )
  }
})
</code></pre>
<p>However, the downside is that this solution <strong>does not work</strong> with <code>watch</code> / <code>watchEffect</code> / <code>computed</code> / <code>provide</code> / <code>inject</code> as they does not accept the instance argument.</p>
<p>To get the effects work, you could use the <a href="https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md"><code>effectScope</code> API</a> in the upcoming Vue 3.2.</p>
<pre><code class="language-ts">import { effectScope } from 'vue'

export default defineAsyncComponent({
  async setup() {
    // create the scope before `await`, so it will be bond to the instance
    const scope = effectScope()

    const data = await someAsyncFunction() // &lt;-----------

    scope.run(() =&gt; {
      /* Use `computed`, `watch`, etc. ... */
    })

    // the lifecycle hooks will not be available here,
    // you will need to combine it with the previous snippet
    // to have both lifecycle hooks and effects works.
  }
})
</code></pre>
<h3>Compile-time Magic!</h3>
<p>In the recent <a href="https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md"><code>&lt;script setup&gt;</code> proposal</a> update, a new <a href="https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md#top-level-await">compile-time magic</a> is introduced.</p>
<p>The way it works is to inject a script after each <code>await</code> statement for restoring the current instance state.</p>
<pre><code class="language-html">&lt;script setup&gt;
  const post = await fetch(`/api/post/1`).then((r) =&gt; r.json())
&lt;/script&gt;
</code></pre>
<pre><code class="language-js">import { withAsyncContext } from 'vue'

export default {
  async setup() {
    let __temp, __restore

    const post
      = (([__temp, __restore] = withAsyncContext(() =&gt;
        fetch(`/api/post/1`).then(r =&gt; r.json())
      )),
      (__temp = await __temp),
      __restore(),
      __temp)

    // current instance context preserved
    // e.g. onMounted() will still work.

    return { post }
  }
}
</code></pre>
<p>With it, the async functions will <strong>just work</strong> when using with <code>&lt;script setup&gt;</code>. The only shame is it does not work outside of <code>&lt;script setup&gt;</code>.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Get Package Root]]></title>
            <link>https://antfu.me/posts/get-package-root</link>
            <guid isPermaLink="true">https://antfu.me/posts/get-package-root</guid>
            <pubDate>Wed, 14 Jul 2021 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>When you want to get the real file path of a certain package, you could use <code>require.resolve</code> to fetch their main entry path.</p>
<pre><code class="language-bash">&gt; require.resolve('vite')
'/Users/.../node_modules/vite/dist/node/index.js'

&gt; require.resolve('windicss')
'/Users/.../node_modules/windicss/index.js'
</code></pre>
<p>However, when you want to get the root directory of the package, you will find the result of <code>require.resolve</code> could vary based on different packages' configurations.</p>
<p>A trick for this is to resolve the <code>package.json</code> instead, as the <code>package.json</code> is always located at the root of the package. Combining with <code>path.dirname</code>, you could always get the package root.</p>
<pre><code class="language-bash">&gt; path.dirname(require.resolve('vite/package.json'))
'/Users/.../node_modules/vite'
</code></pre>
<p>Update: or you can use my package <a href="https://github.com/antfu/local-pkg"><code>local-pkg</code></a> now :)</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Optimize Await]]></title>
            <link>https://antfu.me/posts/optimize-await</link>
            <guid isPermaLink="true">https://antfu.me/posts/optimize-await</guid>
            <pubDate>Thu, 01 Jul 2021 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><code>async</code> / <code>await</code> in ES7 is truly a life-saver for the JavaScript world. It allows you to avoid <a href="http://callbackhell.com/">callback hell</a> in your code and make it more readable. However, a common pitfall is that when you <code>await</code> a huge asynchronous task that takes very long time, it blocks the following code and could potentially make your app slow.</p>
<p>For example:</p>
<pre><code class="language-ts">const app = await createServer()
const middlewareA = await resolveMiddlewareA()
const middlewareB = await resolveMiddlewareB()

app.use(middlewareA)
app.use(middlewareB)
</code></pre>
<p>We have used three <code>await</code> in the example, while the three async function does not actually relying on each other, having them sequentially we are possibility wasted some time of the operations that could be parallelized (IO, Network, etc.)</p>
<p>So we can use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all"><code>Promise.all</code></a> to optimize the code:</p>
<pre><code class="language-ts">const [app, middlewareA, middlewareB] = await Promise.all(
  [
    createServer(),
    resolveMiddlewareA(),
    resolveMiddlewareB(),
  ],
)

app.use(middlewareA)
app.use(middlewareB)
</code></pre>
<p>In another example, you might relying on the async result, but sometime not that urgent:</p>
<pre><code class="language-ts">async function createPlugin() {
  const toolkit = await initToolKit()

  return {
    onHookA() {
      toolkit.invokeA()
    },
    onHookB() {
      toolkit.invokeB()
    },
  }
}

const plugin = await createPlugin()
</code></pre>
<p>Even though you don't need <code>toolkit</code> immediately, you are still forced to use <code>async function</code> because the <code>initToolKit</code> is async. To avoid this, we could make the promise been resolved in the hooks instead</p>
<pre><code class="language-ts">function createPlugin() {
  const toolkitPromise = initToolKit()

  return {
    async onHookA() {
      const toolkit = await toolkitPromise
      toolkit.invokeA()
    },
    async onHookB() {
      const toolkit = await toolkitPromise
      toolkit.invokeB()
    },
  }
}

// now it's sync!
const plugin = createPlugin()
</code></pre>
<p>Since a Promise could only <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#description">be resolved once</a>, using multiple <code>await</code> for a single Promise instance is <a href="https://blog.ashleygrant.com/2020/04/30/resolved-javascript-promises-can-be-used-multiple-times/">totally fine</a> - it will return the resolved result immediate if the Promise is allready settled.</p>
<p>To be more generalized, we could have an utility function like:</p>
<pre><code class="language-ts">export function createSingletonPromise&lt;T&gt;(fn: () =&gt; Promise&lt;T&gt;) {
  let _promise: Promise&lt;T&gt; | undefined

  return () =&gt; {
    if (!_promise)
      _promise = fn()
    return _promise
  }
}
</code></pre>
<p>This function is also available in my utilities collection <a href="https://github.com/antfu/utils"><code>@antfu/utils</code></a></p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[About Yak Shaving]]></title>
            <link>https://antfu.me/posts/about-yak-shaving</link>
            <guid isPermaLink="true">https://antfu.me/posts/about-yak-shaving</guid>
            <pubDate>Wed, 19 May 2021 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<blockquote>
<p><a href="/posts/about-yak-shaving-zh">中文原文 Original in Chinese</a></p>
</blockquote>
<p>I recently visited Zhihu occasionally and saw many questions about how to start open source, or how to make open source projects successful. I kinda had similar doubts for a long time, so I thought that maybe I could share some of my rough views on this.</p>
<p>If you don't know me, I am a team member of Vue, Vite, wenyan-lang, WindiCSS, Intlify, and the author of VueUse, Slidev, Type Challenges and i18n Ally. I also have some small open source tools under my personal GitHub account, you can have a look at my <a href="https://antfu.me/projects">full project list</a>. Since I started doing open source in earnest almost two years ago, even though those contributions aren't that impressive, they still managed to allow me to <a href="https://twitter.com/antfu7/status/1362676666221268995">work full-time on open source development and maintenance through sponsorships</a>.</p>
<p>Many people probably gonna tell you that the success of a project depends on opportunity, marketing, branding, or documentation, ecosystem, technical innovation, code quality, etc. All of these are indeed important, but for me, the most important thing is the motivation to start a project and the drive to do it well. For me, the best way to get the power is via Yak Shaving.</p>
<h2>Yak Shaving</h2>
<p><a href="https://americanexpress.io/yak-shaving">Yak Shaving</a> refers to a series of actions when you're working on one task and then you find another task that's not finished, you tackle that one first, and while you're working on that one, you find another task to do... and so forth, so that you stray from the work that should have been done, and end up not getting nothing finished. Here is a real-world example:</p>
<blockquote>
<p>You want to bake an apple pie, so you head to the kitchen.<br><br>
In the hallway, you notice some paint chipping on the wall.<br><br>
So you walk to the hardware store for some paint.<br><br>
On the way, you pass a bakery and stop in for a cupcake.<br><br>
While eating the cupcake, you feel a pain in your mouth. It’s that cavity that you’ve been putting off.<br><br>
You pick up your phone to call the dentist to make an appointment, but you see a notification from your friend Cher, who’s having a party.<br><br>
You don’t want to show up empty-handed, so you stop for a bottle of wine…</p>
</blockquote>
<p>An example that more relevant to developers might be: You planned to write a blog today, but you found out none of the existing tools are good enough for you. Then you spend a month writing your own static website generator, but end up with the generator unfinished and forgetting about writing the blog.</p>
<p>I guess we all had similar experiences more or less. Yak Shaving usually refers to something negative, emphasizing inattentiveness or lack of clarity of purpose. But I kinda think it's also an important source of motivation for many things. When a person needs a tool, they are most motivated to solve it and make it happen. I, not coincidentally, am an obsessive Yak Shaving fan.</p>
<p>Maybe I'll share some of my stories with you to give you a better idea of what I'm trying to express:</p>
<h2>The story of how I started doing open source</h2>
<p>In my senior year of college, I went on a graduation trip to the Philippines with a group of college friends. Because of the various problems of exchanging foreign currency, an operation down to Taiwan dollars, US dollars, Philippine pesos and different exchange rates each time, making the record of public accounts and settlement very complicated. After we came back from the trip, we came up with the idea of making an app to solve this problem.</p>
<p>To make the app reach a large enough audience, multilingual internationalization is something we have to consider. As there are many foreign language departments in our college, we thought we could use our resources to get our friends to help on translating the App into multiple languages. However, it is obviously unrealistic to have foreign language students write JSON with bare hands, so we had to find something a little easier. Luckily, I found <a href="https://github.com/think2011/vscode-vue-i18n"><code>think2011/vscode-vue-i18n</code></a>, which looks great, but lacks some features we needed. So I contacted the author and got fork permission, and then, here comes the <a href="https://github.com/lokalise/i18n-ally">i18n Ally</a> project.</p>
<p>The later stage of App development coincided with the Composition API RFC of Vue 3. The new API seemed to solve many of the pain points in our development. In the spirit of experimentation, we installed the Vue 2 plugin and started to try it out. In the process of using it, we found out there are quite some functions we are commonly used, and also inspired by <a href="https://github.com/streamich/react-use"><code>react-use</code></a>, I extracted them out and made <a href="https://github.com/vueuse/vueuse">VueUse</a>.</p>
<p>Given that Vue 3 was still in Alpha at the time, and the community needs to gradually migrate from Vue 2 to Vue 3 for a long time in the future. I made VueUse intentionally as a universal library for Vue 2 and Vue 3 so that people could migrate seamlessly. The initial solution was to publish two packages for Vue 2 and 3 under different npm tags. As Vue 3 matured, more and more libraries wanted to go the same way to reduce the cost of maintaining two codebases at the same time. Then I thought, maybe I can find a general solution from VueUse, so that everyone could get benefit from it. And then, <a href="https://github.com/vueuse/vue-demi"><code>vue-demi</code></a> comes out. As a result, it also allows VueUse to publish one version that supports both Vue 2 and 3 at the same time.</p>
<p>VueUse's support for Vue 2 relies on the <a href="https://github.com/vuejs/composition-api"><code>@vue/composition-api</code></a> library, at some point, there are some inconsistencies in the plugin with the latest Vue 3 changes. Which results in VueUse's development being hampered. After a quite long time of no response to the PR from the repository, I thought I might be able to help out a bit, so I posted <a href="https://github.com/vuejs/composition-api/issues/343">an issue</a> in the repository saying I would like to volunteer myself maintaining the project. And that's also the opportunity for me to join the Vue team.</p>
<p>In the end, our App didn't work out, but I gained a lot of valuable experience solving problems and working on open source projects along the way. i18n Ally started out as a vue-i18n specific extension and now supports over 20 major frameworks, with over 60,000 downloads of VS Code. VueUse started as a simple toolset and now it becomes a GitHub Organization with 10 members and 8 ecosystem packages.</p>
<p>I could probably tell stories like these for a whole day, and behind almost every project there is such a motivation to try to solve a certain problem. After all of this harangue, the point I'm trying to make is that Yak Shaving can be a great engine for progressing when used properly.</p>
<p>And here are my methods of how to make Yak Shaving a good thing:</p>
<h2>Shave the Good Yak</h2>
<h3>Identify Problems</h3>
<p>I spent every day of my four years in college thinking if I could make an interesting open source project that everyone needed and live as a full-time open sourceror with freedom. The difference is that the four years I was thinking about how to make something that other people wanted, but later I was solving <strong>the actual problems that I encountered</strong>. As said, when you need a tool, you have the most motivation to make it. And also as a user, you know the best where the pain points and needs are. When you encounter this problem, others might have just encountered a similar one.</p>
<h3>Solve the Problem</h3>
<p>The most basic rule to start trying to solve a problem is to look for existing solutions, if the problem has been well solved, which meets your all needs, then just use it. Reinventing wheels might be a good way to learn, but since the wheels are already there, there has to be someone thinking about how to build a car, right?</p>
<p>When you find that there is no solution to the problem, or that the existing solutions don't work for you, while you have a great idea in your mind. Congratulations, you've found a great Yak.</p>
<h3>Good Enough</h3>
<p>The most important part of making Yak Shaving great is to <strong>just be good enough</strong> -- do not have too much expectation, if the idea is verified feasible, make it just good enough; if the idea does now work, don't be discouraged, just throw it away, maybe one day you can pick it back up again with new ideas. It's not necessary to be perfect at the beginning. You don't have to draw a grand blueprint or plan, you don't have to set a huge goal of how many stars or how many users - you are doing it for yourself, and make it just good enough to solve your own problems. The important thing is to not spending too much time on a single idea, and get back to what you should have been doing in time.</p>
<h3>Refine the Project</h3>
<p>As the first user of your own product, you will find a lot of things for improvement in the process of using it, go and modify it from time to time and do some improvements. If you got more time, you can add a README describing the problems you encountered and the motivation for doing the project, which may be helpful to someone who faced similar problems.</p>
<p>In the end, when using the project makes you feel that it's a pretty good idea, while you have completed the work that should have been done, you may wish to spend some time writing a document, improve the implementations, and promote a little. It's best if it has been recognized, but if not, just treat it as an exercise, and at least you've your own problems solved. If the responses went well, then someone will start to raise issues and send PRs. Which more enhancements and features coming, you will also gradually find the future direction of the project. Besides that, those changes and enhancements from the community may end up being a better solution to the problems you encountered at the beginning.</p>
<h3>Identify More Problems</h3>
<p>The way to find more problems is simple, learn more and try more. In the process of solving and improving the problem, you will likely find new problems that could potentially be solved. Issues from the community can also help you find more inspiration. Anyway, congratulations on entering the positive cycle!</p>
<h2>Wrapping Up</h2>
<p>Hopefully, this insight could give you some inspiration on solving your own problems, or making a good open source product, in one way or another.</p>
<p>I also recommend some awesome Yak Shaving masters, maybe their projects and experiences can give you some inspiration as well:</p>
<ul>
<li><a href="https://github.com/sindresorhus">Sindre Sorhus</a> - actively maintains 1100+ npm packages, Webpack and Babel both rely on 100+ of his packages</li>
<li><a href="https://github.com/tj">TJ Holowaychuk</a> - Author of koa, mocha, express, etc.</li>
<li><a href="https://github.com/lukeed">Luke Edwards</a> - Author of polka, uvu, klona, etc.</li>
<li><a href="https://github.com/egoist">Egoist</a> - Author of poi, cac, saber, etc.</li>
<li><a href="https://github.com/privatenumber">Hiroki Osame</a> - Author of esbuild-loader, vue-2-3, etc.</li>
</ul>
<p>Cheers, and happy hacking!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Composable Vue]]></title>
            <link>https://antfu.me/posts/composable-vue-vueday-2021</link>
            <guid isPermaLink="true">https://antfu.me/posts/composable-vue-vueday-2021</guid>
            <pubDate>Wed, 28 Apr 2021 16:00:00 GMT</pubDate>
            <description><![CDATA[Slides & transcript for my talk at VueDay 2021]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<blockquote>
<p>This is the transcript of my talk <strong>Composable Vue</strong> at <a href="https://2021.vueday.it/">VueDay 2021</a></p>
<p>Slides: <a href="https://antfu.me/talks/2021-04-29">PDF</a> | <a href="https://talks.antfu.me/2021/composable-vue">SPA</a></p>
<p>Recording: <a href="https://youtu.be/IMJjP6edHd0">YouTube</a></p>
<p>Made with <Slidev class="inline"/> <a href="https://github.com/slidevjs/slidev"><strong>Slidev</strong></a> - a slides maker for developers that I am working on recently.</p>
</blockquote>
<YouTubeEmbed id="IMJjP6edHd0" />
<h2>Transcript</h2>
<p>My sharing today is Composable Vue, some pattens and tips that might be able to help you writing better composable logic in Vue.</p>
<h2><a href="https://vueuse.org/">VueUse</a></h2>
<p>It all started with me made this project called VueUse, which is a collection of Vue composable utilities. Initially, I was making this to share some of the functions I wrote with Vue Composition API to be used across apps. Till now, it grows much bigger with the community, we are now an organization on GitHub with 9 team members, 8 add-ons packages for different integrations like motions and document head management. We also have more than 100 functions in the core package that work for both Vue 2 and 3. I have really appreciated all the contributors and the awesome community.</p>
<p><carbon-logo-github class="inline-block"/> <a href="https://github.com/vueuse/vueuse" target="_blank">vueuse/vueuse</a></p>
<p>In today's talk, I will share with you the patterns and tips that I have learned during developing VueUse and using it to make apps in Composition API.</p>
<h2>Composition API</h2>
<p>Let's have a quick look at the Composition API itself. BTW, please note today's talk will be a little bit advanced, which I would assume you already have a basic knowledge of what the Vue Composition API is. But don't worry if you don't, I believe you will still get some basic images of the methodology and you can also find the slides and transcript on my site after the talk.</p>
<h3>Ref vs Reactive <MarkerCore/></h3>
<p>Well, let's start with Ref and Reactive. I bet many of you have wondered the difference between them and which one should you choose.</p>
<p>You can think refs as variables and reactives as objects. When you do the assignment, one is assigning &quot;value&quot; while the other one is assigning properties. While the usage of them can really dependents on what you gonna use them, but if we really need to pick one from them, I'd say go with <code>ref</code> whenever you can.</p>
<p>With <code>ref</code>, you will need to use <code>.value</code> to access and assigning values, but this also gives you more explicit awareness of when you are tracking and triggering the reactivity system.</p>
<!--eslint-skip-->
<pre><code class="language-ts">import { ref } from 'vue'

let foo = 0
let bar = ref(0)

foo = 1
bar = 1 // ts-error
</code></pre>
<p>As you can see the example here, I actually got an error by accidentally assigning ref with a value, and here I can change the code to fix it.</p>
<pre><code class="language-ts">import { reactive } from 'vue'

const foo = { prop: 0 }
const bar = reactive({ prop: 0 })

foo.prop = 1
bar.prop = 1
</code></pre>
<p>On the other hand, when using <code>reactive</code> you actually can't tell the difference between a plain object and a reactive object without looking for the context, which could sometimes make the debugging a little bit harder.</p>
<p>Also note in reactive objects, there are several caveats you need to take care about. Like you can't do object destructure without <code>toRefs</code> otherwise they will lose the reactivity. And you will also need to wrap with a function when using with <code>watch</code> and so on, where <code>ref</code> does not have such limitations.</p>
<h3>Ref Auto Unwrapping <MarkerCore /></h3>
<p>When using with <code>refs</code>, a big obstacle that people facing is the annoying <code>.value</code>. But actually, in many cases, you can omit it and make your code looks cleaner.</p>
<pre><code class="language-ts">const counter = ref(0)

watch(counter, (count) =&gt; {
  console.log(count) // same as `counter.value`
})
</code></pre>
<p>The <code>watch</code> function accepts ref as the watch source directly, and it will return the unwrapped new value of the ref in the callback. So in this case, there is zero <code>.value</code> needed.</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;button @click=&quot;counter += 1&quot;&gt;
    Counter is {\{ counter }}
  &lt;/button&gt;
&lt;/template&gt;
</code></pre>
<p>The other one is the nature of Vue, in the template, all the refs are auto unwrapped, even assignments!</p>
<pre><code class="language-ts">import { reactive, ref } from 'vue'

const foo = ref('bar')
const data = reactive({ foo, id: 10 })
data.foo // 'bar'
</code></pre>
<p>And whenever you feel like to better work with objects, you can pass the ref into the reactive object, and when you access the property, reactive will unwrap the ref automatically for you. Changes to the original ref will also reflect to the reactive object!</p>
<h3><code>unref</code> - Oppsite of Ref <MarkerCore /></h3>
<p><code>unref</code> is another Composition API I would like to introduce. As the name <code>unref</code> sounds, it's kinda the opposite of ref. While the <code>ref()</code> function takes a value and turns it into a ref, <code>unref()</code> takes a ref and returns its value.</p>
<pre><code class="language-ts">function unref&lt;T&gt;(r: Ref&lt;T&gt; | T): T {
  return isRef(r) ? r.value : r
}
</code></pre>
<p>The interesting part of it is that if you pass a plain value to <code>unref</code> it will return the value as-is to you, you can see the implementation is basically this.</p>
<pre><code class="language-ts">import { ref, unref } from 'vue'

const foo = ref('foo')
unref(foo) // 'foo'

const bar = 'bar'
unref(bar) // 'bar'
</code></pre>
<p>This is not a big feature, but a good tip to unify your logic which I will show you soon</p>
<h2>Patterns &amp; Tips</h2>
<p>That's the tips for using ref and reactive. Here I'd like to share with you some patterns of writing composable functions.</p>
<h2>What's composable Functions</h2>
<p>So what's composable functions?</p>
<p>It's actually kind of hard to give a proper definition, but I'd think it's like sets for reusable logic to make your code better organized, and separate the concerns.</p>
<pre><code class="language-ts">export function useDark(options: UseDarkOptions = {}) {
  const preferredDark = usePreferredDark() // &lt;--
  const store = useStorage('vueuse-dark', 'auto') // &lt;--

  return computed&lt;boolean&gt;({
    get() {
      return store.value === 'auto'
        ? preferredDark.value
        : store.value === 'dark'
    },
    set(v) {
      store.value = v === preferredDark.value
        ? 'auto'
        : v ? 'dark' : 'light'
    },
  })
}
</code></pre>
<p>Here is an example, the <code>useDark</code> function in VueUse is provided as a simple toggle to enable or disable the dark mode for apps. There are actually two variables involved, one is the system's preference and one is users' manual overrides. System preference can be got using media queries, while we would also need to use localStorage to read and store the user's preference of different modes.</p>
<DarkToggleButton/>
<p>As you can see in this code snippet, I have used two other composable functions <a href="https://vueuse.org/usePreferredDark"><code>usePreferredDark</code></a> and <a href="https://vueuse.org/useStorage"><code>useStorage</code></a>, they will return two refs that reflecting on their states. Detailed things like monitoring the media query changes, the timing to read and write the storage are left to them. And all I need to do is logically composing their relationship into a single ref.</p>
<p>You can see the full code or directly use it in VueUse with the link below.</p>
<VueUseFn name="useDark"/>
<h3>Think as &quot;Connections&quot;</h3>
<p>The first methodology I want to share today is to think as &quot;connections&quot;. Unlike hooks in React that will run on each updates, the <code>setup()</code> function in Vue only runs <strong>once</strong> on component initialization, to construct the relations between your state and logic.</p>
<p>You can think the equations in mathematics, where the left hand side and right hand side are always equal. Here we have <code>z=x^2+y^2</code>, while <code>x</code> and <code>y</code> are independent variables, and <code>z</code> is a controlled variables relying on <code>x</code> and <code>y</code>. Whenever I changed any of them, <code>z</code> will be updated accordingly (DEMO). Which is also similar to the formula in spreadsheets.</p>
<p>So in composable functions, we could think arguments are input and the returns as the output. The output should be able to reflect on input changes automatically. A bit complicated? I will walk with you on that later with examples.</p>
<h3>One Thing at a Time</h3>
<p>Another aspect is to do one thing at a time - which is the same as how you write any code. No need for me to spend too much time on this, but basically they are listed here.</p>
<ul>
<li>Extract duplicated logics into composable functions</li>
<li>Have meaningful names</li>
<li>Consistent naming conversions - <code>useXX</code> <code>createXX</code> <code>onXX</code></li>
<li>Keep function small and simple</li>
<li>&quot;Do one thing, and do it well&quot;</li>
</ul>
<p>Note it's also important to have a consistent naming conversion, like prefixed with <code>useXX</code> or <code>createXX</code> and so on to make those composable functions distinguishable from other functions.</p>
<h3>Passing Ref as Arguments</h3>
<p>Alright, let's start our first pattern today - Passing refs as arguments.</p>
<pre><code class="language-ts">function add(a: number, b: number) {
  return a + b
}
</code></pre>
<pre><code class="language-ts">const a = 1
const b = 2

const c = add(a, b) // 3
</code></pre>
<p>Here we have a plain add function that sums up the two arguments <code>a</code> and <code>b</code>. You can also see the example on the right.</p>
<pre><code class="language-ts">function add(a: Ref&lt;number&gt;, b: Ref&lt;number&gt;) {
  return computed(() =&gt; a.value + b.value)
}
</code></pre>
<pre><code class="language-ts">const a = ref(1)
const b = ref(2)

const c = add(a, b)
c.value // 3
</code></pre>
<p>And then we can make this function accepting refs, and return a computed ref with their sum. Then we can pass the refs to it as we normally would with plain values. The difference here is that the returned value is also a ref, but it will always be up-to-date with the ref <code>a</code> and <code>b</code>.</p>
<pre><code class="language-ts">function add(
  a: Ref&lt;number&gt; | number,
  b: Ref&lt;number&gt; | number
) {
  return computed(() =&gt; unref(a) + unref(b))
}
</code></pre>
<pre><code class="language-ts">const a = ref(1)

const c = add(a, 5)
c.value // 6
</code></pre>
<p>And remember the <code>unref</code> function we mentioned before? We can actually make this function more flexible, by accepting both refs and plain values. And use <code>unref</code> to get their values. We can also make the addition possible between a ref and a value.</p>
<h3>MaybeRef</h3>
<pre><code class="language-ts">type MaybeRef&lt;T&gt; = Ref&lt;T&gt; | T
</code></pre>
<p>Here is a simple TypeScript's type helper called <code>MaybeRef</code> that we have used a lot in VueUse. It's a union of generic <code>T</code> and <code>Ref&lt;T&gt;</code>.</p>
<pre><code class="language-ts">export function useTimeAgo(
  time: Date | number | string | Ref&lt;Date | number | string&gt;,
) {
  return computed(() =&gt; someFormating(unref(time)))
}
</code></pre>
<pre><code class="language-ts">import type { Ref } from 'vue'
import { computed, unref } from 'vue'

type MaybeRef&lt;T&gt; = Ref&lt;T&gt; | T

export function useTimeAgo(
  time: MaybeRef&lt;Date | number | string&gt;,
) {
  return computed(() =&gt; someFormating(unref(time)))
}
</code></pre>
<p>In this case, we have the function useTimeAgo that accepts a wide range of Date-like types as an argument. Normally if you want to accept refs, you would need to write them again as Ref versions. With this helper, you can make the type shorter and more readable (change code). A cool point it that this works great with <code>unref</code>, it can infer the correct type for <code>MaybeRef</code>.</p>
<h3>Make it Flexible <MarkerPattern /></h3>
<p>Think your functions like LEGO, there should have many different ways of composing them for different needs.</p>
<pre><code class="language-ts">import { useTitle } from '@vueuse/core'

const title = useTitle()

title.value = 'Hello World'
// now the page's title changed
</code></pre>
<p>Here we take <code>useTitle</code> function from VueUse as an example. Basically when you call it, you will get a special ref that binds to your page's title. Whenever you change the ref's value, the page's title will also be updated. Similarly, when the page's title changed externally, the change will also be reflect to the ref's value.</p>
<p>Looks good, right? But It creates a new ref whenever you call it. To make it more flexible, we can actually bind an existing ref, even computed!</p>
<pre><code class="language-ts">import { useTitle } from '@vueuse/core'
import { computed, ref } from 'vue'

const name = ref('Hello')
const title = computed(() =&gt; {
  return `${name.value} - World`
})

useTitle(title) // Hello - World

name.value = 'Hi' // Hi - World
</code></pre>
<p>Here you can see, I constructed a computed with a ref, when I change the source ref, the computed get re-evaluated so as the page's title.</p>
<h3><code>useTitle</code> <Marker class="text-blue-400">Case</Marker></h3>
<p>You must be wondering how could this be implemented. Let's take a look at a simplified version of it.</p>
<pre><code class="language-ts">import type { MaybeRef } from '@vueuse/core'
import { ref, watch } from 'vue'

export function useTitle(
  newTitle: MaybeRef&lt;string | null | undefined&gt;
) {
  const title = ref(newTitle || document.title) // &lt;-- 1

  watch(title, (t) =&gt; { // &lt;-- 2
    if (t != null)
      document.title = t
  }, { immediate: true })

  return title
}
</code></pre>
<p>It's actually only two statements! How?</p>
<p>At the first line, unified the ref from the user, or create a new one. And on the second line, it watches the changes to the ref and sync up with page's title.</p>
<p>Emm, maybe it's a little bit hard to catch on what's happened in the first line, let me explain a bit.</p>
<VueUseFn name="useTitle"/>
<h3>Reuse Refs <MarkerCore /></h3>
<p>Here, we utilized an interesting behavior of the ref function.</p>
<p>Similar to <code>unref</code> - <code>ref</code> also checks whether the passed value is ref or not. If you passed a ref to it, it will it as-is - since it's already a ref, there is no need to make another.</p>
<pre><code class="language-ts">const foo = ref(1) // Ref&lt;1&gt;
const bar = ref(foo) // Ref&lt;1&gt;

foo === bar // true
</code></pre>
<pre><code class="language-ts">function useFoo(foo: Ref&lt;string&gt; | string) {
  // no need!
  const bar = isRef(foo) ? foo : ref(foo)

  // they are the same
  const bar = ref(foo)

  /* ... */
}
</code></pre>
<p>This could also be extremely useful in composable functions that take <code>MaybeRef</code> as argument types.</p>
<h3><code>ref</code> / <code>unref</code></h3>
<p>Let's do a quick summary so far.</p>
<ul>
<li><code>MaybeRef&lt;T&gt;</code> works well with <code>ref</code> and <code>unref</code>.</li>
<li>Use <code>ref()</code> when you want to normalized it as a Ref.</li>
<li>Use <code>unref()</code> when you want to have the value.</li>
</ul>
<pre><code class="language-ts">type MaybeRef&lt;T&gt; = Ref&lt;T&gt; | T

function useBala&lt;T&gt;(arg: MaybeRef&lt;T&gt;) {
  const reference = ref(arg) // get the ref
  const value = unref(arg) // get the value
}
</code></pre>
<p>We can use <code>MaybeRef</code> in arguments to make the function flexible, and use <code>ref()</code> when you want to normalized it as a Ref and use <code>unref()</code> when you want to get the value. Both of them are universal and no conditions needed.</p>
<h3>Object of Refs <MarkerPattern /></h3>
<p>Another pattern today is to use objects of refs. When you need to return multiple data entries in a composable function, consider returns an object composed by refs.</p>
<pre><code class="language-ts">import { reactive, ref } from 'vue'

function useMouse() {
  return {
    x: ref(0),
    y: ref(0)
  }
}

const { x, y } = useMouse()
const mouse = reactive(useMouse())

mouse.x === x.value // true
</code></pre>
<p>In this way, users can have the full features of ES6 object destructure. The restructure values are refs, so the reactivity still remains, and users can also rename them, or take only partial of what they want.</p>
<p>On this other hand, it's also flexible enough when users want to use it as a single object, simply wrap it with the reactive function, the refs will get unwrapped as a property automatically.</p>
<p>That said, users can get benefits from both <code>ref </code>and <code>reactive</code> as need.</p>
<h3>Async to &quot;Sync&quot; <MarkerTips /></h3>
<p>Since we are constructing &quot;connections&quot; using Composition API, we can actually make async functions to &quot;sync&quot; by building the connections first before it resolves.</p>
<pre><code class="language-ts">const data = await fetch('https://api.github.com/').then(r =&gt; r.json())

// use data
</code></pre>
<p>Let's say we want to request some data use the <code>fetch</code> API. Normally we need to <code>await</code> the request been responded and data been parsed, before we can use the data. With Composition API, we can make the data as a ref of null, then be fulfilled later.</p>
<pre><code class="language-ts">const { data } = useFetch('https://api.github.com/').json()

const user_url = computed(() =&gt; data.value?.user_url)
</code></pre>
<p>This can make your apps take the time to handle other stuff while waiting for the data to be fetched. The idea is similar to react's stale-while-revalidate, but with much easier implementation.</p>
<h3><code>useFetch</code> <Marker class="text-blue-400">Case</Marker></h3>
<p>The implementation can be simplified down to this, all you have to do is to assign the value to <code>ref</code> when the promise got resolved.</p>
<pre><code class="language-ts">export function useFetch&lt;R&gt;(url: MaybeRef&lt;string&gt;) {
  const data = shallowRef&lt;T | undefined&gt;()
  const error = shallowRef&lt;Error | undefined&gt;()

  fetch(unref(url))
    .then(r =&gt; r.json())
    .then(r =&gt; data.value = r)
    .catch(e =&gt; error.value = e)

  return {
    data,
    error
  }
}
</code></pre>
<p>In the real world, we might also need some flags to show the current state of the request, where you can find the full code in VueUse.</p>
<VueUseFn name="useFetch"/>
<h3>Side-effects Self Cleanup <MarkerPattern /></h3>
<p><code>watch</code> and <code>computed</code> functions in Vue will stop themselves automatically along with the components unmounting. We'd recommend following the same pattern for your custom composable functions.</p>
<p>By calling the <code>onUnmounted</code> hooks inside your composable functions, you can schedule the effect clean-up logic.</p>
<pre><code class="language-ts">import { onUnmounted } from 'vue'

export function useEventListener(target: EventTarget, name: string, fn: any) {
  target.addEventListener(name, fn)

  onUnmounted(() =&gt; {
    target.removeEventListener(name, fn) // &lt;--
  })
}
</code></pre>
<p>For example, it's common to use <code>addEventListener</code> to register the handler to DOM events. When you finish the usage, you would also need to remember to unregister it using <code>removeEventListener</code>. In this case, we can have a function <code>useEventListener</code> that unregister itself along with the component so you don't need to worry about it anymore.</p>
<VueUseFn name="useEventListener"/>
<h3><code>effectScope</code> RFC <Marker class="text-purple-400">Upcoming</Marker></h3>
<p>While side-effects auto clean-up is nice, sometimes you might want to have better controls over when to do that. I drafted an RFC proposing a new API called <code>effectScope</code> to collect those effects into a single instance, that you can stop them together at the time you want. This is likely to be implemented and shipped with Vue 3.1. Check out for more details if it get you interested.</p>
<pre><code class="language-ts">// effect, computed, watch, watchEffect created inside the scope will be collected

const scope = effectScope(() =&gt; {
  const doubled = computed(() =&gt; counter.value * 2)

  watch(doubled, () =&gt; console.log(double.value))

  watchEffect(() =&gt; console.log('Count: ', double.value))
})

// dispose all effects in the scope
stop(scope)
</code></pre>
<h3>Typed Provide / Inject</h3>
<p>We have a set of new APIs called <code>provide</code> and <code>inject</code>. It's basically for sharing some context for the component's children to consume and reuse. They are two separate function, which means TypeScript can't actually infer the types for each context automatically.</p>
<p>But here we have a solution for that. Vue provided a type helper called <code>InjectionKey</code> where you can define a symbol that carries the type you want, and then it will hint <code>provide</code> and <code>inject</code> to have proper autocompletion and type checking.</p>
<pre><code class="language-ts">// context.ts
import type { InjectionKey } from 'vue'

export interface UserInfo {
  id: number
  name: string
}

export const injectKeyUser: InjectionKey&lt;UserInfo&gt; = Symbol()
</code></pre>
<p>For example, here I defined an interface <code>UserInfo</code> which contains two properties. And I exported a symbol with the <code>InjectionKey</code> type.</p>
<pre><code class="language-ts">// parent.vue
import { provide } from 'vue'
import { injectKeyUser } from './context'

export default {
  setup() {
    provide(injectKeyUser, {
      id: '7', // type error: should be number
      name: 'Anthony'
    })
  }
}
</code></pre>
<p>In usage, I can use the <code>provide</code> function to provide the data with key. Can you see here I get a type error that the id should be a number. So I can catch up the error right away before it goes to production.</p>
<pre><code class="language-ts">// child.vue
import { inject } from 'vue'
import { injectKeyUser } from './context'

export default {
  setup() {
    const user = inject(injectKeyUser)
    // UserInfo | undefined

    if (user)
      console.log(user.name) // Anthony
  }
}
</code></pre>
<p>And in the child component, we can use the <code>inject</code> function with the key as well. You can see it correctly infers the type <code>UserInfo</code> and so as its property.</p>
<h3>Shared State <MarkerPattern /></h3>
<p>With the flexibility of Vue's Composition API, sharing state is actually quite simple.</p>
<pre><code class="language-ts">// shared.ts
import { reactive } from 'vue'

export const state = reactive({
  foo: 1,
  bar: 'Hello'
})
</code></pre>
<p>You can declare some ref or reactive state in a js module, and import them to your components. Since they are using the same instance, the state will be just in sync.</p>
<pre><code class="language-ts">// A.vue
import { state } from './shared.ts'

state.foo += 1
</code></pre>
<pre><code class="language-ts">// B.vue
import { state } from './shared.ts'

console.log(state.foo) // 2
</code></pre>
<p>But please note this is actually not SSR compatible. In SSR your server will create a new app on each request, where this approach will keep the state persistent across multiple rendering. And normally it's not what we would expect.</p>
<h3>Shared State (SSR friendly) <MarkerPattern /></h3>
<p>Let's see if we can make a solution for it to work with SSR.</p>
<pre><code class="language-ts">export const myStateKey: InjectionKey&lt;MyState&gt; = Symbol()

export function createMyState() {
  const state = {
    /* ... */
  }

  return {
    install(app: App) {
      app.provide(myStateKey, state)
    }
  }
}

export function useMyState(): MyState {
  return inject(myStateKey)!
}
</code></pre>
<p>By using <code>provide</code> and <code>inject</code>, to share the state one the App context, which means it will be created every time when the server doing the rendering. You can see here I have two function, <code>createMyState</code> and <code>useMyState</code>. <code>createMyState</code> will returns a Vue plugin that provide the state to the App. While <code>useMyState</code> is just a wrapper of <code>inject</code> using the same key.</p>
<pre><code class="language-ts">// main.ts
const App = createApp(App)

app.use(createMyState())
</code></pre>
<pre><code class="language-ts">// A.vue

// use everywhere in your app
const state = useMyState()
</code></pre>
<p>In usage, we can create the state in the main entry and pass it to <code>app.use</code>. Then you can use the hook <code>useMyState</code> everywhere in your components.</p>
<p>If you have ever tried Vue Router v4, it actually uses a similar method to do that like <code>createRouter</code> and `useRouter.</p>
<h3>useVModel <MarkerTips /></h3>
<p>One last tip I'd like to share is a utility called <code>useVModel</code>.</p>
<pre><code class="language-ts">export function useVModel(props, name) {
  const emit = getCurrentInstance().emit

  return computed({
    get() {
      return props[name]
    },
    set(v) {
      emit(`update:${name}`, v)
    }
  })
}
</code></pre>
<p>It's just a simple wrapper to the component model to bind with <code>props</code> and <code>emit</code>. This is actually a lifesaver to me.</p>
<pre><code class="language-ts">export default defineComponent({
  setup(props) {
    const value = useVModel(props, 'value')

    return { value }
  }
})
</code></pre>
<p>We can take a look at the code, you can see we used a writable computed. When accessing the value, we forward the value of props to it, and when writing, we emit out the update event automatically so you can use just like a normal ref.</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;input v-model=&quot;value&quot;&gt;
&lt;/template&gt;
</code></pre>
<p>Even more, we can actually bind into our children elements's <code>v-model</code> very easily.</p>
<VueUseFn name="useVModel"/>
<h2>Vue 2 &amp; 3</h2>
<p>That's all the tips and patterns I have for today.</p>
<p>As you might think those are for Vue 3 only, but actually they also applies for Vue 2!</p>
<h3><code>@vue/composition-api</code> <Marker class="text-teal-400">Lib</Marker></h3>
<p>In case you didn't know that, if you are still on Vue 2 but want to start using the Composition API, here we offered an official plugin that enables the Composition API for your Vue 2 app. Give it a try if you haven't.</p>
<p><carbon-logo-github class="inline-block"/> <a href="https://github.com/vuejs/composition-api" target="_blank">vuejs/composition-api</a></p>
<pre><code class="language-ts">import VueCompositionAPI from '@vue/composition-api'
import Vue from 'vue'

Vue.use(VueCompositionAPI)
</code></pre>
<pre><code class="language-ts">import { reactive, ref } from '@vue/composition-api'
</code></pre>
<h3>Vue 2.7 <Marker class="text-purple-400">Upcoming</Marker></h3>
<p>We also announced <a href="https://github.com/vuejs/rfcs/blob/ie11/active-rfcs/0000-vue3-ie11-support.md#for-those-who-absolutely-need-ie11-support">our plan for Vue 2.7</a> recently. Vue 2.7 will be the last minor version of Vue 2 with long time support for existing projects and those who still need IE 11 support. We will back-port Vue 3's new features to Vue 2.7 and migrate the <code>@vue/compositon-api</code> plugin into it. Stay tuned on that.</p>
<ul>
<li>Backport <code>@vue/composition-api</code> into Vue 2's core.</li>
<li><code>&lt;script setup&gt;</code> syntax in Single-File Components.</li>
<li>Migrate codebase to TypeScript.</li>
<li>IE11 support.</li>
<li>LTS.</li>
</ul>
<h3>Vue Demi <Marker class="text-teal-400">Lib</Marker></h3>
<p>If you are a library author want your libraries to support Vue 2 and 3 with the same codebase. You can try Vue Demi, which eases out the difference between Vue 2 and 3 and auto-detects users' environment.</p>
<p><carbon-logo-github class="inline-block"/> <a href="https://github.com/vueuse/vue-demi">vueuse/vue-demi</a></p>
<pre><code class="language-ts">// same syntax for both Vue 2 and 3
import { defineComponent, reactive, ref } from 'vue-demi'
</code></pre>
<h2>Thank you!</h2>
<p>That's all for today.</p>
<p>Due to the time limit, it's a shame that I can not share all I have learned with you. As the Vue composition API is still fairly new, I believe there are more patterns and better practices for us to found.</p>
<p>To find more information, do check out the <a href="https://github.com/vueuse">VueUse org on GitHub</a> and <a href="https://vueuse.org/add-ons.html">its awesome ecosystem</a>, and follow us on Twitter <a href="https://twitter.com/vueuse">@vueuse</a> to keep up-to-date with news and tips.</p>
<p>Thank you!</p>
<p align="center">
  <a href="https://github.com/sponsors/antfu#sponsors">
    <img src='https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg'>
  </a>
</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Develop with Vite]]></title>
            <link>https://antfu.me/posts/vue-beijing-2021</link>
            <guid isPermaLink="true">https://antfu.me/posts/vue-beijing-2021</guid>
            <pubDate>Sun, 28 Mar 2021 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>This is the transcript of my talk at <a href="https://twitter.com/beijing_vue">Vue Beijing</a></p>
<p>Slides: <a href="https://antfu.me/talks/2021-03-28/en">English ver.</a> | <a href="https://antfu.me/talks/2021-03-28/zh">中文 ver.</a></p>
<p>Recording: <a href="https://www.youtube.com/watch?v=xx8gEHet6n8">YouTube (English)</a></p>
</blockquote>
<YouTubeEmbed id="xx8gEHet6n8" />
<h2>Transcript</h2>
<p>I guess many of you have already heard about <a href="https://github.com/vitejs/vite">Vite</a>, as the next thing replacing other bundlers like Webpack. Well, it's actually not 100% true. While we are used to &quot;Build with Webpack&quot;, and now, more precisely, we are <strong>Developing with Vite</strong>.</p>
<p>Today I am going to present you with a brief introduction to Vite, the next-generation development tools. And I believe you will find out the answer after it.</p>
<h2>What is Vite?</h2>
<p>Vite is a French word meaning fast. The initial motivation of it is that Evan You, the creator of Vue, got an idea of making a dev server with hot reload for Vue Single File Component without a bundler. And yeah, after a few days, Vite comes out.</p>
<p>With the name of fast, it has to be fast. And it is.</p>
<p>Let me show you a quick <a href="https://twitter.com/amasad/status/1355379680275128321">demonstration of how fast it is</a>. On the left-hand side, we have Create React App, and on the right we have Vite. And you can see during I am introducing to them, the Vite app is already ready and playable, while the other one just finishes installing its dependencies. In this demo, we can see we have over 4x faster boot-up speed improvement over Create React App, on the single component starter template. And actually, it's not even showing the full potential of Vite.</p>
<p>So how could Vite be so fast?</p>
<p>First, Vite is opinionated on providing better DX. It assumes that you are using modern browsers for development, so we don't need to have complex transpiling and polyfills involved. Also since your browser already understands Native ES module, we can even skip the bundling process and let the browser do it for us. We also involved with some optimizations to make it even faster, which I will go through them later.</p>
<p>We have a build mode for production powered by rollup. The difference between development and production make Vite capable of having good experiences for both of them.</p>
<h3>The Dev Server</h3>
<p>In a traditional bundle-based dev server, when we start the server, it will bundle your entire app and the server is ready only until the bundling is finished. In a large-scale app, it could take quite a lot of time.</p>
<p>Native ESM bases server, on the other hand, does need to do the bundling at all. The server is ready immediately and it will only transpile the modules of the pages you have opened on-demanded. So even you have a huge app with thousands of pages, it will be constantly fast as it only needs to transpile the modules for one page.</p>
<p>The transpling is powered by <a href="https://github.com/evanw/esbuild">esbuild</a>. It is a transpiler and bundler written in Go and build to native code. It is optimized with speed in mind and utilizes the potential of parallelism. It claims that it can be 10-100x faster than the traditional build tools.</p>
<p>With the support <a href="https://github.com/evanw/esbuild">esbuild</a>, we are able to support JSX, TypeScript out-of-the-box.</p>
<h3>Dependencies Pre-bundling</h3>
<p>Another optimization of Vite is the dependencies pre-bundling. Normally, your dependencies do not change really often unless you are upgrading them, but on the other hand, your user code can change everyday.</p>
<p>So by treating the user code and dependencies differently, we pre bundles your dependencies into a single file standard ESM that can be understood by the browser. In this way, we ease out the difference of packages shipping different js formats like <code>cjs</code> or node favored modules. It also reduced HTTP request overhead and importing waterfall.</p>
<p>And this bundling process is also powered by <a href="https://github.com/evanw/esbuild">esbuild</a>, with over 20x faster performance.</p>
<h3>Hot Module Replacement</h3>
<p>Another important part of Vite is that it has out-of-box hot module replacement support. Whenever you made a change to your code, the HMR is triggered. It's smart enough to know which modules would be affected by the changes and replace them efficiently. And we have first-party support for Vue single-file components and React Refresh.</p>
<p>As Vite is made by Evan, you may think it's only Vue. Well, initially, it was kind of true, but things are different now.</p>
<p>From Vite 2.0, it's now framework-agnostic and Vue is supported through a plugin. It also comes with a bunch of new features and improvements, for example the universal plugin system and first-class SSR. For more details, you check out the links in the slide.</p>
<p>You can use <code>npm init</code> to create the starter project with the official templates. As you can see, we have supported Vue, React, Preact, Lit Element, Svelte, and even vanilla. It's not limited to these, we will keep adding more as we go.</p>
<hr>
<h2>Powerful Plugins System</h2>
<p>Vite's plugins are compatible with Rollup plugins. This means you can use the huge amount of existing plugins from rollup on Vite.</p>
<p>You can check out <a href="https://github.com/patak-js/vite-rollup-plugins">this Vite Rollup Plugins site</a> by our team member <a href="https://github.com/patak-js">@patak-js</a>. It lists all the compatibility of popular rollup plugins with some demo and guides of how to use them.</p>
<p>We also have <a href="https://github.com/vitejs/awesome-vite">an awesome list</a> that lists Vite plugins for different ecosystems. Check it out, I believe you will find some of them useful to you.</p>
<hr>
<h2>Fresh Vue Authoring Experience</h2>
<p>I'd like to feature some Vite plugins for Vue that provide the Fresh authoring experience on creating Vue apps.</p>
<h3><a href="https://github.com/hannoeru/vite-plugin-pages"><code>vite-plugin-pages</code></a> by <a href="https://github.com/hannoeru">@hannoeru</a></h3>
<p>It provides Nuxt.js-like file base routing, with dynamic routes support that can be accessed as the props in the page component.</p>
<p>Only with 3 lines of code, you can set up this feature and use it immediately.</p>
<pre><code class="language-ts">import routes from 'virtual:generated-pages'
import { createRouter } from 'vue-router'

const router = createRouter({ routes })
</code></pre>
<p>Check <a href="https://github.com/hannoeru/vite-plugin-pages">its docs</a> for more.</p>
<h3><a href="https://github.com/hannoeru/vite-plugin-components"><code>vite-plugin-components</code></a> by <a href="https://github.com/antfu">@antfu</a></h3>
<p>In Vue, writing the name of the component four times in order to import them is kind of a pain for me. So I made this plugin to do the component auto-importing. Now you can put your components under <code>src/components</code> and then use them everywhere without needing to import them. We also have built-in support for auto importing component libraries with minimal configurations. For now, we have supported Vuetify, Ant Design Vue, Element Plus, Vant, and so on.</p>
<p>From:</p>
<pre><code class="language-vue">&lt;script&gt;
import HelloWorld from './src/components/HelloWorld.vue'

export default {
  components: {
    HelloWorld,
  },
}
&lt;/script&gt;

&lt;template&gt;
  &lt;HelloWorld msg=&quot;Hello Vue 3.0 + Vite&quot; /&gt;
&lt;/template&gt;
</code></pre>
<p>To:</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;HelloWorld msg=&quot;Hello Vue 3.0 + Vite&quot; /&gt;
&lt;/template&gt;
</code></pre>
<h3><a href="https://github.com/hannoeru/vite-plugin-icons"><code>vite-plugin-icons</code></a> by <a href="https://github.com/antfu">@antfu</a></h3>
<p>Another one is <code>vite-plugin-icons</code>. It allows you to use icons from any icon set, for example, Material design icons and Font awesome. Which the on-demand spirit of Vite, this will only ship with the icons that you actually use. So you can say goodbye to the old-school icon font approach that downloads a huge font with all the icons that you don't actually need.</p>
<p>It also works well with the component auto importing, and you can use them like magic.</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;i-carbon-accessibility /&gt;
  &lt;i-mdi-account-box style=&quot;font-size: 2em; color: red&quot; /&gt;
&lt;/template&gt;
</code></pre>
<h3><a href="https://github.com/windicss/vite-plugin-windicss"><code>vite-plugin-windicss</code></a> by <a href="https://github.com/antfu">@antfu</a></h3>
<p>If you have ever used <a href="https://tailwindcss.com/">Tailwind CSS</a>, you must aware it's actually quite slow in the dev server as it ships all the utilities with megabytes of CSS to your client. This becomes the slowest part of my Vite app.</p>
<p>Luckily, we have a new thing called <a href="https://github.com/windicss/windicss">Windi CSS</a>, which you can think of it as the on-demand Tailwind CSS. Instead of shipping all the combinations of classes and purge them down later. It only generates the classes you actually use. Turns out it can be 20-100x faster than the traditional Tailwind. While it's on-demand, it also opens up more features like unit auto-inferring. Do check it out if you are using Tailwind.</p>
<h3>Try them all</h3>
<p>If you found them interesting and want to try it yourself, I also made a starter template call <a href="https://github.com/antfu/vitesse">Vitesse</a>, with all of them included and more features. Pull it down and check out.</p>
<p>These are only a small part of our plugins ecosystem, we have more of them available in the <a href="https://github.com/vitejs/awesome-vite">awesome list</a> do remember to check them out.</p>
<h3>Vue 2 for Vite</h3>
<p>If you are still using Vue 2, no worries, we have your covered!</p>
<p>While the official Vue plugin is for Vue 3. Another Vite team member <a href="http://github.com/underfin">@underfin</a> made the plugin <a href="https://github.com/underfin/vite-plugin-vue2"><code>vite-plugin-vue2</code></a> for Vue 2. With a single line in the config, you are good to go. It's been wildly adapted already, for example, <a href="https://github.com/nuxt/vite">Nuxt 2 for Vite</a> is powered on it.</p>
<p>In the <a href="https://github.com/vitejs/awesome-vite">awesome list</a>, we have marked the compatibility for each plugin of the Vue 2 support. Many of them are isomorphic to both Vue 3 and 2. If you are going to try Vue 2, you don't want to miss it.</p>
<h3>Legacy Browser Support</h3>
<p>Vite uses native ESM on both development and production, but if you want to enable legacy browsers that do not support ESM, no problem, we have it.</p>
<p>There is an official plugin <a href="https://github.com/vitejs/vite/tree/main/packages/plugin-legacy"><code>@vitejs/plugin-legacy</code></a> that uses Babel and System JS to transform the modules for legacy support.</p>
<pre><code class="language-ts">// vite.config.js
import legacy from '@vitejs/plugin-legacy'

export default {
  plugins: [
    legacy({
      targets: ['defaults', 'not IE 11']
    })
  ]
}
</code></pre>
<p>Check out the docs for more details.</p>
<hr>
<h2>Ecosystem and Community</h2>
<p>Then, let's talk a bit about the community.</p>
<p>I bet you already have this question - Vite is great, but what does Vite mean for the existing Vue ecosystem? Let me help you to find it out.</p>
<p>How about <a href="https://github.com/vuepress/vuepress-next">VuePress</a>? It's actually <a href="https://twitter.com/meteorlxy_cn/status/1370728812971917315">already supported Vite in the version 2 beta</a>! In v2, you can swap the engine between Webpack and Vite, and have the instant reload from Vite. Check out the docs for how it works!</p>
<p>As for Nuxt, we actually have some exciting news! The <a href="https://twitter.com/_pi0_/status/1352344462954016768">upcoming Nuxt 3 will support interchangeable engines between Webpack and Vite</a>. And you can get the benefit of the huge Nuxt community. It will be available as a public beta at Q2 this year. They also release <a href="https://twitter.com/_pi0_/status/1365049110982778884">an experimental project for Nuxt 2 to support Vite</a> where you can try it today.</p>
<p>About Vue CLI, as <a href="https://twitter.com/youyuxi/status/1354584410482499585">Evan mentioned</a>, we weren't intended to replace Vue CLI with Vite, but it turns out it could be. The long-term goal of Vue CLI is to support Vite with a powerful scaffolding capability and easier to get started.</p>
<p>We also have a community plugin <a href="https://github.com/IndexXuan/vue-cli-plugin-vite"><code>vue-cli-plugin-vite</code></a> that enables Vite support in Vue CLI that you can play with it today.</p>
<hr>
<h2>Higher-level Integrations</h2>
<p>We also have a community plugin that enables Vite support in Vue CLI that you can try today. And on top of Vite, we are now having some cool higher-level integrations tools.</p>
<p>The first one is <a href="https://github.com/vuejs/vitepress">VitePress</a>, a Vite and Vue powered static site generator. Similar to VuePress, but with more opinionated pre-configuration. This project is still experimental but already served as the generator for many official documentation sites, including the Vite docs itself.</p>
<p><a href="https://github.com/ream/ream">Ream</a> is a Vite-based framework with the support of fast SSR built-in by <a href="https://github.com/egoist">@egoist</a>. At the time I am preparing these slides, it's doing a rewrite to make not only for Vue but also for any frameworks. Stay tuned on that.</p>
<p>Edge side rendering becomes quite popular recently, and we are also having a tool called <a href="https://github.com/frandiox/vitedge">Vitedge</a> by <a href="https://github.com/frandiox">@frandiox</a> to bring it to Vite. Take a look at its repo as well.</p>
<p>Vite also supports <a href="https://vitejs.dev/guide/backend-integration.html">backend integrations</a>, now we already have <a href="https://github.com/ElMassimo/vite_ruby">Vite Ruby</a> by <a href="https://github.com/ElMassimo">@ElMassimo</a> and <a href="https://github.com/innocenzi/laravel-vite">Laravel Vite</a>by <a href="https://github.com/innocenzi">@innocenzi</a> in the community. They leverage Vite to serve the front-end and benefit from Vite's fast performance.</p>
<p>You can also set up your own backend integrations easily by following the docs below.</p>
<hr>
<h2>Upcoming</h2>
<p>Not only for Vue and the new tools. The tools you love are also going to support Vite. Here are some news for upcoming things I'd like to share with you.</p>
<p><a href="https://github.com/sveltejs/kit">Svelte Kit</a> is the next official build tools for Svelte, and <a href="https://github.com/sveltejs/kit/pull/409">they are moving from Snowpack to Vite</a>. It's in the early beta now and you can check it out if it got you interested.</p>
<p><a href="https://twitter.com/_jessicasachs/status/1354585366620221443">Cypress is also adding the first-class Vite support</a>. I believe we can see it ready within this year.</p>
<p>And <a href="https://twitter.com/storybookjs/status/1371894052015239170">Storybook is also exploring the interchangeable engines for Vite and Snowpack</a>. You can also keep an eye on that.</p>
<hr>
<h2>Start Vite today!</h2>
<p>We are waiting for you to join our community and start playing with us!</p>
<p>Just firing up this command in your terminal to get the first impressions!</p>
<pre><code class="language-bash">npm init @vitejs/app
</code></pre>
<p>That's all for today. Join the <a href="http://chat.vitejs.dev/">discord</a> to chat with us and follow us on <a href="https://twitter.com/vite_js">Twitter</a> to get the latest news.</p>
<p>See you in the community, thank you!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Reflection of Speaking in Public]]></title>
            <link>https://antfu.me/posts/reflection-of-speaking-in-public</link>
            <guid isPermaLink="true">https://antfu.me/posts/reflection-of-speaking-in-public</guid>
            <pubDate>Sat, 27 Mar 2021 16:00:00 GMT</pubDate>
            <description><![CDATA[My sincere apologies to everyone involved.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Disclaimer: All the words are only representing my opinions. None of them applies to the Windi CSS's author nor the team.</p>
</blockquote>
<p>The past two weeks have been tough for me. I have caused some drama where it might not need to be like that. The guilty keeps spinning around my head, asking myself if I made the right decisions, or what can be better if I am not taking it this way.</p>
<p>I guess I am still immature and naive to the community. I was so lucky to get so many of you enjoying my work, subscribing to my feeds, more than I could ever imagine. I was not aware of how the expression of myself could have so much effect on others.</p>
<p>I couldn't help myself not to think about what's the best outcome we could possibly have, if both of us could make better decisions. Maybe we can have a world that Windi and Tailwind JIT could co-exist, helping and inspiring each other, like react and preact. Windi could be the community-driven playground/experiment field to try out new ideas and ships rapidly, and Tailwind could take those good enhancements that have been proven in Windi and make it more solid and ready for production. That might be the things we should see them happen in the open-source rather than the current drama and sadness.</p>
<p>Not saying I think what Tailwind does is acceptable to me. I believe there isn't something called &quot;original ideas&quot; - we are all learning and inspiring from others. Giving proper credit to where the inspirations come from is the least thing we can do to make open source a better place and encourage more people to share their ideas/code in the public space.</p>
<p>But after these days of reflection, I think I could be more mature and calm to handle this better and prevent all of these from happening. Here are some thoughts for myself to be aware of not doing the same thing again.</p>
<ul>
<li>I should have more active communication with Adam and Tailwind on this thing before I spoke it out in public. Or even better before they make the announcement.</li>
<li>I should not take this personally and emotionally, and not assail Adam and the Tailwind team.</li>
<li>When Tailwind appended the mention in the comments, I should call it over and move on. Instead of bringing this up once again.</li>
<li>I should ask for the opinions from the Windi team before going public.</li>
</ul>
<p>I had failed to make none of these, resulting in the worst outcome. I was irresponsible to do this for both the community and the Windi team who have trusted me.</p>
<p><strong>My sincere apologies to Adam, the Tailwind team, the Windi team, and everybody involved with this drama.</strong></p>
<p>I don't know what this will end with, but fighting is definitely not something I meant to see. This drama was started by me, and I wish we can call it an end. I don't really want to see such things happening again, but I truly wish I can handle it better next time taking the lesson I have learned today. After all, I still believe people here in open source are with good willing to make things better for everyone.</p>
<p>Thank you for reading through. And hope you could understand and forgive this naive and immature boy.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Type your Config]]></title>
            <link>https://antfu.me/posts/type-your-config</link>
            <guid isPermaLink="true">https://antfu.me/posts/type-your-config</guid>
            <pubDate>Wed, 24 Mar 2021 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Configurations can be quite complex, and sometimes you may want to utilize the great type checking that TypeScript provided. Change your <code>xxx.config.js</code> to <code>xxx.config.ts</code> is not an ideal solutions as you will need to have a Node.js register involved to transpile it into JavaScript and some tools might not support doing that way. Fortunately, TypeScript also support type check in plain JavaScript file with JSDoc. Here is an example of Webpack config with type checks:</p>
<pre><code class="language-ts">// webpack.config.js
// @ts-check

/**
 * @type {import('webpack').Configuration}
 */
const config = {
  /* ... */
}

module.exports = config
</code></pre>
<p>Prefect. Everything should work and you can already call it a day.</p>
<p>I have never thought about we can do better, until I saw <a href="https://vitejs.dev/config/#config-intellisense">Vite's approach</a>. In Vite, you can simply have:</p>
<pre><code class="language-ts">// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  /* ... */
})
</code></pre>
<p>No JSDocs, no need to declare a variable first then export it. And since TypeScript will infer the types even you are using plain JavaScript, it works great with both.</p>
<p>How? The <code>defineConfig</code> is literally a pass-through, but brings with types:</p>
<pre><code class="language-ts">import type { UserConfig } from 'vite'

export function defineConfig(options: UserConfig) {
  return options
}
</code></pre>
<p><code>defineConfig</code> exists in the runtime, so it works for JavaScript even if the types get truncated. This is really just some small details of DX, but I would wish more tools could adapt this approach and make the type checking more approachable and simpler.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Windi CSS and Tailwind JIT]]></title>
            <link>https://antfu.me/posts/windicss-and-tailwind-jit</link>
            <guid isPermaLink="true">https://antfu.me/posts/windicss-and-tailwind-jit</guid>
            <pubDate>Thu, 18 Mar 2021 16:00:00 GMT</pubDate>
            <description><![CDATA[Wish this is the end.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Disclaimer: All the words are only representing my opinions. None of them applies to the Windi CSS's author nor the team.</p>
</blockquote>
<Tweet>
<p lang="en" dir="ltr">Congrats Tailwind, and thanks for NOT mentioning Windi CSS at all for your new ideas.<br><br>I appreciated how did Tailwind power my apps and make my life easier. But this really changed my mind about how I see the people behind it.<br><br>Check out <a href="https://t.co/kcEzm5Ickp">https://t.co/kcEzm5Ickp</a> by <a href="https://twitter.com/satireven?ref_src=twsrc%5Etfw">@satireven</a>. <a href="https://t.co/tkJCy8Pgb3">https://t.co/tkJCy8Pgb3</a></p>&mdash; Anthony Fu (@antfu7) <a href="https://twitter.com/antfu7/status/1371533878800748545?ref_src=twsrc%5Etfw">March 15, 2021</a>
</Tweet>
<p>I spoke out for <a href="https://github.com/windicss/windicss">Windi CSS</a> just because keeping silence on this also means we are encouraging such things to happen again in the open-source community.</p>
<p>Tailwind was once my favorite CSS framework and I was really happy to see we made it works on Vite <a href="https://twitter.com/antfu7/status/1361398324587163648">much faster</a> with <a href="https://github.com/voorjaar">@voorjaar</a>'s astonishing Windi CSS compiler. But I was totally unexpected to see that end up being like this. Don't get me wrong, it's great to see Tailwind's long pain get solved officially with Tailwind JIT and benefit the community. What I am saying is that Tailwind used/inspired by Windi's idea <strong>without even mentioning Windi CSS once and claiming it's their own ideas</strong> (until 20min after my tweets about that, <a href="https://twitter.com/adamwathan/status/1371542711086559237?s=20">they appended two tweets in the comments</a>, mentioned about Windi but still implies it's their idea. That's all we got). No official reply of this whole thing, not updates to their repo's README, at all.</p>
<p>I don't want to speculate what's the reasons or motivations behind it, all I know is that <strong>I will not use any products from Tailwind Labs anymore</strong>. If you think I was reacting too much or it was not a big deal, then I truly wish such things would never happend on you once again.</p>
<Tweet>
<p lang="en" dir="ltr">I remember the day when Windi got blown up on Twitter, <a href="https://twitter.com/adamwathan?ref_src=twsrc%5Etfw">@adamwathan</a> called me and asked me about how Windi CSS works and he is happy to make it official. And yeah, I was excited to see things are improving for the whole community, so I shared all I know with him.<br><br>1/n <a href="https://t.co/Ho1DddqA33">https://t.co/Ho1DddqA33</a></p>&mdash; Anthony Fu (@antfu7) <a href="https://twitter.com/antfu7/status/1371538602488786945?ref_src=twsrc%5Etfw">March 15, 2021</a>
</Tweet>
<Tweet>
<p lang="en" dir="ltr">It&#39;s almost impossible for a small org like us to fight with the giant Tailwind. But we will not give up just because of this.<br><br>Let&#39;s see and good luck.</p>&mdash; Anthony Fu (@antfu7) <a href="https://twitter.com/antfu7/status/1371538609388494852?ref_src=twsrc%5Etfw">March 15, 2021</a>
</Tweet>
<p>On the bright side, Windi pushed Tailwind to make the JIT and improved the DX and Tailwind forced Windi to be independent and evolving to not being a &quot;Tailwind accessory&quot; any longer.</p>
<p>There is another community based CSS-in-JS alternative called <a href="https://github.com/tw-in-js/twind">Twind</a>, which you definitely want to check out (they are working closely with Windi to bring an uniformed community standard/spec of our DSL, cheers <a href="https://github.com/sastan">@sastan</a>!).</p>
<p>As for myself, I will spend more time on working with Windi CSS to make it even better. Here is some of exciting things we are working on:</p>
<Tweet>
<p lang="en" dir="ltr">Spoiler of the new piece of our playground - interactive selector.<br><br>I always find myself trouble picking the directions of rounded corners and looking up for a huge table of all the possible combinations could be inefficient. So yeah :)<br><br>rounded-1/2 is new in Windi CSS btw 😉 <a href="https://t.co/f841k9BgPF">https://t.co/f841k9BgPF</a> <a href="https://t.co/pVIlB2X1nY">pic.twitter.com/pVIlB2X1nY</a></p>&mdash; Anthony Fu (@antfu7) <a href="https://twitter.com/antfu7/status/1371779599084888064?ref_src=twsrc%5Etfw">March 16, 2021</a>
</Tweet>
<Tweet>
<p lang="en" dir="ltr">Limitation? Let&#39;s break it!💥<br><br>Upcoming feature in <a href="https://twitter.com/windi_css?ref_src=twsrc%5Etfw">@windi_css</a> 🍃<br>&quot;Design in DevTools&quot;!<br><br>Whenever you made changes to the classes in DevTools, the CSS updates automatically for you, on-demand as always :)<br><br>Idea credit goes to <a href="https://twitter.com/MaximoMussini?ref_src=twsrc%5Etfw">@maximomussini</a> 🙌 <a href="https://t.co/DHf2h5wroM">pic.twitter.com/DHf2h5wroM</a></p>&mdash; Anthony Fu (@antfu7) <a href="https://twitter.com/antfu7/status/1372244287975387145?ref_src=twsrc%5Etfw">March 17, 2021</a>
</Tweet>
<p>Thanks for reading though. Don't feel any pressure about using any tools that you needed. I choose not to use them just because my personal preference. I don't want to force anyone to switch their stack because reading this post, we will keep improve it and show you which is the better one you should use :)</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Typed Provide and Inject in Vue]]></title>
            <link>https://antfu.me/posts/typed-provide-and-inject-in-vue</link>
            <guid isPermaLink="true">https://antfu.me/posts/typed-provide-and-inject-in-vue</guid>
            <pubDate>Fri, 05 Mar 2021 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I didn't know that you can type <code>provide()</code> and <code>inject()</code> elegantly until I watched <a href="https://github.com/LinusBorg/">Thorsten Lünborg</a>'s talk on <a href="https://vuejs.amsterdam/">Vue Amsterdam</a>.</p>
<p>The basic idea here is the Vue offers a type utility <code>InjectionKey</code> will you can type a Symbol to hold the type of your injection values. And when you use <code>provide()</code> and <code>inject()</code> with that symbol, it can infer the type of provider and return value automatically.</p>
<p>For example:</p>
<pre><code class="language-ts">// context.ts
import type { InjectionKey } from 'vue'

export interface UserInfo {
  id: number
  name: string
}

export const InjectKeyUser: InjectionKey&lt;UserInfo&gt; = Symbol()
</code></pre>
<pre><code class="language-ts">// parent.vue
import { provide } from 'vue'
import { InjectKeyUser } from './context'

export default {
  setup() {
    provide(InjectKeyUser, {
      id: '117', // type error: should be number
      name: 'Anthony',
    })
  },
}
</code></pre>
<pre><code class="language-ts">// child.vue
import { inject } from 'vue'
import { InjectKeyUser } from './context'

export default {
  setup() {
    const user = inject(InjectKeyUser) // UserInfo | undefined

    if (user)
      console.log(user.name) // Anthony
  },
}
</code></pre>
<p>See <a href="https://v3.vuejs.org/api/composition-api.html#provide-inject">the docs</a> for more details.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Color Scheme for VS Code]]></title>
            <link>https://antfu.me/posts/color-scheme-for-vscode-ext</link>
            <guid isPermaLink="true">https://antfu.me/posts/color-scheme-for-vscode-ext</guid>
            <pubDate>Mon, 01 Mar 2021 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>There is currently no API to access colors of current theme in VS Code Extensions, nor the meta information of them. It frustrated me for a long while, until today I came up with a dirty but working solution.</p>
<p>Since most of the themes follow the conversions of having <code>Light</code> or <code>Dark</code> in their names. Then we can have:</p>
<pre><code class="language-ts">import { workspace } from 'vscode'

export function isDarkTheme() {
  const theme = workspace.getConfiguration()
    .get('workbench.colorTheme', '')

  // must be dark
  if (theme.match(/dark|black/i) != null)
    return true

  // must be light
  if (theme.match(/light/i) != null)
    return false

  // IDK, maybe dark
  return true
}
</code></pre>
<p>Simple, but surprisingly, it works really well. This is used for my <a href="https://github.com/antfu/vscode-browse-lite">Browse Lite</a> extension to inject the preferred color schema matching with VS Code's theme. And also <a href="https://github.com/antfu/vscode-iconify">Iconify IntelliSense for VS Code</a> to update icons color with the theme.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Types for Submodules]]></title>
            <link>https://antfu.me/posts/types-for-sub-modules</link>
            <guid isPermaLink="true">https://antfu.me/posts/types-for-sub-modules</guid>
            <pubDate>Mon, 01 Mar 2021 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>When you build multiple entries in a single package, you exports them with <code>exports</code> syntax. Like</p>
<pre><code class="language-json">{
  &quot;exports&quot;: {
    &quot;.&quot;: {
      &quot;types&quot;: &quot;./dist/index.d.ts&quot;,
      &quot;import&quot;: &quot;./dist/index.mjs&quot;,
      &quot;require&quot;: &quot;./dist/index.cjs&quot;
    },
    &quot;./foo&quot;: {
      &quot;types&quot;: &quot;./dist/foo.d.ts&quot;,
      &quot;import&quot;: &quot;./dist/foo.mjs&quot;,
      &quot;require&quot;: &quot;./dist/foo.cjs&quot;
    }
  }
}
</code></pre>
<p>Then tho you provide <code>types</code> field for the sub modules, most of the users still got the error:</p>
<pre><code class="language-txt">Cannot find module 'my-pkg/foo' or its corresponding type declarations.
</code></pre>
<p>Well that's because the <code>types</code> field in <code>exports</code> will only be resolved when you add <code>&quot;moduleResolution&quot;: &quot;NodeNext&quot;</code> to the <code>tsconfig.json</code> file. Which might cause more issue since not all the packages are up to date.</p>
<p>So when you trying to import <code>my-pkg/foo</code>, TypeScript actually looking for the <code>foo.d.ts</code> file under your package root instead of your <code>dist</code> folder. One solution I been used for a long time is to create a redirection file that published to npm, like:</p>
<pre><code class="language-ts">// foo.d.ts
export { default } from './dist/foo.d.ts'
export * from './dist/foo.d.ts'
</code></pre>
<p>Which solve the problem, but also making your root directory quite messy.</p>
<p>Until <a href="https://github.com/tmkx">@tmkx</a> <a href="https://github.com/antfu/unplugin-auto-import/pull/120">shared me</a> this solution:</p>
<pre><code class="language-json">{
  &quot;typesVersions&quot;: {
    &quot;*&quot;: {
      &quot;*&quot;: [
        &quot;./dist/index.d.ts&quot;,
        &quot;./dist/*&quot;
      ]
    }
  }
}
</code></pre>
<p>Good day! <a href="https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#version-selection-with-typesversions">Reference</a></p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Match Quotes in Pairs]]></title>
            <link>https://antfu.me/posts/match-quotes-in-pairs</link>
            <guid isPermaLink="true">https://antfu.me/posts/match-quotes-in-pairs</guid>
            <pubDate>Sun, 28 Feb 2021 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In JavaScript, single quotes('') and double quotes(&quot;&quot;) are interchangeable. With ES6, we now even have backticks(``) for template literals. When you want to write a quick script to find all the strings without introducing a heavy parser, you may think about using RegExp. For example, you can have:</p>
<pre><code class="language-ts">/['&quot;`](.*?)['&quot;`]/g
</code></pre>
<p>It works for most of the case, but not for mixed quotes:</p>
<pre><code class="language-ts">'const a = &quot;Hi, I\'m Anthony&quot;'.match(/['&quot;`](.*)['&quot;`]/)[1] // &quot;Hi, I&quot;
</code></pre>
<p>You have to make sure the starting quote and ending quote are the same type. Initially I thought it was impossible to do it in RegExp, or we have to do like this:</p>
<pre><code class="language-ts">/'(.*?)'|&quot;(.*?)&quot;|`(.*?)`/g
</code></pre>
<p>That's definitely a bad idea as it makes you duplicated your notations. Until I found this solution:</p>
<pre><code class="language-ts">/(['&quot;`])(.*?)\1/g
</code></pre>
<p><code>\1</code> is a <a href="https://www.regular-expressions.info/backref.html">Backreferences</a> to your first group, similarly you can have <code>\2</code> for the second group 2 and <code>\3</code> for the third, you got the idea. This is exactly what I need! Take it a bit further, to exclude the backslash escaping, now we can have a much reliable RegExp for extracting quoted texts from any code.</p>
<pre><code class="language-ts">/([&quot;'`])((?:\\\1|(?:(?!\1)|\n|\r).)*?)\1/g
</code></pre>
<p>You can find it running in action on my <a href="https://github.com/windicss/vite-plugin-windicss/blob/571c1d9d9bcbf699038614e6f9fab0ddc62b959b/packages/plugin-utils/src/regexes.ts#L1"><code>vite-plugin-windicss</code></a>.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Match Chinese Characters]]></title>
            <link>https://antfu.me/posts/match-chinese-characters</link>
            <guid isPermaLink="true">https://antfu.me/posts/match-chinese-characters</guid>
            <pubDate>Thu, 25 Feb 2021 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>When you need to detect if a string contains Chinese characters, you would commonly think about doing it will RegExp, or grab a ready-to-use package on npm.</p>
<p>If you Google it, you are likely end up with <a href="https://stackoverflow.com/a/21113538">this solution</a>:</p>
<pre><code class="language-ts">/[\u4E00-\u9FCC\u3400-\u4DB5\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|[\uD86A-\uD86C][\uDC00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D]/
</code></pre>
<p>It works, but a bit dirty. Fortunately, I found <a href="https://stackoverflow.com/a/61151122">a much simpler solution</a> today:</p>
<pre><code class="language-ts">/\p{Script=Han}/u
</code></pre>
<pre><code class="language-ts">!!'你好'.match(/\p{Script=Han}/u) // true
</code></pre>
<p>It's called <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes">Unicode property escapes</a> and already available in <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#browser_compatibility">Chrome 64, Firefox 79, Safari 11.1 and Node.js 10</a>.</p>
<p><a href="https://www.regular-expressions.info/unicode.html">All available scripts here</a>.</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Netlify Redirects]]></title>
            <link>https://antfu.me/posts/netlify-redirects</link>
            <guid isPermaLink="true">https://antfu.me/posts/netlify-redirects</guid>
            <pubDate>Sat, 20 Feb 2021 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h2>Domains Redirects</h2>
<p>On <a href="https://netlify.com">Netlify</a>, you can setup multiple domains for a site. When you add a custom domain, the <code>xxx.netlify.app</code> is still accessible. Which would potentially cause some confusion to users. In that way, you can setup the redirection in your <code>netlify.toml</code> file, for example:</p>
<pre><code class="language-toml">[[redirects]]
from = &quot;https://vueuse.netlify.app/*&quot;
to = &quot;https://vueuse.org/:splat&quot;
status = 301
force = true
</code></pre>
<ul>
<li><code>*</code> and <code>:splat</code> mean it will redirect all the sub routes as-is to the new domain.</li>
<li><code>force = true</code> specifying it will always redirect even if the request page exists.</li>
</ul>
<h2>Site Names Redirects</h2>
<p><em>2021/02/20</em></p>
<p>Unlike domain redirection, sometimes you would need to rename the Netlify subdomain name (a.k.a site name), for example <code>xxx.netlify.app</code> to <code>yyy.netlify.app</code>. After you do the rename, people visiting <code>xxx.netlify.app</code> will receive a 404. And since you no longer have controls over <code>xxx.netlify.app</code>, you can't just setup a redirect in your new site.</p>
<p>A solution here is to create a new site with your original name <code>xxx</code> and upload the redirection rules. In this case, we can have <code>netlify.toml</code> like this:</p>
<pre><code class="language-toml">[[redirects]]
from = &quot;*&quot;
to = &quot;https://yyy.netlify.app/:splat&quot;
status = 301
force = true
</code></pre>
<p>Note you don't have to link a repo to that, Netlify offers a great feature that <a href="https://app.netlify.com/drop">let you drag and drop for static files and serve as a site</a>. So you can just save <code>netlify.toml</code> and upload it, rename the site to your original name. The redirection is done!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Rewrite in Vite]]></title>
            <link>https://antfu.me/posts/rewrite-in-vite</link>
            <guid isPermaLink="true">https://antfu.me/posts/rewrite-in-vite</guid>
            <pubDate>Sun, 31 Jan 2021 16:00:00 GMT</pubDate>
            <description><![CDATA[My site is now powered by Vite!]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<p>The page you are looking at is now powered by <a href="https://github.com/vitejs/vite">Vite</a>. This is something I want to do for a long while since Vite came out, and it's finally done. As I have mentioned in my <a href="/posts/new-house">first blog post</a>, it was powered <a href="https://gridsome.org/">Gridsome</a> using Vue 2. With this overhaul, I can now take full advantage of Vue 3 and the <a href="https://v3.vuejs.org/guide/composition-api-introduction.html">Composition API</a> with the new <a href="https://github.com/vuejs/rfcs/pull/227"><code>&lt;script setup&gt;</code> SFC style</a>.</p>
<p>The reason for it taking me so long to do this is because I am busy (enjoy) doing <a href="https://americanexpress.io/yak-shaving">yak shaving</a>, for the tools I need to build this site.</p>
<h3>Fundamentals</h3>
<p>It begins with me trying to improve the DX using icons in <a href="/posts/journey-with-icons">this post</a>. At that time, Vue 3 just into RC, and Vite didn't reach 1.0 yet. Hearing a lot of how good Vue 3 and Vite are, I decided to give them a try on building the icon site I want to build for a long time. Since Vite is such a brand new thing, there aren't many tools/plugins out there, the ecosystem was way far from what Webpack has. I took that as a chance for me to dive deep into how Vite works while doing some contributions the ecosystem. Here is a few tools I made while building the app <a href="https://github.com/antfu/icones">Icônes</a>:</p>
<ul>
<li><a href="https://github.com/antfu/vite-plugin-components">vite-plugin-components</a> - On-demand components auto importing for Vite.</li>
<li><a href="https://github.com/antfu/vite-plugin-pwa">vite-plugin-pwa</a> - Zero-config PWA for Vite.</li>
<li><a href="https://github.com/antfu/purge-icons">vite-plugin-purge-icons</a> - Bundles icons on demand, with a Vite plugin.</li>
</ul>
<p>Also found some awesome tools form the community:</p>
<ul>
<li><a href="https://github.com/iconify/iconify">Iconify</a> - Universal icon framework, by <a href="https://github.com/cyberalien">@cyberalien</a>.</li>
<li><a href="https://github.com/brattonross/vite-plugin-voie">vite-plugin-voie</a> - File system based routing for Vite, by <a href="https://github.com/brattonross">@brattonross</a>.</li>
<li><a href="https://github.com/hannoeru/vite-plugin-pages">vite-plugin-pages</a> - Another file system based route generator, by <a href="https://github.com/hannoeru">@hannoeru</a>.</li>
</ul>
<p>With them, I got the fundamentals of a Vite project setup. Nuxt-liked file-based routing and component auto importing. I was quite satisfied with it as I could focus more on the content and logic rather than getting distracted by the routes setup and component registration.</p>
<p>I also learned <a href="https://tailwindcss.com/">Tailwind CSS</a> as a replacement of the missing UI component libraries for Vue 3. It turns out that I really enjoy Tailwind's way of rapid prototyping. As I got more control over styling things, it makes me think more about the design rather than just applying the default theme of the components library I use.</p>
<h3>Dark Mode</h3>
<p>Dark mode is supported as an experimental feature in Tailwind CSS v1.8 and shipped in v2.0. It supports two modes for you to choose from - <code>media</code> and <code>class</code>. <code>media</code> is something that works out-of-box, it changes based on users' system preference. But the limitation is that you can't toggle it manually as you want. So I went with <code>class</code> mode where I have more controls over it. But that also means I would need to implement the toggling logic myself.</p>
<p>With the power of Vue's Composition API, I am able to combine the best parts of them - reactive to the system's preference while being able to override manually.</p>
<pre><code class="language-ts">import { usePreferredDark, useStorage } from '@vueuse/core'

const preferredDark = usePreferredDark()
const colorSchema = useStorage('color-schema', 'auto')

export const isDark = computed({
  get() {
    return colorSchema.value === 'auto'
      ? preferredDark.value
      : colorSchema.value === 'dark'
  },
  set(v: boolean) {
    if (v === preferredDark.value)
      colorSchema.value = 'auto'
    else
      colorSchema.value = v ? 'dark' : 'light'
  },
})

watch(
  isDark,
  v =&gt; document.documentElement.classList.toggle('dark', v),
  { immediate: true },
)
</code></pre>
<p>Click it to try 👇🏼</p>
<ToggleTheme class="text-2xl pb-2 pt-1"/>
<p>If you would like to use it in your own apps, I also extract the logic above into <a href="https://vueuse.org/core/useDark/"><code>useDark()</code> in VueUse</a>. Where you can simply use like this:</p>
<pre><code class="language-ts">import { useDark, useToggle } from '@vueuse/core'

const isDark = useDark()
const toggleDark = useToggle(isDark)
</code></pre>
<h3>Markdown</h3>
<p>After building Icônes, I started working on the <a href="http://codecember.ink/">Codecember</a> project with <a href="https://blog.matsu.io/about">@octref</a>, an initiative of learning and creating generative arts in December. With the spirit of dogfooding, we chosen Vite for building the site. In Codecember we will need to have a prompt every day with some texts, code snippets, and demos. This comes with the problem that Vite does not have a plugin for handling markdown files at that moment, so of course, I made one myself.</p>
<ul>
<li><a href="https://github.com/antfu/vite-plugin-vue-markdown">vite-plugin-vue-markdown</a> - Markdown for Vite.</li>
</ul>
<p>Basically, it uses <a href="https://markdown-it.github.io/"><code>markdown-it</code></a> to transform markdown into HTML and feed it into Vue's template compiler. As the generated template is handled by Vue, we can easily support Vue components inside Markdown.</p>
<h3>Syntax Highlighting</h3>
<p>Getting syntax highlight works in dark mode isn't an easy task as well. <a href="https://github.com/shikijs/shiki">Shiki</a> inlined all the colors into the HTML so you would not be bored by the CSS namespace pollution, but that also means it will be really hard to get the colors aware of your global color scheme. <a href="https://prismjs.com/">Prism</a> on the other hand, uses the classes combining the CSS theme to do the job. It's easier to merge two color schemes and make them aware of the <code>dark</code> trigger. The bad thing is, themes are often wrote by different authors with different styles of coloring and styling things. Sometimes, even the font and spacing could be different across different themes. If you ever ran into a similar situation, you should know what I mean. If you don't (lucky you!), see <a href="https://github.com/PrismJS/prism-themes/tree/main/themes">Prism's themes collection</a>(<a href="https://github.com/PrismJS/prism-themes/blob/c24ddffde2737293d9b2df7dc59939d527648863/themes/prism-vs.css#L9"><code>prism-vs.css</code></a> and <a href="https://github.com/PrismJS/prism-themes/blob/c24ddffde2737293d9b2df7dc59939d527648863/themes/prism-vsc-dark-plus.css#L6"><code>prism-vsc-dark-plus.css</code></a> for example).</p>
<p>Fight with them for a while you might be able to ease the misalignment eventually. But what if we can have a smarter way to do this?</p>
<ul>
<li><a href="https://github.com/antfu/prism-theme-vars">prism-theme-vars</a> - A customizable Prism.js theme using CSS variables.</li>
</ul>
<p>Instead of dealing with the lengthy CSS theme, now you can have one in less than 20 lines of CSS variables. For example:</p>
<pre><code class="language-css">@import 'prism-theme-vars/base.css';

:root {
  --prism-foreground: #393a34;
  --prism-background: #fbfbfb;
  --prism-comment: #b8c4b8;
  --prism-string: #c67b5d;
  --prism-literal: #3a9c9b;
  --prism-keyword: #248459;
  --prism-function: #849145;
  --prism-deleted: #a14f55;
  --prism-class: #2b91af;
  --prism-builtin: #a52727;
  --prism-property: #ad502b;
  --prism-namespace: #c96880;
  --prism-punctuation: #8e8f8b;
}
</code></pre>
<p>To have it supports dark mode is extremely simple as well:</p>
<pre><code class="language-css">html:not(.dark) {
  --prism-foreground: #393a34;
  --prism-background: #f8f8f8;
  --prism-comment: #758575;
  --prism-namespace: #444444;
  --prism-string: #bc8671;
  --prism-punctuation: #80817d;
  --prism-literal: #36acaa;
  --prism-keyword: #248459;
  --prism-function: #849145;
  --prism-deleted: #9a050f;
  --prism-class: #2b91af;
  --prism-builtin: #800000;
}

html.dark {
  --prism-foreground: #d4d4d4;
  --prism-background: #1e1e1e;
  --prism-namespace: #aaaaaa;
  --prism-comment: #758575;
  --prism-namespace: #444444;
  --prism-string: #ce9178;
  --prism-punctuation: #d4d4d4;
  --prism-literal: #36acaa;
  --prism-keyword: #38a776;
  --prism-function: #dcdcaa;
  --prism-deleted: #9a050f;
  --prism-class: #4ec9b0;
  --prism-builtin: #d16969;
}
</code></pre>
<p>That's all. You can also play with the themes in the <a href="https://prism-theme-vars.netlify.app/">Playground</a> and make some your own within 5 mins. I created my first code theme in my life using it, which is also exactly what you are looking at :)</p>
<h3>Serve-Side Generatation (SSG)</h3>
<p>While Codecember is more like a site than an app, we would need to do some server-side generation to improve our <a href="https://searchengineland.com/guide/what-is-seo">SEO</a>. Read quite a lot of code from <a href="https://github.com/vuejs/vitepress">VitePress</a>, I came up with this plugin:</p>
<ul>
<li><a href="https://github.com/antfu/vite-ssg">vite-ssg</a> - Server-side generation for Vite.</li>
</ul>
<p>The idea here is fairly simple: bundle the app entry, then for each route, dump the app using APIs from the <a href="https://github.com/vuejs/core/tree/main/packages/server-renderer"><code>@vue/server-renderer</code></a> package. Simplified code here:</p>
<pre><code class="language-ts">import { renderToString } from '@vue/server-renderer'

const createApp = required('dist-ssr/app.js')

await Promise.all(
  routes.map(async (route) =&gt; {
    const { app, router, head } = createApp(false)

    router.push(route)
    await router.isReady()

    const appHTML = await renderToString(app)
    const renderedHTML = renderHTML(indexHTML, appHTML)

    await fs.writeFile(`${route}.html`, renderedHTML, 'utf-8')
  })
)
</code></pre>
<p>The full code can be found <a href="https://github.com/antfu/vite-ssg/blob/fa256449923e05e55bf15dcf4747d517bc22e33a/src/node/build.ts#L94-L104">here</a>.</p>
<p>With the <a href="https://github.com/vueuse/head">@vueuse/head</a> package made by <a href="https://github.com/egoist">@egoist</a>, I made the document head/meta manipulation in SSG with ease. Combining with <a href="https://github.com/antfu/vite-plugin-vue-markdown">vite-plugin-vue-markdown</a>, you can even use the frontmatter to set the meta (title, description, og:image, etc.).</p>
<pre><code class="language-html">&lt;script setup&gt;
  import { useHead } from '@vueuse/head'

  useHead({
    title: 'Website Title',
    meta: [
      {
        name: 'description',
        content: 'Website description',
      },
    ],
  })
&lt;/script&gt;
</code></pre>
<h3>The Vite Template</h3>
<p>I found myself making small web apps frequently. Setting up plugins and configs for Vite kinda becomes the bottleneck for me to make my idea landded. So combining with those tools I am using, I made an opinionated template for myself but unexpectedly got quite some good feedback:</p>
<ul>
<li><a href="https://github.com/antfu/vitesse">Vitesse</a> - Opinionated Vite Starter Template</li>
</ul>
<h2>This Website</h2>
<p>This site is <strong>made from <a href="https://github.com/antfu/vitesse">Vitesse</a> combining with all the tools I mentioned above</strong>. To be honest, even making a static site generator right is something hard to me, not to mention that most of the hard parts are already handled by Vite. I am really happy to see so many things I have learned and crafted along the way. And glad I can make these contributions to the Vite ecosystem, that someone could find my work useful for building their apps.</p>
<h2>Thanks</h2>
<p>I can't make all these happened without the help/support from the great community, thank y'all!</p>
<p>Also want to have some special thanks to people made significant contributions towards these projects 🙌 (A-Z)</p>
<ul>
<li><a href="https://github.com/hannoeru">@hannoeru</a></li>
<li><a href="https://github.com/matias-capeletto">@matias-capeletto</a></li>
<li><a href="https://github.com/privatenumber">@privatenumber</a></li>
<li><a href="https://github.com/sibbng">@sibbng</a></li>
</ul>
<p>Appreciation to my sponsors as well for supporting my works:</p>
<p align="center">
  <a href="https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg">
    <img src='https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg'>
  </a>
</p>
<p>And thank you for reading through!</p>
<p><strong>This site is now open sourced at <a href="https://github.com/antfu/antfu.me">antfu/antfu.me</a></strong></p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Destructuring... with object or array?]]></title>
            <link>https://antfu.me/posts/destructuring-with-object-or-array</link>
            <guid isPermaLink="true">https://antfu.me/posts/destructuring-with-object-or-array</guid>
            <pubDate>Wed, 21 Oct 2020 16:00:00 GMT</pubDate>
            <description><![CDATA[Prefer object destructure or array? Can we support both?]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<blockquote>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment">Destructuring</a> is a JavaScript language feature introduced in ES6 which I would assume you already familiar with it before moving on.</p>
</blockquote>
<p>We see it quite useful in many scenarios, for example, value swapping, named arguments, objects shallow merging, array slicing, etc. Today I would like to share some of my immature thoughts on &quot;destructuring&quot; in some web frameworks.</p>
<p>I am a Vue enthusiast for sure and I wrote a lot of my apps using it. And I did write React a while for my previous company <s>reluctantly</s>. As the Vue 3.0 came out recently, its exciting Composition API provides quite similar abilities for abstracting. Inspired by <a href="https://github.com/streamich/react-use">react-use</a>, I wrote a composable utility collection library early this year called <a href="https://github.com/antfu/vueuse">VueUse</a>.</p>
<p>Similar to React hooks, Vue's composable functions will take some arguments and returns some data and functions. JavaScript is just like other C-liked programming languages - only one return value is allowed. So a workaround for returning multiple values, we would commonly wrap them with an array or an object, and then destructure the returned arrays/objects. As you can already see, we are having two different philosophies here, using arrays or objects.</p>
<h2>Destructuring Arrays / Tuples</h2>
<p>In React hooks, it's a common practice to use array destructuring. For example, built-in functions:</p>
<pre><code class="language-ts">const [counter, setCounter] = useState(0)
</code></pre>
<p>Libraries for React hooks would natural pick the similar philosophy, for example <a href="https://github.com/streamich/react-use">react-use</a>:</p>
<pre><code class="language-ts">const [on, toggle] = useToggle(true)
const [value, setValue, remove] = useLocalStorage('my-key', 'foo')
</code></pre>
<p>The benefits of array destructuring is quite straightforward - you get the freedom to set the variable names with the clean looking.</p>
<h2>Destructuring Objects</h2>
<p>Instead of returning the getter and setter in React's <code>useState</code>, in Vue 3, a <code>ref</code> is created combining the getter and setter inside the single object. Naming is simpler and destructuring is no longer needed.</p>
<pre><code class="language-ts">// React
const [counter, setCounter] = useState(0)
console.log(counter) // get
setCounter(counter + 1) // set

// Vue 3
const counter = ref(0)
console.log(counter.value) // get
counter.value++ // set
</code></pre>
<p>Since we don't need to rename the same thing twice for getter and setter like React does, in <a href="https://github.com/antfu/vueuse">VueUse</a>, I implemented most of the functions with object returns, like:</p>
<pre><code class="language-ts">const { x, y } = useMouse()
</code></pre>
<p>Using objects gives users more flexibility like</p>
<pre><code class="language-ts">// no destructing, clear namespace
const mouse = useMouse()

mouse.x
</code></pre>
<pre><code class="language-ts">// use only part of the value
const { y } = useMouse()
</code></pre>
<pre><code class="language-ts">// rename things
const { x: mouseX, y: mouseY } = useMouse()
</code></pre>
<p>While it's been good for different preferences and named attributes can be self-explaining, the renaming could be somehow verbose than array destructuring.</p>
<h2>Support Both</h2>
<p>What if we could support them both? Taking the advantages on each side and let users decide which style to be used to better fit their needs.</p>
<p>I did saw one library supports such usage once but I can't recall which. However, this idea buried in mind since then. And now I am going to experiment it out.</p>
<p>My assumption is that it returns an object with both behaviors of <code>array</code> and <code>object</code>. The path is clear, either to make an <code>object</code> like <code>array</code> or an <code>array</code> like <code>object</code>.</p>
<h3>Make an object behaves like an array</h3>
<p>The first possible solution comes up to my mind is to make an object behaves like an array, as you probably know, arrays are actually objects with number indexes and some prototypes. So the code would be like:</p>
<pre><code class="language-ts">const data = {
  foo: 'foo',
  bar: 'bar',
  0: 'foo',
  1: 'bar',
}

let { foo, bar } = data
let [foo, bar] = data // ERROR!
</code></pre>
<p>But when we destructure it as an array, it will throw out this error:</p>
<pre><code class="language-txt">Uncaught TypeError: data is not iterable
</code></pre>
<p>Before we working on how to make an object iterable, let's try the other direction first.</p>
<h3>Make an array behaves like an object</h3>
<p>Since arrays are objects, we should be able to extend it, like</p>
<pre><code class="language-ts">const data = ['foo', 'bar']
data.foo = 'foo'
data.bar = 'bar'

let [foo, bar] = data
let { foo, bar } = data
</code></pre>
<p>This works and we can call it a day now! However, if you are a perfectionist, you will find there is an edge case not be well covered. If we use the rest pattern to retrieve the remaining parts, the number indexes will unexpectedly be included in the rest object.</p>
<pre><code class="language-ts">const { foo, ...rest } = data
</code></pre>
<p><code>rest</code> will be:</p>
<!-- eslint-skip -->
<pre><code class="language-ts">{
  bar: 'bar',
  0: 'foo',
  1: 'bar'
}
</code></pre>
<h3>Iterable Object</h3>
<p>Let's go back to our first approach to see if we can make an object iterable. And luckily, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator"><code>Symbol.iterator</code></a> is designed for the task! The document shows exactly the usage, doing some modification and we get this:</p>
<pre><code class="language-ts">const data = {
  foo: 'foo',
  bar: 'bar',
  * [Symbol.iterator]() {
    yield 'foo'
    yield 'bar'
  },
}

let { foo, bar } = data
let [foo, bar] = data
</code></pre>
<p>It works well but the <code>Symbol.iterator</code> will still be included in the rest pattern.</p>
<!-- eslint-skip -->
<pre><code class="language-ts">let { foo, ...rest } = data

// rest
{
  bar: 'bar',
  Symbol(Symbol.iterator): ƒ*
}
</code></pre>
<p>Since we are working on objects, it shouldn't be hard to make some properties not enumerable. By using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty"><code>Object.defineProperty</code></a> with <code>enumerable: false</code>:</p>
<pre><code class="language-ts">const data = {
  foo: 'foo',
  bar: 'bar',
}

Object.defineProperty(data, Symbol.iterator, {
  enumerable: false,
  * value() {
    yield 'foo'
    yield 'bar'
  },
})
</code></pre>
<p>Now we are successfully hiding the extra properties!</p>
<pre><code class="language-ts">const { foo, ...rest } = data

// rest
{
  bar: 'bar'
}
</code></pre>
<h2>Generator</h2>
<p>If you don't like the usage of generators, we can implement it with pure functions, following <a href="https://itnext.io/introduction-to-javascript-iterator-eac78849e0f7#:~:text=An%20iterator%20is%20an%20object,new%20iterator%20for%20each%20call">this article</a>.</p>
<pre><code class="language-ts">Object.defineProperty(clone, Symbol.iterator, {
  enumerable: false,
  value() {
    let index = 0
    const arr = [foo, bar]
    return {
      next: () =&gt; ({
        value: arr[index++],
        done: index &gt; arr.length,
      })
    }
  }
})
</code></pre>
<h2>TypeScript</h2>
<p>To me, it's meaningless if we could not get proper TypeScript support on this. Surprisingly, TypeScript support such usage almost out-of-box. Just simply use the <a href="https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#intersection-types"><code>&amp;</code> operator</a> to make insertion of the object and array type. Destructuring will properly infer the types in both usages.</p>
<pre><code class="language-ts">type Magic = { foo: string, bar: string } &amp; [ string, string ]
</code></pre>
<h2>Take Away</h2>
<p>Finally, I made it a general function to merge arrays and objects intro the isomorphic destructurable. You can just copy the TypeScript snippet below to use it. Thanks for reading through!</p>
<blockquote>
<p>Please note this does NOT support IE11. More details: <a href="https://caniuse.com/?search=Symbol.iterator">Supported browers</a></p>
</blockquote>
<pre><code class="language-ts">function createIsomorphicDestructurable&lt;
  T extends Record&lt;string, unknown&gt;,
  A extends readonly any[]
&gt;(obj: T, arr: A): T &amp; A {
  const clone = { ...obj }

  Object.defineProperty(clone, Symbol.iterator, {
    enumerable: false,
    value() {
      let index = 0
      return {
        next: () =&gt; ({
          value: arr[index++],
          done: index &gt; arr.length,
        })
      }
    }
  })

  return clone as T &amp; A
}
</code></pre>
<h4>Usage</h4>
<pre><code class="language-ts">const foo = { name: 'foo' }
const bar: number = 1024

const obj = createIsomorphicDestructurable(
  { foo, bar } as const,
  [foo, bar] as const
)

let { foo, bar } = obj
let [foo, bar] = obj
</code></pre>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
            <enclosure url="https://antfu.me/images/destructuring.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Watch with @vue/reactivity]]></title>
            <link>https://antfu.me/posts/watch-with-reactivity</link>
            <guid isPermaLink="true">https://antfu.me/posts/watch-with-reactivity</guid>
            <pubDate>Fri, 18 Sep 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[A brief intro of how it works and a guide to implementing the (missing) `watch` on your own.]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<p><a href="https://twitter.com/antfu7/status/1298667080804233221">As you probably know</a>, the things I excited most in Vue 3 are the <a href="https://v3.vuejs.org/guide/composition-api-introduction.html">Composition API</a> and the <a href="https://v3.vuejs.org/guide/reactivity.html">reactivity system</a>. With the Composition API we can reuse logics and states across components or even apps. What's better? The underhood reactivity system is decoupled from Vue, which means you can use it almost everywhere, even without UI.</p>
<p>Here are some proof of concepts for using the reactivity system outside of Vue:</p>
<ul>
<li>
<p><a href="https://github.com/yyx990803/vue-lit"><code>@vue/lit</code></a> is a minimal framework wrote by Evan combining <a href="https://github.com/vuejs/core/tree/main/packages/reactivity"><code>@vue/reactivity</code></a> and <a href="https://lit-html.polymer-project.org/"><code>lit-html</code></a>. It can run directly in browsers, with the almost identical experience as Vue Composition API.</p>
</li>
<li>
<p><a href="https://github.com/antfu/reactivue"><code>ReactiVue</code></a> ports Vue Composition API to React. It also provides React's lifecycles in the Vue style.</p>
</li>
</ul>
<p>Furthermore, you can even use Vue's libraries in them. Tested with <a href="https://github.com/antfu/vueuse"><code>VueUse</code></a> and <a href="https://github.com/posva/pinia"><code>pinia</code></a> in <a href="https://github.com/antfu/reactivue"><code>ReactiVue</code></a>, and they just work. You can find <a href="https://github.com/antfu/reactivue#using-vues-libraries">more details and examples here</a>.</p>
<p>I am also experimenting more possibility of Vue reactivity in other scenarios, for example <a href="https://twitter.com/antfu7/status/1305313110903779330?s=20">reactive file system</a>, in a project called <code>tive</code>. It's currently a WIP private repo, but keep tuned, I get more to come 😉!</p>
<h2>Understanding <code>@vue/reactivity</code></h2>
<p>&quot;reactive objects&quot; returned by <code>ref()</code> or <code>reactive()</code> are actually <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxies</a>. Those proxies will trigger some actions to track the changes on properties accessing or writing.</p>
<p>For a simplified example,</p>
<pre><code class="language-ts">function reactive(target) {
  return new Proxy(target, {
    get(target, prop, receiver) {
      track(target, prop)
      return Reflect.get(...arguments) // get the original data
    },
    set(target, key, value, receiver) {
      trigger(target, key)
      return Reflect.set(...arguments) // set the original data
    }
  })
}

const obj = reactive({
  hello: 'world'
})

console.log(obj.hello) // `track()` get called
obj.hello = 'vue' // `trigger()` get called
</code></pre>
<p>So in this way, vue can be notified when those properties get accessed or when they be modified.</p>
<blockquote>
<p>For more detailed explanations, check out the <a href="https://v3.vuejs.org/guide/reactivity.html#what-is-reactivity">official docs</a></p>
</blockquote>
<h3>Computed</h3>
<p>Since we are able to know those events, we can start diving into the <code>computed</code> which is where the &quot;reactive&quot; magic start shining.</p>
<p><code>computed</code> is like a getter that auto collects the reactive dependencies source and auto re-evaluate when they get changed.</p>
<p>For example,</p>
<pre><code class="language-ts">const counter = ref(1)
const multiplier = ref(2)

const result = computed(() =&gt; counter.value * multiplier.value)

console.log(result.value) // 2
counter.value += 1
console.log(result.value) // 4
</code></pre>
<p>To know how the <code>computed</code> work, we need to dig into the lower level API <code>effect</code> first.</p>
<h2>Effect</h2>
<p><code>effect</code> is a new API introduced in Vue 3. Underneath, it's the engine powers the &quot;reactivity&quot; in <code>computed</code> and <code>watch</code>. For the most of the time, you don't need to directly use it. But knowing it well helps you understand the reactivity system much easier.</p>
<p><code>effect</code> takes the first argument as the <code>getter</code> and a second argument for the options. The <code>getter</code> is the function that collect its deps on each run via their <code>track()</code> hooks. The field <code>scheduler</code> in options provides a way to invoke a custom function when the deps change.</p>
<p>So basically, you can write a simple <code>computed</code> on your own like:</p>
<pre><code class="language-ts">function computed(getter) {
  let value
  let dirty = true

  const runner = effect(getter, {
    lazy: true,
    scheduler() {
      dirty = true // deps changed
    }
  })

  // return should be a `Ref` in real world, simplified here
  return {
    get value() {
      if (dirty) {
        value = runner() // re-evaluate
        dirty = false
      }
      return value
    }
  }
}
</code></pre>
<p>If you really interested in how it works in Vue, check out <a href="https://github.com/vuejs/core/blob/main/packages/reactivity/src/computed.ts">the source code here</a></p>
<h2>Build yourself a <code>watch</code></h2>
<p>We have done the most important APIs in <code>@vue/reactivity</code> now, which is <code>ref</code> <code>reactive</code> <code>effect</code> <code>computed</code>.</p>
<p>Oh wait, we are missing the <code>watch</code> here!</p>
<!-- eslint-skip -->
<pre><code class="language-js">import { watch } from '@vue/reactivity' // does NOT exist!
</code></pre>
<p>If you take a look at Vue 3's source code, you will find that <code>watch</code> is actually <a href="https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiWatch.ts">implemented in <code>@vue/runtime-core</code></a>, along with the Vue's component model and lifecycles. The main reason for this is that <code>watch</code> is deep bound with the component's lifecycles (auto dispose, invalidate, etc.). But it shouldn't be the thing to keep you from using it outside of Vue.</p>
<p>Let's implement the <code>watch</code> our own!</p>
<h3>The Basic</h3>
<p>Let's take a look at Vue's watch interface first</p>
<pre><code class="language-ts">const count = ref(0)

watch(
  () =&gt; count.value,
  (newValue) =&gt; {
    console.log(`count changed to: ${newValue}!`)
  }
)

count.value = 2
// count changed to: 2!
</code></pre>
<p>With the knowledge of <code>effect</code>, it's quite straight forward to implement</p>
<pre><code class="language-ts">function watch(getter, fn) {
  const runner = effect(getter, {
    lazy: true,
    scheduler: fn
  })

  // a callback function is returned to stop the effect
  return () =&gt; stop(runner)
}
</code></pre>
<p>Watch is lazy by default in Vue, you can add the third options to give control to the users.</p>
<h3>Watch for Ref</h3>
<p>You may also notice that the Vue's <code>watch</code> also allows passing the ref directly to it.</p>
<pre><code class="language-ts">watch(
  count,
  () =&gt; { /* onChanged */ }
)
</code></pre>
<p>For that, just wrap it into a getter will do</p>
<pre><code class="language-ts">function watch(source, fn) {
  const getter = isRef(source)
    ? () =&gt; source.value
    : source

  const runner = effect(getter, {
    lazy: true,
    scheduler: fn
  })

  return () =&gt; stop(runner)
}
</code></pre>
<h3>Watch Deeply</h3>
<p>One other great feature about <code>watch</code> is that it allows you to watch on deep changes.</p>
<pre><code class="language-ts">const state = reactive({
  info: {
    name: 'Anthony',
  }
})

watch(state, () =&gt; console.log('changed!'), { deep: true })

state.info.name = 'Anthony Fu'
// changed!
</code></pre>
<p>To implement this feature, you need to collect the <code>track()</code> events on every nested property. We can achieve that with a <code>traverse</code> function.</p>
<pre><code class="language-ts">function traverse(value, seen = new Set()) {
  if (!isObject(value) || seen.has(value))
    return value

  seen.add(value) // prevent circular reference
  if (isArray(value)) {
    for (let i = 0; i &lt; value.length; i++)
      traverse(value[i], seen)
  }
  else {
    for (const key of Object.keys(value))
      traverse(value[key], seen)
  }
  return value
}

function watch(source, fn, { deep, lazy = true }) {
  let getter = isRef(source)
    ? () =&gt; source.value
    : isReactive(source)
      ? () =&gt; source
      : source

  if (deep)
    getter = () =&gt; traverse(getter())

  const runner = effect(getter, {
    lazy,
    scheduler: fn
  })

  return () =&gt; stop(runner)
}
</code></pre>
<p>Done! The thing left to do is to polish, adding overloads to make it more flexible, add more options to get better control, and handle some edge cases. Then you should get yourself a good start for using a custom <code>watch</code>!</p>
<h2>Lifecycles</h2>
<p>In Vue, <code>computed</code> and <code>watch</code> will automatically bind their <code>effect</code> runner to the current component instance. When the component get unmounted, the effects bond to it will be auto disposed. More specially, you can read <a href="https://github.com/vuejs/core/blob/985bd2bcb5fd8bccd1c15c8c5d89a6919fd73922/packages/runtime-core/src/apiWatch.ts#L294">the source code here</a>.</p>
<p>Since we don't have an instance, if you want to stop those effects, you have to do them manually. When you have multiple effects in used, to stop them together, you have to manually collect them together. One easier way is to mock similar lifecycles like Vue. This requires some amount of works, I will explain that in another blog post. Please keep tuned.</p>
<h2>Take Away</h2>
<p>Thanks for reading! And hope it is helpful for you to understand and better play with the Vue reactivity system. If you want to have the <code>watch</code> outside of Vue, I made one for you (much more robust than the examples above for sure).</p>
<pre class="block language-bash">
npm i <a href='https://github.com/antfu/vue-reactivity-watch' target='_blank'>@vue-reactivity/watch</a>
</pre>
<p>Have fun ;P</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Journey with Icons]]></title>
            <link>https://antfu.me/posts/journey-with-icons</link>
            <guid isPermaLink="true">https://antfu.me/posts/journey-with-icons</guid>
            <pubDate>Sun, 16 Aug 2020 16:00:00 GMT</pubDate>
            <description><![CDATA[To solve the pain I faced in using icons for the web, I built several tools to make the DX better.]]></description>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<blockquote>
<p>Update Sep. 2021: <a href="/posts/journey-with-icons-continues">Journey with Icons Continues</a></p>
</blockquote>
<h3>TL;DR</h3>
<p>To solve the pain I faced in using icons for the web, I built the following tools to make the DX better.</p>
<h4>Apps</h4>
<ul>
<li><a href="https://github.com/antfu/icones">Icônes</a> - Icon Explorer with Instant Fuzzy searching</li>
<li><a href="https://github.com/antfu/vscode-iconify">Iconify IntelliSense</a> - VS Code Extension for inline icon previewing</li>
<li><a href="https://github.com/antfu/vitesse">Vitesse</a> - An Opinionated Vite Starter Template</li>
</ul>
<h4>Tools</h4>
<ul>
<li><a href="https://github.com/antfu/purge-icons">PurgeIcons</a> - Bundles icons on demand</li>
<li><a href="https://github.com/antfu/svg-packer">SVG Packer</a> - Pack SVGs to Icon Fonts - In Browser</li>
</ul>
<p>Would be nice if you are willing to give them a try. As there are still a lot of works to be done, contributions are greatly welcome :)</p>
<h2>Journey with Icons</h2>
<p>I make websites and small web-based utilities from time to time. Every time I build them, I take care of the design by myself. Amount of the different aspects, icons always play a big role to me. <a href="https://materialdesignicons.com/">Material Design Icons</a> is the icon set I used most overtime, it has an excellent design foundation from Google and a wide range of icons maintained by the community. And the most important fact is that it has a complete tooling ecosystem - svgs with js, web fonts or even being built-in supported by Vuetify. I could just plugin it in most any kind of apps with very low effort.</p>
<p>However, if you want to try some other icon sets for different looks &amp; feels, you may not be that lucky. Many awesome icon-sets only offer SVGs for download and need to be manually imported to your projects. This could be a laborious and time-consuming work, even just preview them on your apps.</p>
<p>Fortunately, I found <a href="https://iconify.design/">Iconify</a> - a unified icons framework that offers over 6,000 icons from 80+ popular icon sets with a single CDN entry and on demand loading. The usage would be something like this:</p>
<pre><code class="language-html">&lt;!--Import Framework--&gt;
&lt;script src=&quot;https://code.iconify.design/1/1.0.7/iconify.min.js&quot;&gt;&lt;/script&gt;
</code></pre>
<pre><code class="language-html">&lt;!--Use an icon from Font Awesome--&gt;
&lt;span class=&quot;iconify&quot; data-icon=&quot;fa:home&quot;&gt;&lt;/span&gt;

&lt;!--Use another icon from Material Design Icons--&gt;
&lt;span class=&quot;iconify&quot; data-icon=&quot;mdi:flask&quot;&gt;&lt;/span&gt;
</code></pre>
<p>It's done. You get access to all the 6,000 icons with in an unfied syntax. As it's on-demand, you can switch your icon systems whenever you want without worrying about the setup or the bundle size. It's also framework independent, which means you can use it in Vue, React, Svelte, plain html or whatever you want.</p>
<p>This looks so good and the story should end here, however, it does have some limitations. As it's loaded on demand via web queries with its icon services, there will be a visible delay for icons to be loaded on the first page, especially when users have unstable connections to the Iconify servers. Also, you might have some logic to change icons with user interactions, Iconify will only start to request the icon when you actually rendered the id into the DOM. This causes some flickers on the icon switching which you possibly want to avoid.</p>
<p>The solution for this is quite straightforward, preloading the icons and the icon rendering could become synchronized. However, loading the entire icon set will impact your bundle size while manually picking what you used could be laborious and make it less flexible.</p>
<h3><a href="https://github.com/antfu/purge-icons">PurgeIcons</a></h3>
<p>Inspired by <a href="https://purgecss.com/">PurgeCSS</a>, I made the tool called <a href="https://github.com/antfu/purge-icons">PurgeIcons</a>. It statical analyzes your code and generates the <a href="https://docs.iconify.design/sources/bundles/">icon bundle</a> on-demand.</p>
<p><img src="https://user-images.githubusercontent.com/11247099/89781398-ce625a80-db45-11ea-86bf-d50471c526b7.gif" alt=""></p>
<p>Along with <a href="https://github.com/antfu/purge-icons/tree/main/packages/vite-plugin-purge-icons/README.md">the Vite plugin</a>, you can simplify import this inline in your app's entry, and the icons you use will be bundled into your code and load them synchronously.</p>
<!-- eslint-skip -->
<pre><code class="language-ts">import { createApp } from 'vue'
import App from './App.vue'

import '@purge-icons/generated' // &lt;-- This

createApp(App).mount('#app')
</code></pre>
<p>It also provides a CLI tool and plugins for <a href="https://github.com/antfu/purge-icons/tree/main/packages/purge-icons-webpack-plugin">Webpack</a> and <a href="https://github.com/antfu/purge-icons/tree/main/packages/rollup-plugin-purge-icons">Rollup</a>. More frameworks support like Vue CLI, Nuxt, Gridsome or even plain html are coming soon.</p>
<p>With it, the tooling is kinda perfect to me now - I can use any icons without any compromise in the runtime. If you want to give it a try, I also made a pre-configured start template <a href="https://github.com/antfu/vitesse">🏕 Vitesse</a> with PurgeIcons built-in.</p>
<h2>Icon Searching</h2>
<p>The tooling gets solved, here comes to my another pain for a long time - searching for icons.</p>
<p>I live in China, my network conditions are usually quite unstable for oversee connections. It often took me around 30 seconds to get the searching in <a href="https://materialdesignicons.com/">Material Design Icons</a> or <a href="https://iconify.design/">Iconify</a>. And for most of the time, you won't get the perfect icons on your first try. Repeating searching for multiple times with a huge delay is just killing me.</p>
<p>And so, I finally get some time and motivation to work on making one my own recently. Also considering this being a chance for me to try Vue 3 + Vite and to learn Tailwind CSS.</p>
<h3>Icônes</h3>
<p>By the power of Iconify, I can only ship with the icon IDs and leave the icon loading task to Iconify. In this way, searching could be done locally - instantly.</p>
<p><img src="https://github.com/antfu/icones/raw/main/screenshots/1.png" alt=""></p>
<p><img src="https://github.com/antfu/icones/raw/main/screenshots/2.png" alt=""></p>
<p>With Iconify's data collection, it can get access to all the 80+ icon sets within a single place.</p>
<p>I also made a small utility called <a href="https://github.com/antfu/svg-packer">SVG Packer</a> for Icônes. With it, you can select the icons you want and pack them into ready to used icon fonts.</p>
<p><img src="https://github.com/antfu/icones/raw/main/screenshots/5.png" alt=""></p>
<p><img src="https://github.com/antfu/icones/raw/main/screenshots/3.png" alt=""></p>
<blockquote>
<p>Tips: You can also copy the icons as Vue or React components</p>
</blockquote>
<p>Try it out if you haven't. A fully-offline electron version is also coming soon.</p>
<h2>Editor Support</h2>
<p>Browsing and searching for icons are good to me now. Now comes to the editor integration. It's actually kinda hard to know what the icons look like from their IDs. Auto-completion for IDs is also a good feature I would love to have.</p>
<h3><a href="https://github.com/antfu/vscode-iconify">Iconify IntelliSense</a> for VS Code</h3>
<p>With a lot of inspirations from the <a href="https://github.com/lukas-tr/vscode-materialdesignicons-intellisense">VS Code extension for MDI</a>, Iconify IntelliSense was born.</p>
<p><img src="https://github.com/antfu/vscode-iconify/raw/main/screenshots/preview-1.png" alt=""></p>
<p>Loading icon data and cache them on demand, it encoded svgs into data urls to be displayed as images in VS Code. With the <a href="https://code.visualstudio.com/api/references/vscode-api#DecorationRenderOptions">TextEditorDecoration</a> API, I achieve the feature to replace the icon IDs with the icon image itself in place. The icons will become visible and editable when you move the cursor around them.</p>
<p>Auto-completion with icon preview is also available. By typing the icon set id and the following delimiter colon <code>:</code>, a list of icons in the set will popup and you may continue type to do a simple search.</p>
<h3>Journey is not ended</h3>
<p>These apps and tools solved my long pain with icons. I can focus on bringing ideas to life much more efficiently. I will call it a page for no. However, the journey is not yet ended. I am still passionate about exploring how the tooling for icons could go. And also, keep polishing these tools to make them easier to use and integrate. I wish they could benefit more developers and designers and make site building more simple and pleasant.</p>
<p>Glad if you found them useful to you as well. And thanks for reading XD.</p>
<h3>Continues</h3>
<p>Enjoy the journey and the tools? Here is how it continues in Sep. 2021:</p>
<p><a href="/posts/journey-with-icons-continues">Journey with Icons Continues</a></p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Make Libraries Working with Vue 2 and 3]]></title>
            <link>https://antfu.me/posts/make-libraries-working-with-vue-2-and-3</link>
            <guid isPermaLink="true">https://antfu.me/posts/make-libraries-working-with-vue-2-and-3</guid>
            <pubDate>Wed, 01 Jul 2020 14:00:00 GMT</pubDate>
            <description><![CDATA[Try Vue Demi!]]></description>
            <content:encoded><![CDATA[<p>Today, Evan has announced that the first RC of Vue 3 <a href="https://github.com/vuejs/rfcs/issues/183">will be released in mid-July</a>. The post suggests library/plugin authors to start migrating the support for Vue 3. But as the APIs and behaviors have changed a lot, is that possible to make our libraries support Vue 2 and 3 at the same time?</p>
<h2>Universal Code</h2>
<p>The most simple way is to write universal code that works in both versions without any additional modification, just like people would do for <a href="https://python-future.org/compatible_idioms.html">Python 2 and 3</a>. Simple does not mean it's easy. Write such components requires you to avoid <strong>things that newly introduced in Vue 3</strong> and <strong>things that deprecated from Vue 2</strong>. In other words, you can't use:</p>
<ul>
<li>Composition API</li>
<li><code>.sync</code> <code>.native</code> modifiers</li>
<li>Filters</li>
<li><a href="/posts/vue-3-notes#-use-markraw-for-vendor-objects">3rd-parties vendor objects</a></li>
<li>etc. (let me know if I missed anything and I will update the list)</li>
</ul>
<p>This just makes life harder and you can't benefit from the new awesome APIs in Vue 3.</p>
<h2>Use Branches</h2>
<p><a href="https://github.com/vuejs/rfcs/issues/183#issuecomment-652134760">A reply</a> to <a href="https://github.com/vuejs/rfcs/issues/183#issuecomment-651944231">this problem</a> from a core team member suggests using different branches to separate your support for each targeting version. I think it's a good solution for existing and mature libraries as their codebases are usually more stable and version targeting optimization might require them to have better code isolations.</p>
<p>The drawback of this is that you will need to maintain two codebases which double your workload. For small scale libraries or new libraries that want to support both versions, doing bugfix or feature supplements twice is just no ideal. I would not recommend using this approach at the very beginning of your projects.</p>
<h2>Build Scripts</h2>
<p>In <a href="https://github.com/antfu/vueuse">VueUse</a>, I wrote <a href="https://github.com/antfu/vueuse/tree/main/scripts">some build scripts</a> to make the code imports from the target version's API while building. After that, I would need to publish two tags <code>vue2</code> <code>vue3</code> to distinguish different version supports. With this, I can wite the code once and make the library supports both Vue versions. The problem of it is that I need to build twice on each release and guide users to install with the corresponding plugin version (and manually install <a href="https://github.com/vuejs/composition-api"><code>@vue/composition-api</code></a> for Vue 2).</p>
<hr>
<p>It has been several months since I wrote VueUse. During this period, I am always trying to figure out a proper way to extract the logic from VueUse, so it can be reused and benefit other library authors. But after all, I still think it's just too complicated to be used in general.</p>
<p>However, at the moment I started to write this post, I came up with the idea - providing the universal interface for both versions. If it works, in this way, authors are no need to worried about the users' environments anymore.</p>
<p>And after some experiments...</p>
<h2>🎩 Introducing <a href="https://github.com/antfu/vue-demi">Vue Demi</a></h2>
<p><a href="https://github.com/antfu/vue-demi"><strong>Vue Demi</strong></a> is a developing utility that allows you to write Universal Vue Libraries for Vue 2 and 3. Without worrying about users' installed versions.</p>
<p>When you are going to create a Vue plugin/library, simply install <code>vue-demi</code> as your dependency and import anything related to Vue from it. Publish your plugin/library as usual, your package would become universal!</p>
<pre><code class="language-json">{
  &quot;dependencies&quot;: {
    &quot;vue-demi&quot;: &quot;latest&quot;
  }
}
</code></pre>
<pre><code class="language-ts">import Vue, { reactive, ref } from 'vue-demi'
</code></pre>
<p>Underhood, it utilized the <a href="https://docs.npmjs.com/misc/scripts"><code>postinstall</code> npm hook</a>. After all packages get installed, <a href="https://github.com/antfu/vue-demi/blob/main/scripts/postinstall.js">the script</a> will start to check the installed Vue version and redirect the exports to based on the local Vue version. When working with Vue 2, it will also install <code>@vue/composition-api</code> automatically if it doesn't get installed.</p>
<p>You can also check <a href="https://github.com/antfu/vue-demi/tree/main/examples">the working examples</a>, where I created a demo library <a href="https://github.com/antfu/vue-demi/blob/main/examples/%40vue-demi/use-mouse/src/index.ts"><code>@vue-demi/use-mouse</code></a> with just a single file entry.</p>
<p>Please keep in mind that Vue Demi is experimental and there will definitely be some glitches. Feel free to give it a try and let me know if anything goes wrong.</p>
<p>Thanks and happy hacking!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Vue 3 Migration Notes]]></title>
            <link>https://antfu.me/posts/vue-3-notes</link>
            <guid isPermaLink="true">https://antfu.me/posts/vue-3-notes</guid>
            <pubDate>Wed, 01 Jul 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>Note: This is my personal notes/tips for migrating to Vue 3 and will be updated overtime. Please refer to <a href="https://v3.vuejs.org">the official docs</a> for the complete changelog.</p>
</blockquote>
<p>Sorted by the importance of my personal sense.</p>
<h3>💫 use <code>markRaw</code> for vendor objects</h3>
<p>The new reactivity system proxied the object passed to the Vue context. For vendor objects or class instances, you need to wrap it with <code>markRaw</code> in order to disable the reactivity injection.</p>
<pre><code class="language-ts">// works in Vue 2
this.codemirror = CodeMirror.fromTextArea(el)

// in Vue 3 you need to use markRaw()
// otherwise the CodeMirror won't work as expected
this.codemirror = markRaw(CodeMirror.fromTextArea(el))
</code></pre>
<p>I think this is a pretty tricky one. You won't see any warn or error on initialization, but the internal state of the vendor object might be messed up. You might face errors that comes from the libraries while couldn't find out why (the example above took me one hour of debugging to find out).</p>
<h3>💫 <code>.sync</code> → <code>v-model:</code></h3>
<p><code>.sync</code> modifier is unified by <code>v-model:</code></p>
<pre><code class="language-vue">&lt;!-- Vue 2 --&gt;
&lt;Component name.sync=&quot;name&quot; /&gt;

&lt;!-- Vue 3 --&gt;
&lt;Component v-model:name=&quot;name&quot; /&gt;
</code></pre>
<p><code>v-model</code> on native element would be <code>value/input</code> while on custom components, it changed to <code>modelValue</code> and <code>update:modelValue</code></p>
<h3>💫 <code>shims-vue.d.ts</code></h3>
<blockquote>
<p>Update: now you can use <a href="https://github.com/znck/vue-developer-experience/tree/main/packages/typescript-plugin-vue"><code>@vuedx/typescript-plugin-vue</code></a> for better type inference with SFC (no need for <code>shims-vue.d.ts</code> then)</p>
</blockquote>
<p>Changed to this:</p>
<pre><code class="language-ts">declare module '*.vue' {
  import { defineComponent } from 'vue'

  const Component: ReturnType&lt;typeof defineComponent&gt;
  export default Component
}
</code></pre>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Type Inferencing in Vue]]></title>
            <link>https://antfu.me/posts/type-inferencing-in-vue</link>
            <guid isPermaLink="true">https://antfu.me/posts/type-inferencing-in-vue</guid>
            <pubDate>Sun, 28 Jun 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>[[toc]]</p>
<p>As you may or may not know, I am working on preparing to release the v1.0 version for <a href="https://github.com/vuejs/composition-api">@vue/composition-api</a> recently. One of the current problems is that the type inference does not play well <a href="https://github.com/vuejs/composition-api/issues/338">#338</a>. So I get a chance to have a deeper look at <a href="https://github.com/vuejs/composition-api">vue-next</a>'s type implementations. I will tell you what I learned and how magic works in Vue.</p>
<p>Forget about the <code>setup()</code> function and <code>Composition API</code> for now, let talk about the options API in Vue 2 that everybody familiar with. In a classical example, we would have <code>data</code>, <code>computed</code>, <code>methods</code> and some other fields like this:</p>
<pre><code class="language-js">export default {
  data: {
    first_name: 'Anthony',
    last_name: 'Fu',
  },
  computed: {
    full_name() {
      return `${this.first_name} ${this.last_name}`
    },
  },
  methods: {
    hi() {
      alert(this.full_name)
    }
  }
}
</code></pre>
<p>It works well in JavaScript and putting all the context into <code>this</code> is pretty straightforward and easy to understand. But when you switch to TypeScript for static type checking. <code>this</code> will not be the context you expected. How can we make the types work for Vue like the example above?</p>
<h2>Type for <code>this</code></h2>
<p>To explicitly assign the type to <code>this</code>, we can simply use the <code>this parameter</code>:</p>
<pre><code class="language-ts">interface Context {
  $injected: string
}

function bar(this: Context, a: number) {
  this.$injected // ok
}
</code></pre>
<p>The limitation of this approach is that we will lose the signature of the method when working with a dict of methods:</p>
<pre><code class="language-ts">type Methods = Record&lt;string, (this: Context, ...args: any[]) =&gt; any&gt;

const methods: Methods = {
  bar(a: number) {
    this.$injected // ok
  }
}

methods.bar('foo', 'bar') // no error, the type of arguments becomes `any[]`
</code></pre>
<p>We would not want to ask users to explicitly type <code>this</code> in every method in order to make the type checking works.<br>
So we will need another approach.</p>
<h3><a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#thistypet"><code>ThisType</code></a></h3>
<p>After digging into Vue's code, I found an interesting TypeScirpt utility <code>ThisType</code>. The official doc says:</p>
<blockquote>
<p>This utility does not return a transformed type. Instead, it serves as a marker for a contextual <code>this</code> type.</p>
</blockquote>
<p><code>ThisType</code> would affect all the nested functions. With it, we can have:</p>
<pre><code class="language-ts">interface Methods {
  double: (a: number) =&gt; number
  deep: {
    nested: {
      half: (a: number) =&gt; number
    }
  }
}

const methods: Methods &amp; ThisType&lt;Methods &amp; Context&gt; = {
  double(a: number) {
    this.$injected // ok
    return a * 2
  },
  deep: {
    nested: {
      half(a: number) {
        this.$injected // ok
        return a / 2
      }
    }
  }
}

methods.double(2) // ok
methods.double('foo') // error
methods.deep.nested.half(4) // ok
</code></pre>
<p>The typing works well, but it still requires users to define the type interface of Methods first. Can we make it infer itself automatically?</p>
<p>We can do that with function inference:</p>
<pre><code class="language-ts">type Options&lt;T&gt; = {
  methods?: T
} &amp; ThisType&lt;T &amp; Context&gt;

function define&lt;T&gt;(options: Options&lt;T&gt;) {
  return options
}

define({
  methods: {
    foo() {
      this.$injected // ok
    },
  },
})
</code></pre>
<p>There is only one step left, to make context object dynamic inference from <code>data</code> and <code>computed</code>.</p>
<p>The full working demo would be:</p>
<pre><code class="language-ts">/* ---- Type ---- */
export type ExtractComputedReturns&lt;T extends any&gt; = {
  [key in keyof T]: T[key] extends (...args: any[]) =&gt; infer TReturn
    ? TReturn
    : never
}

type Options&lt;D = {}, C = {}, M = {}&gt; = {
  data: () =&gt; D
  computed: C
  methods: M
  mounted: () =&gt; void
  // and other options
}
&amp; ThisType&lt;D &amp; M &amp; ExtractComputedReturns&lt;C&gt;&gt; // merge them together

function define&lt;D, C, M&gt;(options: Options&lt;D, C, M&gt;) {}

/* ---- Usage ---- */
define({
  data() {
    return {
      first_name: 'Anthony',
      last_name: 'Fu',
    }
  },
  computed: {
    fullname() {
      return `${this.first_name} ${this.last_name}`
    },
  },
  methods: {
    notify(msg: string) {
      alert(msg)
    }
  },
  mounted() {
    this.notify(this.fullname)
  },
})
</code></pre>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[Domain Email]]></title>
            <link>https://antfu.me/posts/domain-email</link>
            <guid isPermaLink="true">https://antfu.me/posts/domain-email</guid>
            <pubDate>Wed, 17 Jun 2020 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Saw <a href="https://twitter.com/youyuxi/status/1272932071749619712">a tweet from Evan You</a> about the <a href="https://hey.com/">hey.com</a> mail service recently. I got interested in having a short and nice email address. My current one in Hotmail is just too long to even being spell out in talk. <code>hey.com</code> looks very nice but $99/year is not a very good deal to me. I decide to use my own domain for receiving emails.</p>
<p>I did this a couple years ago for another domain, I kinda remember an open-source tool allowing forwarding emails by just adding DNS record. I took some time to search for it but I didn't find the page matched with my memory. I went GitHub to search in my stared projects, it turns out the tool now becomes and Freemium SaaS <a href="https://forwardemail.net/">forwardemail.net</a> with a fresh new logo and website design that I can't even recognize it.</p>
<p>The DNS forwarding feature just works the same, but it requires you to log in and register your domain now. I am glad it now being more well documented and charging for premium supports which can help it sustain.</p>
<p>The config is quite simple as usual, with just 3 DNS record:</p>
<!-- eslint-skip -->
<pre><code class="language-html">MX   @  mx1.forwardemail.net  10
MX   @  mx2.forwardemail.net  20
TXT  @  forward-email=youremail@example.com
</code></pre>
<p>That’s it! It also provides some advanced configs, you can check <a href="https://forwardemail.net/en/faq">the doc here</a>.</p>
<p>While setting up and reading the docs, I learned that you can explicitly purge the cache for <a href="https://1.1.1.1/purge-cache/">Cloudflare DNS</a> and <a href="https://developers.google.com/speed/public-dns/cache">Google DNS</a>. That's a very good-to-know tip!</p>
<p>And now, you can say hi to me at <a href="mailto:hi@antfu.me">hi@antfu.me</a>!</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
        <item>
            <title><![CDATA[New House]]></title>
            <link>https://antfu.me/posts/new-house</link>
            <guid isPermaLink="true">https://antfu.me/posts/new-house</guid>
            <pubDate>Fri, 12 Jun 2020 16:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I always feel that I need a blog but I never have one.</p>
<p>So I took some time to rewrite my homepage and adding the blog section. and Hi, here I am :)</p>
<p>This time I gave a try on <a href="http://gridsome.org/">Gridsome</a> and it works petty well. I do love the <code>&lt;static-query&gt;</code> custom block that works even in components. The <a href="https://gridsome.org/docs/images/">progressive image</a> also solves my years of pain in making a photography portfolio (and I have never finished one).</p>
<p>While I was browsing Gridsome's starter templates, I found a great website <a href="https://forestry.io/">Forestry.io</a>. It's a Git-backed CMS that I can write and manage my blog posts in a nice GUI website and then commit back to GitHub. This is the feature that I wish to have for a long time, did not expect to see such a well built and nicely designed service out there.</p>
<p>As for the blog, I am likely to do some bilingual stuff in the future. Some posts will be written in English, some might be in Chinese. A switch button will be shown then. Also planned to post my photos in this site, which I would need to implement the gallery page for it.</p>
<p>The site is still WIP, if you have any thoughts or suggestions, please tell me via <a href="mailto:hi@antfu.me">email</a> or on <a href="https://twitter.com/antfu7">Twitter</a>. Thanks!</p>
<p>:)</p>
]]></content:encoded>
            <author>hi@antfu.me (Anthony Fu)</author>
        </item>
    </channel>
</rss>