Monorepo setup with Lerna, TypeScript, Babel 7 and other — Part 4
Series
- Basic monorepo setup with Lerna, Babel and TypeScript
- Tslint, Stylelint, Jest, Storybook and Conventional Commit in the monorepo
- First package development and publishing
- Multiple packages and magic in TypeScript monorepo configuration
Basing on our previous stories we have monorepo ready and one simple component published as separate package. It’s time to create second package which is dependant on our first one and check how integration between them looks like and how it’s simple to write code in the monorepo packages instead of package per repository approach.
As everyone knows, the main goal of the monorepo is to have multiple packages inside one repository for easily solving dependencies between them and simple release process.
Let’s create second package which is dependant on our @taxi/input
and check how our tools works with it.
Development
Just create second package in the same way as the first one. In our example it would be @taxi/login-form
with next content:
Now we could add @taxi/input
to the list of dependencies. This could be achieved easily with next command:
As soon as we have it, it’s time to run tsc
and check what typescript
thinks about our code:
It happens because in the package.json
file for @taxi/input
package we have "main": "dist/index.js"
which is pointing to the build version of the package and we have not run build
command. So, let's build our packages and check tsc
again:
Now typescript
could find our package, however it doesn't know anything about typings for that module. It happens because we built our package with babel
and it cannot create declaration files.
Declarations generation
Let’s configure tsc
to generate types declarations for our modules. As a first step, we have to move login-form
package to the different folder (we would need to have only compilable with tsc
packages in the packages
directory).
We would need:
tsconfig.build.json
in the root of monorepo with:
Where we specify that we need to emit declarations only and exclude test, stories and dist folder.
Next we would need tsconfig.build.json
inside the each of our packages with:
Here we would specify from where we would like to take files for generating declarations and where we would like to put them.
Now we would need script inside the root of monorepo for generating declarations:
Time to test it:
As the result we should have *.d.ts
files generated in the dist folder:
Now we could check that @taxi/login-form
works as well, just return it back to the packages
directory and check that tsc
works now.
Tslint
As soon as we generate declarations and run tslint
we would have errors like:
It happens because tslint
tries to validate *d.ts
files as well. Let's add the to the ignore. With latest version of tslint
we could do it in configuration file, so:
Now if we run yarn lint:ts
we won't have any errors related to .d.ts
files.
Drawbacks
We are able to generate declarations for all packages, run storybook
and tests
, however, as soon as we change @taxi/input
we would not see any changes in the storybook
for @taxi/login-form
because we still would use previously built version. To see these changes we would need to rebuild packages one more time. The same applies to the jest
runs.
This approach looks not so cool if each change in one of the packages would require constant rebuild. Lucky we have better option how to fix this issues. More details in the next paragraphs.
Typescript
Firstly, let’s remove all dist
folders inside our packages to have clean code only. On this stage tsc
should still fail. Now we could extend tsconfig.json
with:
"@taxi/*": ["packages/*/src"]
tells to tsc
where to search for @taxi/
packages. In our case we would like to search them in packages/*/src
folders as there we have our source code.
Now we could run tsc
and it would finish successfully without any errors like we had earlier.
Storybook
As storybook
is using webpack
with awesome-typescript-loader
and babel
integration, as soon as we start storybook we would get next errors:
It happens even when tsc
could find those modules. The reason of this is simple: tsc
checks files, babel
transforms them and tries to execute, and it doesn't know anything about @taxi/input
as main
still points to the dist
folder.
However, we could override it with aliases for webpack
. It could be done in two ways:
- Manual one, in this case we’ll have to define each package which we would like to have in
webpack.config.resolve.alias
. This approach is simple, but it introduces potential issues in the future when someone could forget about aliases or introduces wrong one. - Automated one with next info in the
storybook/webpack.config.js
This code is rather simple and self explanatory. The most important part is in reducer for building aliases:
With this reducer we would have automatically generated list of our packages from packages
directory.
Now we could start storybook and check that it works, moreover, if we change @taxi/input
and check stories for @taxi/login-form
they would have changes immediately without even building packages.
Jest
We have tsc
and storybook
working, however, if we run jest
it would fail with:
It happens because jest
doesn't tolerate tsconfig.json
, webpack
aliases or even babel-webpack-aliases
. However, this problem could be solved with simple line in jest.config.js
:
'@taxi/(.+)$': '<rootDir>packages/$1/src
will tell jest where to find source code for @taxi/
packages and again it points to the src
instead of dist
.
Now we could run yarn test
and it will work as expected without even building packages.
Final words
The goal of the article was to configure monorepo with lerna
, typescript
, babel
, tslint
, stylelint
, jest
and semantic-release
and it was achieved. We do have monorepo with all those tools in place and it's ready for real usage. Packages could be created, developed, tested, presented and published easily with one commands in the root of monorepo.
For those who doesn’t want to use babel
, but pure typescript
, changes would be extremely simple - just drop babel.config.js
, change awesome-typescript-loader
for using tsconfig.json
instead of babel
, ts-jest
to use it as well and build
command from babel
to tsc
(just replace emitDeclarationOnly
to false
in tsconfig.build.json
) and that's it, typescript
could be used without babel
.