zapier-design-system-repo v2.2.6
Zapier design system monorepo
This repo houses all of the components within Zapier's design system and global UI, as well as the styleguide that demos all of the components. It's a monorepo that's broken into multiple /packages
.
The live styleguide can be viewed at https://design.zapier.com/.
Node version
This project runs on node 12, so you'll likely want to use a tool like nvm to manage your node versions. The .nvmrc
file specifies the version, as does rush.json
.
If you don't have nvm
set up already, follow its installation instructions and then run nvm use
within this repo to use the version defined in .nvmrc
.
I also highly recommend adding code to automatically run nvm use
when you cd
so as you cd
between projects the correct version of node
is set.
# Automatically `nvm use` when `cd`ing into a directory.
# This has to occur after all other sourcing which is why
# this is at the bottom of the file.
autoload -U add-zsh-hook
load-nvmrc() {
local node_version="$(nvm version)"
local nvmrc_path="$(nvm_find_nvmrc)"
if [ -n "$nvmrc_path" ]; then
local nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")")
if [ "$nvmrc_node_version" = "N/A" ]; then
nvm install
elif [ "$nvmrc_node_version" != "$node_version" ]; then
nvm use
fi
elif [ "$node_version" != "$(nvm version default)" ]; then
echo "Reverting to nvm default version"
nvm use default
fi
}
add-zsh-hook chpwd load-nvmrc
load-nvmrc
Package management
This repo uses a combination of rush
and yarn
for package management. yarn
is used primarily for package installation, while rush
is used for verifying that changelogs are generated and for publishing new versions of packages.
For a while this repo only used rush
, but as more packages were added that became untenable for our styleguide.
Rush works by installing packages into a common/temp
directory instead of a root node_modules
directory in an effort to avoid phantom dependencies. It then creates node_modules
directories within each package and symlinks packages within that directory to the actual versions installed into common/temp
. This setup is closer to how npm
works than yarn
. However through some combination of Rush's symlinking, node module resolution, Webpack, loaders, and Styleguidist, the styleguide effectively cannot boot and runs out of memory.
A few attempts were made to resolve this, such as using Webpack's alias
, trying Webpack 5, trying yarn 2, etc, however none of them led to an acceptable solution. Switching to use yarn
to install packages directly, however, did.
One solution that hasn't been tried yet is switching to Storybook which we hope to attempt at some point, but it hasn't yet been prioritized.
Lerna is another monorepo management tool that facilitates publishing multiple packages. It has a larger community than Rush and more functionality. However Rush has a few features that our team found preferable to Lerna:
- Rush handles phantom dependencies and doppelgangers out of the box.
- We preferred Rush's
change
command for generating changelogs over Lerna's usage of conventional commits. Conventional commits are fine, but they are rigid and open up questions about squashing during merging. Commit messages and changelogs also serve slightly different purposes. Commits should describe why a change was made and any technical details of the change. Changelogs describe what changes were made, potentially why they were made, and more importantly what consumers need to be aware of when updating to new versions. Commits are for reference within the repo, changelogs are for reference outside of the repo. - Rush's
change
command also neatly separates versioning, publishing, and changelogs. Changelogs are written in the PRs that generate them, but versioning and publishing happen in the main repo branch. Lerna's default behavior is for keeping package versions in lockstep, which this repo doesn't need or want. In fact it's essential that versions are not kept in lockstep due to the current needs of the
design-system-context
package.Neither Rush nor Lerna are likely to go anywhere. Rush is maintained by Microsoft and they use it for many of their internal projects. If we find that Rush doesn't suit our needs, we will change to a tool that does, and that tool could even be Lerna. Changing from one tool to another should be relatively painless since changes affect configuration rather than source code or its structure. However at this time Rush appears to be a more appropriate tool for our team's goals.
Using packages
Note: if you get an error about NPM_TOKEN
when running any of these steps you may need to create an NPM_TOKEN
environment variable locally.
š To add a new dependency, add it to the applicable package.json
file and then run yarn
. Alternatively you can use yarn add
.
š To update a dependency, update it in the applicable package.json
file and then run yarn
.
š When you're ready to merge your PR, run yarn change
(an alias to rush change
) from the repo root to generate changelog entries for all changed packages via Rush. CI will fail PRs that don't specify a changelog entry.
When your PR is merged the new version of your package will be published automatically via GitHub Actions and a changelog will be generated based on the inputs to rush change
.
Local development
Local development happens primarily through the styleguide. After cloning the repo, run the following commands:
# Log into npm if you haven't already.
# This is necessary to access our private npm registry.
npm login
# Install dependencies.
yarn install
# Build all packages
yarn build:all
# Start the styleguide.
cd apps/design-system-site
yarn dev
š If the styleguide runs slowly or you don't want to include all packages within it, create a styleguide.personal.config.js
file next to styleguide.config.js
, and then use the yarn dev:personal
command to use the styleguide.personal.config.js
config file.
// Sample `styleguide.personal.config.js` file
const styleguide = require('./styleguide.config.js');
module.exports = {
...styleguide,
sections: styleguide.sections.filter((section) => {
return [
// Uncomment packages to render them.
// '@zapier/design-system',
// '@zapier/design-system-patterns',
// '@zapier/universal-nav',
// '@zapier/universal-footer',
// '@zapier/universal-layout',
].includes(section.packageName);
}),
};
The styleguide can be viewed at http://localhost:6061/.
A note on transpiled code
All of the packages in this repo publish transpiled code, meaning their source .tsx?
files are converted to .js
files that build systems can understand without configuration. Their package.json
main
and module
fields point to their transpiled code, which lives in .gitignore
d /cjs
(main
) and /esm
(module
) directories next to /src
.
Because of this, it's important to be aware that packages may need to be transpiled in order to be imported while working in the monorepo. This minimizes how much configuration packages need, but may change in the future if it proves too cumbersome.
The styleguide uses the source
field in package.json
to import /src
code instead of transpiled code so that transpiling isn't necessary for it, but tests and other commands may need transpiled code to exist.
š To build a package, run yarn build
from the package's directory.
š To build all packages, run yarn build:all
from the repo root.
Developing within other projects
The packages in this repo are published to our private npm registry and can be consumed by other projects:
# Log into npm if you haven't already.
# This is necessary to access our private npm registry.
npm login
# Install the package to your `dependencies` via `yarn`, `npm`, etc.
yarn add @zapier/design-system
In order to see local package changes within codebases that specify it as a dependency, you'll need to use yalc
or yarn link
.
yalc
is preferred because it installs built versions of packages, whereas yarn link
does not. yalc
behaves more closely to npm install
. Packages' main
and module
fields in package.json
point to transpiled code in /cjs
and /esm
directories which only exist if npm run build
is run within the package, which yalc
does automatically in a flow more similar to npm install
and npm publish
.
With yarn link
you'll have to rebuild the package using yarn build
each time you make a change. With yalc
you'll need to yalc publish
.
# First, `cd` into the directory of the package you're working on.
cd packages/design-system
# After making changes, publish them locally.
yalc publish
# Then `cd` into the consuming project's repo.
cd my-project
# Install the locally published changes.
yalc add @zapier/design-system
Note: yalc
solves most of these problems so only use yarn link
if you need to, and keep these things in mind:
- Use
yarn list @zapier/package-name
to ensure that only one version of the package is installed. Multiple versions may cause issues. - Use Webpack's
alias
configuration to avoid multiple versions ofreact
. This can be done by settingreact: require.resolve('react')
withinresolve.alias
in thewebpack.config.js
file. This is necessary because node module resolution is relative to source files, soreact
would be used from thenode_modules
of two separate repos sinceyarn link
symlinks to a separate repo with its ownnode_modules/react
;alias
ensures that only the consuming repo's version ofreact
is used. - Use TypeScript's
paths
to handle redeclared module issues, as nicely summarized in this comment. Withoutpaths
, TS will use two identical or near identical@types
definitions across two separatenode_modules
directories, which it can't handle, andpaths
ensures that it points to the exact same files. This is effectively the same issue that webpack'salias
solves for bundling code, andpaths
solves it for TS.
Publishing
How to publish a new version of a package
- Run
rush change
in your PR. This will prompt you to choose between major, minor, and patch, and will also prompt you for a changelog description. CI will enforce that you've run this step. - Commit the generated files.
JSON files will have been generated by
rush change
for each package that changed, and those files need to be committed. - Merge your PR.
Once your PR has passed CI and has been approved, merge it into
master
. - Publish a new version. (Automated š¤)
This process is automated in GitHub Actions, however the following steps can be used to publish new versions manually if necessary. (Note: only repo admins and CI can do this since it involves pushing to
master
)- Run
rush publish
to perform a dry run and confirm it's publishing what you expect. - Run
rush publish -ap -b master
to publish all packages. This will a) publish each package to npm, b) use the.json
files generated byrush change
in earlier steps to updateCHANGELOG.md
andCHANGELOG.json
, c) delete the now-usedrush change
files, d) commit those changes with appropriate tags, e) merge those changes intomaster
and push them.
- Run
How to publish a temporary version of a package
This process publishes temporary versions of a package that can be used to test them within an external PR.
cd
into the package's directory. This is the package you want to publish a temporary version for.- Run
npm run publish-temp
. This will create a prerelease version of the package onv0.0.0
, using the git branch name for the prerelease name and tag. It will increment the prerelease version based on the latest tagged version on npm, and attach the git SHA as an extra piece of glue. It will not commit thepackage.json
version change, and there's no need to commit it. - Use your temporary version. Update the necessary
package.json
files in the consuming PR to use the temporary version you published. Tip: You can use the generated tag to avoid updating the specificversion
each time. For instance you could set"@zapier/design-system": "zds-123"
to use thezds-123
tag, which would be used for git branches that hadzds-123
in their name. Remember that the lockfile will still lock to a specific version though!
Tips and tricks
This repo has a lot of tools to facilitate writing code. A few specific ones to call out:
yarn scaffold
scaffolds a package when run from the repo root. It will create a new package under thepackages/
directory, add the package torush.json
and a few other places, and symlink it withinpackages/node_modules/@zapier
(see elsewhere for why that workaround exists). Furthermore, it creates component scaffolding within the new package.yarn scaffold
within apackages/
package will scaffold a component based on that package's templates, which themselves will have been (initially) generated byrush scaffold
while generating a package. Besides scaffolding the component files, an import to the component will be injected into the rootindex.ts
file.
Other documentation
5 years ago