Monorepo setup with Lerna, TypeScript, Babel 7 and other — Part 4

Serhii Havrylenko
5 min readOct 28, 2018

--

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:

  1. 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.
  2. 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.

--

--