馃О 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

React

react-router

Babel

Rollup

used by

MDC

Gatsby

Next.js

react-router

Storybook

Jest

Lerna

路 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/*