question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

RFC: Packages and versioning

See original GitHub issue

Preface

Our current packaging and versioning strategy hinders both consumers and development. To improve the modularity and composability of our components, we should publish packages that are far more granular.

Scope

This RFC focuses specifically on improving how HIG distributes components. Subjects such as project structure, design tokens, and CSS versioning should be discussed in other RFCs.

Issue

HIG’s current packages are based on the different interfaces to components, instead of the components themselves. As a result, components are only available in bundles under a single version, and modularity and composability are limited.

Import example of existing packages:

import { Button as VanillaButton } from 'hig-vanilla';
import { Button } from 'hig-react';
import * as Hig from 'hig-react';
Case

Lets use bundle size optimization as an example. Consumers should be able to only import what’s necessary to maintain small bundle sizes.

When tree-shaking is supported (WIP), this issue will be resolved for some consumers. However, tree-shaking also needs to be supported by the consumer’s build system to be utilized. Specifically, consumers using CommonJS modules will see no benefit from tree-shaking support.

This RFC’s proposal aims to allow components to be consumed individually, regardless of the consumer’s build system.

Proposal

To improve the modularity and composability of our components, we should migrate our packaging strategy to use:

Scoped component packages, e.g. @hig/button

import Button from '@hig/button';
import VanillaButton from '@hig/button/vanilla';

Scoped aggregate package, i.e. @hig/components

import { Button } from '@hig/components';
import Button from '@hig/components/button';
import * as Hig from '@hig/components';

Utilities package, e.g. @hig/utilities (the name doesn’t really matter)

This package should contain anything that’s shared by multiple components. This package would exist so that each component doesn’t bundle the same utilities over and over. Facebook has a similar package with a clear, detailed explanation for the reasoning behind it.

While being a public package, the API should be considered unstable, and always published with an alpha version.

Private packages, e.g. @hig/playground

All local development tooling and integration tests can be moved into unpublished, private packages.

Migration

We can gradually migrate existing components into separate packages while maintaining support of the existing non-scoped packages (i.e. hig-react and hig-vanilla). Once all of the new packages are published, the non-scoped packages can be deprecated.

To clarify, deprecation does not imply breaking changes or abandonment. We should provide support for these packages as long as there’s substantial usage.

Outline:

  • Move existing components into new scoped packages
    • Consolidate all modules related to a particular component into one package
      • e.g. Vanilla component, React adapter, React facade component, etc.
  • Recompose non-scoped packages from the new scoped packages
    • No breaking changes
    • Deprecate but maintain support
  • Compose scoped aggregate package containing all components
    • Should only contain components.
    • No colors, typography etc. These should be design tokens, but that’s for another RFC. For now, we can put these in @hig/utilities.

Versioning

Generally speaking, JavaScript versioning is a solved problem.

Modularity and composability are staples of the community now, and SemVer has been widely adopted to facilitate the use of highly composable modules. HIG’s codebase is a monorepo using an independent versioning scheme. However, to properly leverage semver, our packages should be much more granular.

Using component packages with the aggregate package

Each component should have it’s own version. A change in a particular component shouldn’t result in a version change for a completely unrelated component.

This also provides an upgrade path for consumers. Imagine a scenario where a there’s a breaking change for the Button component (relax, this is hypothetical). Consumers will be able to easily use a previous version of the Button component while keeping the rest of the components up-to-date.

Example of both types of packages together:

{
  "dependencies": {
    "@hig/components": "^2.0.0",
    "@hig/button": "1.0.0"
  }
}

Yet, this reveals another issue of CSS versioning. BEM, though useful, isn’t well suited to handle multiple versions of the same component rendered together. I’ll be making a separate RFC discussing this issue.

Scoped packages

Using scoped packages is an obvious way to group all of our packages together.

Additionally, it provides confidence to consumers that they’re using the correct package. This is especially important as we add more packages in the future. In short, we can help avoid issues like this and this.

Version numbers

We should closely follow SemVer 2.0.0. However, our versions currently differ slightly.

SemVer 2.0.0 defines major version zero as:

Major version zero (0.y.z) is for initial development. Anything may change at any time. The public API should not be considered stable.

It seems we’re treating major version zero as v1.0.0-beta. Additionally, in practice many consumers expect 0.x.x versions to be stable APIs without breaking changes in minor versions. I propose that we maintain our existing approach of treating major version zero as v1.0.0-beta.

Prior art

The strategy proposed in this RFC isn’t new. Notable libraries have been published in this manner for quite a while now.

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:7 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
nfinitesetcommented, Mar 15, 2018

I love this Morris. Smart and inspirational. I want propose taking versioning even farther.

tldr; How about this:

import Button as OldButton from '@hig/button/v1';
import Button as NewButton from '@hig/button/v2';

Suppose I have a large app that makes extensive use of a fairly complex component such as a multi-select dropdown. Say HIG makes a breaking API change. If I have to go back and touch every instance of the component in my huge app, I may never bother to update to a newer version.

How can I use the new component without having to go back and change all existing instances?

I’ve heard of one approach that allows this called Semantic Import Versioning. This article comes up in a quick google.

1reaction
morrisallisoncommented, Mar 14, 2018

@eskfung

This is an interesting note for me. If we wanted to sunset hig-vanilla and hig-react, what does that look like? Do we have tools to gauge “substantial usage”?

  1. The most simplistic metric are npm stats. However, do those numbers include our testing environments?
  2. GitHub shows us our public dependents. Valuable, but doesn’t show the entire picture.
  3. We can add our own analytics similar to this. I’m not sure of all the effects of this; just an idea.

I was purposefully vague with “substantial usage”, so we can better define what that means for us. I don’t foresee much difficulty in maintaining hig-react as it is, but hig-vanilla is another story.

Read more comments on GitHub >

github_iconTop Results From Across the Web

RFC 3253 Versioning Extensions to WebDAV - IETF
2.1 Basic Versioning Packages A server MAY support any combination of versioning features. However, in order to minimize the complexity of a WebDAV...
Read more >
Brainstorming for RFC: pname and version - NixOS Discourse
Hello, people! There is occurring a lengthy discussion about how Nixpkgs should implement the parameters pname and version in Nixpkgs.
Read more >
Semantic Versioning 2.0.0 | Semantic Versioning
Semantic Versioning 2.0.0. Summary. Given a version number MAJOR.MINOR.PATCH, increment the: MAJOR version when you make incompatible API changes ...
Read more >
netmod versioning and packaging - IEEE 802
It describes how packages: are represented on a server, can be defined in offline YANG instance data files, and can be used to...
Read more >
RFC 2291: Requirements for a Distributed Authoring and ...
RFC 2291 Distributed Authoring and Versioning February 1998 possible that a single mechanism could simultaneously satisfy several requirements.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found