2.2.6 • Published 5 years ago

zapier-design-system-repo v2.2.6

Weekly downloads
5
License
-
Repository
-
Last release
5 years ago

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 .gitignored /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 of react. This can be done by setting react: require.resolve('react') within resolve.alias in the webpack.config.js file. This is necessary because node module resolution is relative to source files, so react would be used from the node_modules of two separate repos since yarn link symlinks to a separate repo with its own node_modules/react; alias ensures that only the consuming repo's version of react is used.
  • Use TypeScript's paths to handle redeclared module issues, as nicely summarized in this comment. Without paths, TS will use two identical or near identical @types definitions across two separate node_modules directories, which it can't handle, and paths ensures that it points to the exact same files. This is effectively the same issue that webpack's alias solves for bundling code, and paths solves it for TS.

Publishing

How to publish a new version of a package

  1. 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.
  2. 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.
  3. Merge your PR. Once your PR has passed CI and has been approved, merge it into master.
  4. 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)
    1. Run rush publish to perform a dry run and confirm it's publishing what you expect.
    2. Run rush publish -ap -b master to publish all packages. This will a) publish each package to npm, b) use the .json files generated by rush change in earlier steps to update CHANGELOG.md and CHANGELOG.json, c) delete the now-used rush change files, d) commit those changes with appropriate tags, e) merge those changes into master and push them.

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.

  1. cd into the package's directory. This is the package you want to publish a temporary version for.
  2. Run npm run publish-temp. This will create a prerelease version of the package on v0.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 the package.json version change, and there's no need to commit it.
  3. 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 specific version each time. For instance you could set "@zapier/design-system": "zds-123" to use the zds-123 tag, which would be used for git branches that had zds-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:

  1. yarn scaffold scaffolds a package when run from the repo root. It will create a new package under the packages/ directory, add the package to rush.json and a few other places, and symlink it within packages/node_modules/@zapier (see elsewhere for why that workaround exists). Furthermore, it creates component scaffolding within the new package.
  2. yarn scaffold within a packages/ package will scaffold a component based on that package's templates, which themselves will have been (initially) generated by rush scaffold while generating a package. Besides scaffolding the component files, an import to the component will be injected into the root index.ts file.

Other documentation