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
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.
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.
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.
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.
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
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
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.
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.
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.
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
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.
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.
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
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 packagesconventionalCommits
- 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.