MoreRSS

site iconAnthony Fu Modify

A fanatical open sourceror. Working at NuxtLabs. Creator of Vitest Slidev VueUse UnoCSS Elk Type Challenges
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of Anthony Fu

Epoch Semantic Versioning

2025-01-07 20:00:00

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 v0.x.x. For instance, as of writing this post, the latest version of UnoCSS is v0.65.3, Slidev is v0.50.0, and unplugin-vue-components is v0.28.0. Other projects, such as React Native is on v0.76.5, and sharp is on v0.33.5, also follow this pattern.

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.

Why? - I bet that's your question reading this.

Versioning

Version numbers act as snapshots of our codebase, helping us communicate changes effectively. For instance, we can say "it works in v1.3.2, but not in v1.3.3, there might be a regression." 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.

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.

I see versioning as a way to communicate changes to users — a contract 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 v2.3.4 and v2.3.5 without checking the changelog. But by looking at the numbers, you can infer that it's a patch release meant to fix bugs, which should 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.

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.

Semantic Versioning

In the JavaScript ecosystem, especially for packages published on npm, we follow a convention known as Semantic Versioning, or SemVer for short. A SemVer version number consists of three parts: MAJOR.MINOR.PATCH. The rules are straightforward:

  • MAJOR: Increment when you make incompatible API changes.
  • MINOR: Increment when you add functionality in a backwards-compatible manner.
  • PATCH: Increment when you make backwards-compatible bug fixes.

Package managers we use, like npm, pnpm, and yarn, 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 ^1.2.3, it indicates that you are comfortable with upgrading to any version that shares the same major version (1.x.x). In these scenarios, package managers will automatically determine the best version to install based on what is most suitable for your specific project.

This convention works well technically. If a package releases a new major version v2.0.0, your package manager won't install it if your specified range is ^1.2.3. This prevents unexpected breaking changes from affecting your project until you manually update the version range.

However, humans perceive numbers on a logarithmic scale. We tend to see v2.0 to v3.0 as a huge, groundbreaking change, while v125.0 to v126.0 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 v125.0, it becomes difficult to convey the significance of a major change, as the jump to v126.0 appears minor.

{@TkDodo|Dominik Dorfmeister} had a great talk about API Design, which mentions an interesting inequality that descripting this: "Breaking Changes !== Marketing Event"

Progressive

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.

Progressive as Stairs
Progressive as Stairs - a screenshot of my talk The Progressive Path

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 v2.0.0 with 10 breaking changes from v1.x, we could distribute these changes across several smaller major releases. This way, we might release v2.0 with 2 breaking changes, followed by v3.0 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.

Progressive on Breaking Changes
Progressive on Breaking Changes - a screenshot of my talk The Progressive Path

Leading Zero Major Versioning

The reason I've stuck with v0.x.x 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 v2 to v3. Some changes might be "technically" 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).

There's a special rule in SemVer that states when the leading major version is 0, every minor version bump is considered breaking. I am kind of abusing that rule to workaround the limitation of SemVer. With zero-major versioning, we are effectively abandoning the first number, and merge MINOR and PATCH into a single number (thanks to David Blass for pointing this out):

ZERO.MAJOR.{MINOR + PATCH}

Of course, zero-major versioning is not the only solution to be progressive. We can see that tools like Node.js, Vite, Vitest 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!

I have to admit that sticking to zero-major versioning isn't the best practice. 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.

Thus, here, I am proposing to change.

Epoch Semantic Versioning

In an ideal world, I would wish SemVer to have 4 numbers: EPOCH.MAJOR.MINOR.PATCH. The EPOCH version is for those big announcements, while MAJOR 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 Romantic Versioning that propose HUMAN.MAJOR.MINOR. The creator of SemVer, Tom Preston-Werner also mentioned similar concerns and solutions in this blog post. (thanks to Sébastien Lorber for pointing this out).

But, of course, it's too late for the entire ecosystem to adopt a new versioning scheme.

If we can't change SemVer, maybe we can at least extend it. I am proposing a new versioning scheme called 🗿 Epoch Semantic Versioning, or Epoch SemVer for short. It's built on top of the structure of MAJOR.MINOR.PATCH, extend the first number to be the combination of EPOCH and MAJOR. To put a difference between them, we use a third digit to represent EPOCH, which gives MAJOR a range from 0 to 99. This way, it follows the exact same rules as SemVer without requiring any existing tools to change, but provides more granular information to users.

The name "Epoch" is inspired by Debian's versioning scheme.

The format is as follows:

{EPOCH * 100 + MAJOR}.MINOR.PATCH
  • EPOCH: Increment when you make significant or groundbreaking changes.
  • MAJOR: Increment when you make minor incompatible API changes.
  • MINOR: Increment when you add functionality in a backwards-compatible manner.
  • PATCH: Increment when you make backwards-compatible bug fixes.

For example, UnoCSS would transition from v0.65.3 to v65.3.0 (in the case EPOCH is 0). Following SemVer, a patch release would become v65.3.1, and a feature release would be v65.4.0. If we introduced some minor incompatible changes affecting an edge case, we could bump it to v66.0.0 to alert users of potential impacts. In the event of a significant overhaul to the core, we could jump directly to v100.0.0 to signal a new era and make a big announcement. I'd suggest assigning a code name to each non-zero EPOCH 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.

We shouldn't need to bump EPOCH often. It's mostly useful for high-level, end-user-facing libraries or frameworks. For low-level libraries, they might never need to bump EPOCH at all (ZERO-EPOCH is essentially the same as SemVer).

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.

Moving Forward

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.

I'd love to hear your thoughts and feedback on this idea. Feel free to share your comments using the links below!

CPU Profiling Nuxt

2025-01-01 00:00:00

If you want to debug the bundling performance of your Nuxt app to generate CPU profiles.

Node.js provides a built-in --cpu-prof flag that allows you to generate CPU profiles. However you can't directly pass it in your nuxi command, you have to use it with node directly.

Instead of running nuxi dev, you can run node with the direct path to the CLI in node_modules:

# nuxi dev
node --cpu-prof ./node_modules/nuxi/bin/nuxi.mjs dev --fork=false

Note that --fork=false is important as by default nuxi will start the Nuxt process in a forked process which will make the CPU profile not working.

The simliar technique can be applied to other CLI tools that are not directly using node to start the process.

After killing your Nuxt process, you will find two CPU.***.cpuprofile files generated in your working directory. I recommend using CPUpro to visualize the profile. If you are using VS Code, I also created an extension for you to directly open the .cpuprofile file in VS Code easily.

Initiative on Sponsorship Forwarding

2024-04-20 08:00:00

[[toc]]

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 xz/liblzma vulnerability accident is a great example that shows the importance of open-source projects and how severe the problem could be.

There are multiple ways to support open-source projects (another excellent article by Rob Mensching, highly recommended). 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.

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 (for example, thanks {NuxtLabs} for having the Nuxt core team and me 💚. {Astro} {Stackblitz} {Netlify} {Vercel} and other companies are also doing it). We, as the maintainers, genuinely appreciate all that support.

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.

Unbalanced Funding

In open-source, there are different types of projects.

Some projects are more "front-facing" 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.

On the other hand, there are also "underlying" 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.

Whether a project is "front-facing" or "underlying", 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.

Among all the contributors and maintainers, there is a mix of individuals who have different working styles and levels of visibility. Some are more "high-profile" 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.

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 this famous xkcd comic again:

Open-source Sponsor Dependencies

Imagine, with that critical dependency being removed, here we have a falling apart version by Adam Leventhal:

Open-source Sponsor Dependencies

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.

However, it's important to recognize that when we develop a "front-facing" tool or framework, we often rely on other tools and dependencies to make our work possible. It wouldn't be fair for the "front-facing" projects to take all the credit. In reality, even those "front-facing" projects are often underfunded. That's why we are grateful to see many open-source projects forwarding sponsorships to supporting their dependencies.

For example, {ESLint} is forwarding sponsorships to its contributors and dependencies, {Astro} is giving funds to the ecosystem, projects that I am participating like {Vite|https://opencollective.com/vite/expenses} and {Elk|https://opencollective.com/elk/expenses?collectiveSlug=elk&type=INVOICE} are also started following similar approaches on their Open Collective. Many other projects are doing it as well.

{@nzakas|Nicholas C. Zakas}, the creator of ESLint, also wrote a great article Sponsoring dependencies: The next step in open source sustainability, explaining the importance of this.

About Me

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 all the sponsorships I have received. As I mentioned in another post, 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.

Sponsorships Forwarding on GitHub

I am already sponsoring a number of maintainers who I benefit a lot from. {GitHub Sponsors|https://github.com/sponsors} is a great platform. It covers the transaction fees, 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} raised this feature request for GitHub two years ago, which is currently the top-upvoted request from the community, but unfortunately, it has not been resolved yet.

Here, let's do some simple math to see why this feature is essential:

  • With GitHub Sponsors, the maintainer receives a monthly payout to their bank account.
  • This payout counts as personal income, and the maintainer needs to pay taxes based on the country they live in.
  • If the maintainer wants to sponsor another maintainer, they need to pay from their own pocket.
  • When the second maintainer receives the funds after another month, they must also pay taxes again.

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

(1 - 41%) x (1 - 41%) = 34.81%

of the original amount, plus two months of delay in between (on top of the case that GitHub already covers the transaction fees). 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 not yet available in all countries).

Despite GitHub Sponsors limitations, a lot of maintainers are still forwarding part of their funds to others ({@danielroe|Danielroe's Sponsoring|https://github.com/danielroe?tab=sponsoring}, {@patak-dev|Matias' Sponsoring|https://github.com/patak-dev?tab=sponsoring}, {@kazupon|Kazupon's Sponsoring|https://github.com/kazupon?tab=sponsoring} 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.

Open Collective

Another popular platform for open-source projects to receive sponsorships is Open Collective. It provides great transparency on how funds are collected and spent.

For open-source projects, Open Source Collective 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 on the same fiscal host with no additional fees. This makes it a much better fit for the sponsorship forwarding use case.

Anthony Fu's Collective

So, I came up with the idea of creating my personal collective: {Anthony Fu Collective}

Where you can sponsor my work across the ecosystem, including but not limited to:

{Vite} {Vue} {Nuxt} {Vitest} {VueUse} {UnoCSS} {Slidev} {Elk} {Shiki} {TwoSlash} {ESLint Stylistic}

The main difference is that I won't take the funds for myself;
(except for occasional expense reimbursements like hosting or domain renewal)

All the funds will be redistributed to the dependencies and contributors. 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.

Open-source Sponsor Dependencies

To allow differentiating easily, I will treat the funds on GitHub Sponsors and Open Collective differently:

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

Recurring sponsorships are highly appreciated. They help provide a more consistent monthly income and contribute to long-term sustainability for the projects and maintainers.

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.

Transparency

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.

Why am I Doing this?

As I explained throughout this post, my intention is to contribute to the open-source ecosystem and make it better with my little efforts.

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.

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.

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.

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.

How to Ensure the Distribution is Fair?

Regrettably, I don't think it's possible to design a "perfect algorithm" to "score" and distribute the funds fairly. Representing a project, a person, or their work with a single number to rank is unrealistic.

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.

Why "Personal" Collective

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.

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.

For a related example, inspired by this tweet, 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.

Other Alternative Platforms?

There are quite a few other platforms for open-source funding that also bring exciting solutions to this problem.

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.

Sponsor Now

This initiative is something I couldn't do alone. I would need your support to make it happen.

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!

Feedback

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 this tweet or this toot. You can also mail me at [email protected].

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. 💚