🧰 ui-kit / 🐉 multi-packages

TOC

  • Intro
  • Issues
  • Code organisation
  • Build
  • Watch
  • Release
  • Extras
  • Next

Intro

@ui-kit/*
@sewan/ui-kit
@sewan/ui-kit
@ui-kit/avatar

before / after

Memo: what's a package

  • 1 or more components
  • (assets)
  • (libraries)
  • (binaries)
  • etc...

@ui-kit/form

Inspiration

Tools

npm 7+

Rollup

Lerna

used by

· The world

Issues

Decoupling

reduce the ui-kit coupling

v1

v2

I want that upgrade

But not this one

I didn't want it (yet)

@sewan/ui-kit

@sewan/ui-kit

App

App

Solution: cracking

App

App

v1.0

v1.1

I want that upgrade

@ui-kit/triangle

@ui-kit/triangle

v2.6

v3.0

But not this one (yet)

@ui-kit/circle

@ui-kit/circle

@ui-kit/triangle

@ui-kit/circle

Isolation

Self-contained

INDEPENDENT - UNAWARE
OF EACH OTHERS

A package owns:

  • ☑️ all its code
  • ☑️ its list of all dependencies

making them:

Independence

Independent versioning/releases

v2

v5

v1

v2

v6

Code organisation

multiple packages

front-ui-kit
├── ...
├── packages
│   ├── Avatar
│   ├── Icon
│   ├── ...
│   └── ui-kit
└── ...

multiple packages

@sewan/ui-kit
@ui-kit/*

meta-package

individuals

import Avatar from '@ui-kit/avatar'
import { Avatar } from '@sewan/ui-kit'

or

Comsuming a package

individually

globally (backward compatible)

├── packages
│   ├── Avatar/package.json
│   ├── Icon/package.json
│   ├── ...
│   └── ui-kit/package.json
├── ...
└── package.json

multiple package.json

individuals
project-wide (dev)
├── packages
│   ├── Avatar/README.md
│   ├── Icon/README.md
│   ├── ...
│   └── ui-kit/README.md
├── ...
└── README.md

multiple READMEs

individuals
project-wide (dev)
├── packages
│   ├── Avatar/package.json
│   ├── Icon/package.json
│   ├── ...
│   └── ui-kit/package.json
├── ...
└── package.json
{
  "name": "@ui-kit/avatar",
  "version": "1.6.2",
  ...
}

inner-dependencies

{
  "name": "@sewan/ui-kit",
  "version": "1.2.5",
  "dependencies": {
    "@ui-kit/avatar": "^1.6.2",
    ...
  }
}
├── node_modules
│   ├── @ui-kit/avatar -> ../packages/Avatar
│   ├── @ui-kit/icon -> ../packages/Icon
│   ├── ...
│   └── @sewan/ui-kit -> ../packages/ui-kit
└── ...
import Icon from '@ui-kit/icon'

const ico = <Icon name="FranceFlagIcon" />

workspaces

thx to npm --version 7

package.json
{
  ...
  "workspaces": [...],
  ...
}
├── packages
│   ├── Avatar/dist
│   ├── Icon/dist
│   ├── ...
│   └── ui-kit/dist
└── ...

JS compiled version
(ie: not JSX)

dist folder

├── packages
│   ├── Avatar/dist
│   │   ├── index.cjs.js
│   │   └── index.es.js
│   ├── Icon/dist
│   ├── ...
│   └── ui-kit/dist
└── ...
{
  "name": "@ui-kit/avatar",
  "version": "1.6.2",
  "main": "dist/index.cjs.js",
  "module": "dist/index.es.js",
  ...
}
const Avatar = require('@ui-kit/avatar')
import Avatar from '@ui-kit/avatar'

module format

a unique script to bundle all

Build

$ npx @sewan/ui-rollup
#!/bin/sh
index.js
dist/index.es.js
dist/index.cjs.js
{
  ...
  "src": "index.js",
  ...
}
package.json
#!/bin/sh
foo/index.js
bar/dist/index.es.js
bar/dist/index.cjs.js
bar/index.js
foo/dist/index.es.js
foo/dist/index.cjs.js
$ npx @sewan/ui-rollup \
<input1> <input2> ...
#!/bin/sh
foo/index.js
bar/dist/index.es.js
bar/dist/index.cjs.js
bar/index.js
foo/dist/index.es.js
foo/dist/index.cjs.js
$ npx @sewan/ui-rollup foo/index.js bar/index.js \
--watch
$ npx @sewan/ui-rollup --help
$ lerna run dist
packages/*
{
  ...,
  "main": "dist/index.cjs.js",
  "module": "dist/index.es.js",
  "scripts": {
    "dist": "sewan-ui-rollup",
    ...
  }
}
npm run dist

  aka.

packages/*/dist
package.json

aka. dev mode

Watch

npm start

  aka.

$ npx start-storybook
// .storybook/main.js

if (config.mode !== 'production') {
  config.plugins.push(onFirstBuildDonePlugin(() => {
    spawn('npm', ['run', 'dist-packages-watch'], {stdio: "inherit"})
  }))
}

Storybook +

Dependencies graph

$ ./bin/list-packages --toposort
/home/abernier/sewan/front-ui-kit/packages/Icon/index.js
/home/abernier/sewan/front-ui-kit/packages/core/index.js
/home/abernier/sewan/front-ui-kit/packages/Avatar/index.js
/home/abernier/sewan/front-ui-kit/packages/ui-kit/index.js

1

2

3

4

npm run dist-watch

  aka.

$ npx @sewan/ui-rollup \
    $(./bin/list-packages --toposort) \
    --watch

1

@ui-kit/icon

2

@ui-kit/core

3

@ui-kit/avatar

4

@sewan/ui-kit

$ npx @sewan/ui-rollup \
    $(./bin/list-packages --toposort --scope @ui-kit/*) \
    --watch
npm run dist-packages-watch

  aka.

1

@ui-kit/icon

2

@ui-kit/core

3

@ui-kit/avatar

Release

Change Log

All notable changes to this project will be documented in this file. See Conventional Commits for commit guidelines.

 

2.0.0 (2021-05-26)

Features

  - avatar: new comment (8a65f63)
 

BREAKING CHANGES

  - avatar: this is huge

 

1.6.3 (2021-05-18)

...

packages/Avatar/CHANGELOG.md

for any packages/*

$ git commit                                                                                                       

feat(avatar): new comment

BREAKING CHANGE: this is huge
$ lerna changed/diff
$ npx lerna changed                                                                                                       lerna notice cli v4.0.0                                                                                                         lerna info versioning independent                                                                                               lerna info Looking for changed packages since @sewan/ui-kit@1.2.8                                                               @ui-kit/avatar                                                                                                                  @sewan/ui-kit                                                                                                                   lerna success found 2 packages ready to publish
$ npx lerna diff                                                                                                          lerna notice cli v4.0.0                                                                                                         lerna info versioning independent                                                                                               diff --git a/packages/Avatar/index.js b/packages/Avatar/index.js                                                                index 649e010..554e401 100644                                                                                                   --- a/packages/Avatar/index.js                                                                                                  +++ b/packages/Avatar/index.js                                                                                                  @@ -1,3 +1,4 @@                                                                                                                  export { default } from './Avatar';                                                                                             export { default as AvatarGroup } from './AvatarGroup';                                                                         export { AvatarSet } from './styled';                                                                                          +// breaking change
$ lerna version
npm run release

  aka.

  1. Detect changed packages, choose version bump(s)
  2. Run preversion lifecycle in root
  3. For each changed package, in topological order (all dependencies before dependents):
    1. Run preversion lifecycle
    2. Update version in package.json + CHANGELOG
    3. Run version lifecycle
  4. Run version lifecycle in root
  5. Add changed files to index, if enabled
  6. Create commit and tag(s), if enabled
  7. For each changed package, in lexical order (alphabetical according to directory structure):
    1. Run postversion lifecycle
  8. Run postversion lifecycle in root
  9. Push commit and tag(s) to remote, if enabled
  10. Create release, if enabled
$ lerna publish from-git
  1. If versioning implicitly, run all version lifecycle scripts

  2. Run prepublish lifecycle in root, if enabled

  3. Run prepare lifecycle in root

  4. Run prepublishOnly lifecycle in root

  5. Run prepack lifecycle in root

  6. For each changed package, in topological order (all dependencies before dependents):

    1. Run prepublish lifecycle, if enabled

    2. Run prepare lifecycle

    3. Run prepublishOnly lifecycle

    4. Run prepack lifecycle

    5. Create package tarball in temp directory via JS API

    6. Run postpack lifecycle

  7. Run postpack lifecycle in root

  8. For each changed package, in topological order (all dependencies before dependents):

    1. Publish package to configured registry via JS API

    2. Run publish lifecycle

    3. Run postpublish lifecycle

  9. Run publish lifecycle in root

    • To avoid recursive calls, don't use this root lifecycle to run lerna publish

  10. Run postpublish lifecycle in root

  11. Update temporary dist-tag to latest, if enabled

postversion lifecycle

{
  ...,
  "scripts": {
    "prepack": "npm run dist",
    ...
  }
}
package.json

Let's try a release !

by modifying 

@ui-kit/avatar

🏷 tags and 🚀 releases

Extras

pre-commit hook → npx lint-staged

eslint-config-airbnb + eslint-config-prettier

lint / format

test + storybook

both now run on built version of the packages (confidence)

no more storyshots (time saving ~300%)

lint included (pretest lifecycle)

npm-ci:
  image: $DOCKER_REGISTRY/$DOCKER_NODE_IMAGE
  stage: intro
  variables:
    FF_USE_FASTZIP: 'true'
    CACHE_COMPRESSION_LEVEL: 'fastest'
  cache:
    key:
      files: [package-lock.json]
    paths: [node_modules/]
    policy: push
  script:
    - npm ci --no-audit --no-optional
   only:
     changes:
       - package-lock.json

CI Pipelines

-300%

FROM node:15.14.0-alpine

$DOCKER_NODE_IMAGE

that comes with

node --version
7.7.6 
FROM node:15.14.0-alpine
node-ci:15.14.0

What's next

Migration

of all @ui-kit/*

ui-kit / multi-packages

By Antoine BERNIER

ui-kit / multi-packages

Sewan's ui-kit cracking

  • 1,023