Skip to content

Instantly share code, notes, and snippets.

@serhii-havrylenko
Created October 28, 2018 15:29
Show Gist options
  • Save serhii-havrylenko/8f25cfacf6589ec4a40e2de2172c8375 to your computer and use it in GitHub Desktop.
Save serhii-havrylenko/8f25cfacf6589ec4a40e2de2172c8375 to your computer and use it in GitHub Desktop.

Multiple packages

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:

https://gist.github.com/85189fa9406b12e9fae2b7500158768e

Now we could add @taxi/input to the list of dependencies. This could be achieved easily with next command:

https://gist.github.com/a95ffc62d80d6477fc1f26c008a31790

As soon as we have it, it's time to run tsc and check what typescript thinks about our code:

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

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:

https://gist.github.com/0536fd076f9987d86f02d4c9ebfc0d80

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:

https://gist.github.com/88dd8fea231faa8d3ade6a0251556c2b

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:

https://gist.github.com/bd5ed5b3876133dfb9b4c39f89bc16aa

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:

https://gist.github.com/f384448b08a9da562032d50d3523dec4

Time to test it:

https://gist.github.com/72cda0f027146499c1d08931ca1b5890

As the result we should have *.d.ts files generated in the dist folder:

https://gist.github.com/1009a031f2b261fc861443d9d2545323

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.

https://gist.github.com/d356795f290aeb215a8fe0d4345c98d3

Tslint

As soon as we generate declarations and run tslint we would have errors like:

https://gist.github.com/b4b3eee09d65eefe24cbaa6adec36b24

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:

  1. tslint.prod.json

https://gist.github.com/e1512fba53fae4ab934f949bcfa7f3ec

  1. tslint.test.json

https://gist.github.com/b387660be45c363349799f91820174e5

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:

https://gist.github.com/d505a22773d3bff621a642c7b33c89aa

"@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:

https://gist.github.com/a8bde07545ccf89962b584a8ab75d127

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

https://gist.github.com/5a45a374110520f5665808a714715736

This code is rather simple and self explanatory. The most important part is in reducer for building aliases:

https://gist.github.com/ca581122ab8f446986a5e26e4e2af5c0

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:

https://gist.github.com/873ed9333e12dedd9a0be5941ab390d8

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:

https://gist.github.com/cf0e3e55377ee9259a22ce23deb21850

'@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.

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