Skip to content

Instantly share code, notes, and snippets.

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

Peer dependencies

Lerna does not have ability to add peer dependency for packages. Nevertheless, it can be done with yarn workspaces - all we need is just add workspaces definition to the root package.json

https://gist.github.com/4c5fc14b378bb67766f504120d38a115

In our case react, as well as styled-components, are defined as peerDependencies as all our packages will have them, however we don't want to have plenty of dependencies on each package installed separately.

As we decided to set react and styled-components as peer dependency, we still should have them, and associated types definitions for typescript, installed in our node_modules. So let's add them as devDependency to the root of monorepo:

https://gist.github.com/87567bb5395edd29c5da417c1e54eb7a

Static code analyser

Having typescript for types checking could protect us from creating plenty of mistakes and save a lot of time for writing basic unit tests related to incorrect input data. However, it won't protect us from writing over-complicated, unreadable, or even hacky code. Even more, it won't protect us from using incorrect or unsupported CSS rules, would it be CSS, SCSS or CSS-in-JS.

Tslint

tslint

The easiest solution for automating such checks is using tslint for static code analyzing.

Let's install it as devDependency in the root of our monorepo:

https://gist.github.com/35ce8325b70db41a98a350be1b0864a5

and create simple config file for it tslint.json:

https://gist.github.com/e07091f26e96c19568eddfc13cd30cd7

and new script in package.json for linting *.ts files:

https://gist.github.com/15c1e1b48141ace6e3512038c2045683

Now static code analyzer could be run with simple yarn lint:ts command.

Stylelint

stylelint

Let's use stylelint for improving our CSS styles quality and readability. It could be easily used for analyzing CSS-in-JS as well as with simple CSS or SCSS files.

Installation and configuration process as simple as with tslint:

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

Create simple config file for it tslint.json:

https://gist.github.com/c1eb6ef8787744c6803f9c2b5b7738d9

and new script in package.json for linting *.ts files:

https://gist.github.com/c685a1880ae7606a8f671180776ebadb

Now static code analyzer could be run with simple yarn lint:css command.

All together

For now we have lint:ts for checking typescript code quality and lint:css for CSS, however, they are still separated commands and it would be uncomfortable to run them separately all the time. Let's group them in one unified lint command and run it with npm-run-al:

https://gist.github.com/fb68fe509effa845722ca51b08ece537

and new script in the root package.json:

https://gist.github.com/d4140559cf4fe302e8b8c6837bf9d7de

Note: run-p -c allows to run all lint:* commands even if one of them failed. It's useful in case of separated static code analyzer steps, as after one run we have output from tslint and stylelint, instead of only first failed.

Code formatting

prettier

For now we have typescript for static types checking, tslint and stylelint for static code analyzing. Still, we could write unreadable or not well formatted code. We could avoid all issues relate to code formatting with prettier. It will automatically format our code according to predefined standards. Moreover, it will fix some issues reported by tslint.

As usual, installation and configuration is very simple:

https://gist.github.com/9df163aa906359aba46c9a3f0f6e9ee5

Next is needed is .prettierrc:

https://gist.github.com/41575498fb029bba41368b3bed3c9862

and integration with tslint, as both of the tools have common rules (like tabWidth, trailingComma, etc). Next lines should be added to the tslint.json to make it work with prettier:

https://gist.github.com/2227aaa73ecf364977815906f4ccb23d

Important note is quotemark rule. Because of jsx usage we have to override default recommended rules which requires to have single quotemark everywhere.

Last but not least, let's add script to the root package.json for automatic code formatting based on defined above rules:

https://gist.github.com/702e1f77f7a88a7cb5175e9557176ba1

Testing

jest

For now we have TypeScript for checking types, tslint and stylelint for static code quality analyzing. Last, but not least part is testing. Let's use jest as test runner and test assertion tool. It has the best support for react, including snapshot testing, extensive mocking library, build-in coverage reporting and ability to run tests in different processes. ts-jest should be used for running typescript code. Installation is also quite simple:

https://gist.github.com/3a678d171f207baf88e99aba6163cc4a

jest could be configured in two ways:

  • yarn jest --init and answer for all questions
  • manually create config file with minimum required configuration

Here is basic setup from jest.config.js in the root of the monorepo

https://gist.github.com/0eb9a855a9911f5781c3649009a7ac87

Notes:

  • ts-jest could use babel config or pure typescript compiler. In our case we have babel configured, so it could be used with just 1 line in config:

https://gist.github.com/87fa52eb68b26577a2ef27b56d32f31b

  • coverageThreshold is protecting us from writing not enough tests and having poor test coverage.

As soon as we have basic config, it's time to install enzyme (and all related libraries) which is extending support for react:

https://gist.github.com/3be725012ede9621c598c12e5c22aefc

Add setupTestFrameworkScriptFile: '<rootDir>jest/setupTests.ts' into jest.config.js file and proper setup for enzyme into jest/setupTests.ts:

https://gist.github.com/c9bed9269b5ec0c13ed374cc86c30aec

Now jest is able to render react components, however, it will serialize snapshots as pure objects and we want to have it serialized as HTML markup. We could achieve it with proper serializer:

https://gist.github.com/3f6004c054db0e1d3b00d1922aaeff8c

and snapshotSerializers: ['enzyme-to-json/serializer'] in jest.config.js.

We are almost there, we are able to run tests and create proper snapshots. Nevertheless, we still have issues with styled-components - on each change in styles, styled-components is creating different class name. Based on it we'll have plenty of false negative tests fails just because of class name is changed. Let's fix it with proper tool

https://gist.github.com/ae190c40fe2f850df617f53ee790eee5

import 'jest-styled-components' should be added to the jest/setupTests.ts.

Just to sum up, jest.config.js:

https://gist.github.com/fac8092db481b0461a4f8110a62c60f2

That's it, jest is configured and could be run. Let's add test to the root package.json scripts:

https://gist.github.com/289ac2f6450f27f6156b0d4ec95ee5d8

Storybook

storybook

We already able to write code in monorepo with typescript, analyze it with tslint and stylelint, test it with jest. However, we still cannot see how our components will look like and we cannot even debug it properly.

There are plenty of ways how to present react component. Let's go with most famous one - storybook. It allows to present separate components and/or group of them as well as testing it in real browsers, and having documentation close to it.

Installation

Latest stable version of storybook@3.4.10 works with webpack@3, babel@^6 and typescript@^2.7. As we have latest @babel@^7 and typescript@^3 it's better to use next version of storybook which has the same set of dependencies as our monorepo, even if it's bleeding edge version.

If you do it for the first time, you should have @storybook@cli installed globally on your machine and init:

https://gist.github.com/13aa18eb72c5d47e5ad5507fe95ac989

It will automatically detect project type (react), installs all required packages and create basic configuration folder.

Typescript

Still, as we use typescript, we have to install typings for those packages, loader for wepback and needed peerDependencies:

https://gist.github.com/d8abbb55285c5861dc9812dbc641cf43

Storybook inits application as it is javascript based, as we have typescript everywhere, we have to change path resolution for stories in storybook/config.js file:

https://gist.github.com/c03087d9104a62a9ce350637610d3f69

Last but not least part is webpack.config.js inside storybook folder, just create it with next content:

https://gist.github.com/a0edd62fda5c2665539351638001ebce

and associated configuration part in tsconfig.json for awesome-typescript-loader:

https://gist.github.com/3ac0d73035b68323a5d1436f3535981b

Configuration part is ready, we could start storybook with yarn storybook command.

Tslint

As soon as we add storybook to our devDependencies and run yarn lint:ts we will get an error from tslint:

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

The reason is obvious, we use package which is installed as devDependency in our source code (in the story file). Unfortunately there is no options like override for specific path or files. It could be done by splitting lint-ts command into two separated for production code (which will be shipped as packages) and for test code (storybook, tests, etc).

Let's create config for tslint for production phase, called tslint.prod.json:

https://gist.github.com/3eaaba0014ce294e4c7e6bde5bf3ce03

Another config for the test phase called tslint.test.json:

https://gist.github.com/d47ac96d7ac36f10cea9fa7ea62f9329

"no-implicit-dependencies": false into tslint.json to disable this rule by default. This one is need to fix issues with IDEs as by default they use tslint.json for all files, whether it test or production code.

Last, but not least, scripts in the root package.json have to be adjusted:

https://gist.github.com/74b2664a040c543f9624a5589de720dd

Addons

Storybook allows to pass any props to the react component without rebuilding stories, just through UI interface. To do it, we have to add one more addons - @storybook/addon-knobs

https://gist.github.com/f196d03aa5c5dc7552436610bc211cba

NOTE: moment has to be installed because of wrong peerDependencies management on storybook and react-datetime level.

Next step is to add import '@storybook/addon-knobs/register'; to the storybook/addons.js and modify storybook/config.js to have global decorator for each story:

https://gist.github.com/92fde863e9d8d529b972994815c08800

Now knobs could be used in the stories.

Final build

As we already have build command as well as code formatting and static code analyzing tools in place, it's time to use them all together in the build process:

https://gist.github.com/613c302a6923607d5bc72253df96f15d

As soon as we run yarn build command, yarn automatically will run tsc for type checks, tslint for code quality analyzing and test on each of packages in the monorepo.

If they succeed, build will proceed and prepare all packages for publishing.

Committing your work

Let's use conventional commit for committing our work for having consistent commit messages across the monorepo and as a benefit, proper version creation per package basing on conventional-commit approach.

Let's install required packages:

https://gist.github.com/08e286190fb0ecc334f7c2a5268c50fa

cz-lerna-changelog should be installed with version ^2.0.0 as it supports latest lerna

Configuration is quite simple, just add next line to the root package.json file:

https://gist.github.com/e7abaf9e6eb209f575ac55c0774a52ac

and simple alias for commit command:

https://gist.github.com/86f4eb6e6118ea4e2010a4533a61d984

Now it's time to test it, just change something in one of the packages, stage changes with git and run yarn commit:

https://gist.github.com/1749521a5766ac1585ab674b978e63b5

Package publishing

As described earlier, lerna is used for publishing packages. It could be configured quite easily with next lines in the lerna.json:

https://gist.github.com/7f2224b50d92e33864266e0991b10b6e

The config is self descriptive, however, the most important parts are:

  • registry - specifies where do we want to publish our packages
  • conventionalCommits - allows us to use conventional commits for determining new versions

All other options could be found on the official lerna documentation.

Now we could add simple alias to the our scripts for having release command there:

https://gist.github.com/bd81e3304ccb5f3b7e60238a36a70cc6

That's it. The key part is configured and ready to be used. Time to test it with real packages.

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