Skip to content

Instantly share code, notes, and snippets.

@serhii-havrylenko
Created October 28, 2018 13:22
Show Gist options
  • Save serhii-havrylenko/313a52ae76d12cb71ab185be8f1632c6 to your computer and use it in GitHub Desktop.
Save serhii-havrylenko/313a52ae76d12cb71ab185be8f1632c6 to your computer and use it in GitHub Desktop.

Everyone is tired with creating templates for each type of NPM package with pre-configured skeletons, managing different dependencies across own packages, linking packages for local development with instant packages rebuilding which is "wasting developers time", writing proper configuration for linters, babel, typescript, etc, and adds a lot of repeatable steps for developers. The worst part appears when one of the key dependencies is getting breaking change upgrade, for instance babel - it produces plenty of duplicated changes in each of the package, massive amount of merge request to be reviewed and introduces high change to make a mistake (or even worse - forget about change) in one of the packages. Hopefully, it's year 2018 and JavaScript world has a solution for managing group of the packages in the one repository. This approach calls monorepo.

There are many of approaches how to implement monorepo - yarn workspaces, buck, pants, lerna, and others. In this article we'll cover lerna approach with yarn as package manager. Moreover, lerna integrates so greatly with yarn workspaces as it allows to use workspaces commands directly in the repository even without lerna.

The goal

The goal of this article is to create monorepo starter with Babel for building packages, TypeScript for having benefits of statically typed languages in the JavaScript world, static code analyzing tools (Tslint, Stylelint), Prettier for having automatically formated code (no more tabs or spaces holly wars), Storybook for developing components, and last, but not least - Lerna for publishing. All components will be written with React and StyledComponents for CSS-in-JS styling.

Let's not waste time on long talks about what are those tools and why they are so important, and proceed to the real configuration and will see how everyone will benefit from each in the future.

For those who are impatient, here is the link to the repository which contains whole set of results form this article - configured monorepo, ready to go and use.

Pre-requirements

Packages:

Local NPM registry

Local NPM registry will be used in whole article to avoid publishing to global registry plenty of test packages. There are plenty of ways how to set up private NPM repository. In this example Verdaccio will be used for such purpose.

Configuration is simple tnd trivial:

https://gist.github.com/dc48833aee10385f401d53c880c82a3e

Now we could test how it works:

https://gist.github.com/f2eb5fc38adebc6988098ba6f6e4da3b

should still show package details and in console where we started verdaccio we should see incoming request

https://gist.github.com/56b5f826899111c8fb3d39cfc2fdd009

Moreover, you could open http://localhost:4873/ for searching published packages.

Getting started

Time to start our configuration.

Repository initialization

Lerna

https://gist.github.com/39ee652a622a85a47112a3d71b4879ff

And you have initial structure of the monorepo created by Lerna.

A little bit of important theory about packages Versioning. This step is the most important one on the repository creation stage as it would impact how do we publish/tags our packages.

Lerna supports two types of packages versioning:

  1. Independent
  2. Exact

When exact type is chosen, Lerna will use the same version for all packages in monorepo. In case when independent version is selected, Lerna will release each package with independent version. More details about versioning is on official Lerna page

This article will consider only independent versioning for all packages as on the initial stage of packages development of some packages would have much more releases then others, and with independent versioning we would have only required packages released.

Going back to the real examples:

https://gist.github.com/07065e2a75095791badab38603fd92e0

and new repository for packages with independent versioning is ready.

Setting up package manager

By default lerna is using NPM, however, it's quite simple to set Yarn as package manager:

https://gist.github.com/8a1f37bf517c18ab7dea6b035c42319b

Moreover, with "useWorkspaces": true we allow lerna to support yarn workspaces and with "packages": ["packages/*"] we specify in which folder(s) we would have all our packages.

TypeScript

typescript

TypeScript could be used in two ways:

Implementation from babel does not have all features from latest TypeScript (like const enum), however, in case of usage babel we do not miss all cool plugins and integration with all tools looks much easier.

In this article we would use only babel with typescript preset.

Installation

Just hit next command to install all required plugins and presets:

https://gist.github.com/545325ea561e79487a70d127dad81652

babel-core@7.0.0-bridge.0 should be noticed separately as it's needed for properly resolving babel-core packages for all libs which requires old version of babel and for avoiding duplicated packages installed and possible misusage of babel configuration. Next line have to be added to the root package.json:

https://gist.github.com/b26880140accbf1e2dc98c42174b37f2

Configuration

babel

Configuration is quite simple and trivial - @babel/preset-react and @babel/preset-typescript plus simple config for @babel/preset-env.

In Release candidate version, Babel team has removed support for stage-* and preset-201* packages, so it means that all actually used plugins should be set by users manually.

Apart from removing stage-* packages, Babel has changed approach for looking up config files - it looks for config in cwd till the first package.json. More details could be found in official Babel documentation

Let's configure babel.config.js with next data:

https://gist.github.com/170c09c06aa76fa67a0cafbe6dd02ed8

Building packages

Due to changes for config lookup, build command in monorepo should have path to .babelrc or babel.config.js specified, or as --root-mode option. It could be done directly in build commands:

  1. In root package.json:

https://gist.github.com/6a9c2565ade75a6e0fca845f15aafbdd

  1. build command in each package. In this case each of packages could be built independently without lerna. In this case build command is a little bit different:

https://gist.github.com/f76c58e6dd72aeacc51d74b2f46a513b

  1. Another way how to treat parent babel config is extends option provided by latest babel. The easiest approach for that is setting next lines in package.json on package level:

https://gist.github.com/df8388463c3ee00b72deab9f85ddeb3e

  • In this case build command doesn't require passing path to babel config and will be simplified:

https://gist.github.com/fce1c72ad9879321fdb7df52992f8299

  1. Last and easiest option is just add --root-mode=upward option to the build command which allows to resolve babel config upward from the current root. In this case build command looks like:

https://gist.github.com/004ad3d116ae69aee5d06477b5f9c400

Checking types

@babel/plugin-transform-typescript does not perform type-check for it's input. However, it could be checked with native typescript compiler (tsc). It should be run with option noEmit for checking types only without emitting any code.

Let's create minimal required tsconfig.json in the root of monorepo:

https://gist.github.com/ebaeac682da1fd603acb397376fb4641

and put it in prebuild step to run type checks automatically prior each build

https://gist.github.com/1a010dd88b4e505340cb3037964962c9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment