🧰 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
Google Material Components (aka. MDC)
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
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.
- Detect changed packages, choose version bump(s)
- Run preversion lifecycle in root
- For each changed package, in topological order (all dependencies before dependents):
- Run preversion lifecycle
- Update version in package.json + CHANGELOG
- Run version lifecycle
- Run version lifecycle in root
- Add changed files to index, if enabled
- Create commit and tag(s), if enabled
-
For each changed package, in lexical order (alphabetical according to directory structure):
- Run postversion lifecycle
- Run postversion lifecycle in root
- Push commit and tag(s) to remote, if enabled
- Create release, if enabled
$ lerna publish from-git
-
If versioning implicitly, run all version lifecycle scripts
-
Run prepublish lifecycle in root, if enabled
-
Run prepare lifecycle in root
-
Run prepublishOnly lifecycle in root
-
Run prepack lifecycle in root
-
For each changed package, in topological order (all dependencies before dependents):
-
Run postpack lifecycle in root
-
For each changed package, in topological order (all dependencies before dependents):
-
Run publish lifecycle in root
-
To avoid recursive calls, don't use this root lifecycle to run lerna publish
-
-
Run postpublish lifecycle in root
-
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
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,032