I know there are plenty of solutions for managing monorepos, Lerna, and so on. To be honest, I am not checking those anymore.
Our setup (e.g., at https://screen.studio) is, I believe, very simple and effective. I set it up, I think, 4 years ago and have never looked back.
Typescript, bundling, libraries, and apps
At the core of our setup, we have the concept of libraries and apps.
App is something the end user interacts with. In our case, it is our website and our Electron app.
Apps are responsible for bundling libraries. Libraries are simply buckets of code. Libs have no build pipelines; they don’t even have a
src
folder. They are flat, with very simple package.json
and TypeScript files. No configs, no build scripts, and no type definitions (more on that later). Apps will be responsible for building libs.Core packages config
At the core, we have
package.json
like this:And then each package have
package.json
like thisBelieve it or not - this is the entire
package.json
of quite a huge part of our codebase.You could technically say it has no dependencies defined, which is not a good thing. I kind of agree, but I think out of laziness, I usually add dependencies to the root package.
Now, you run
yarn install
, and magic starts to happen.@s/ui
is moved to node_modules/@s/ui
, but it is not copied there - it is symlinked, so technically <repo>/ui
folder is mirrored with <repo>/node_modules/@s/ui
- every change that happens in the source file is also present inside node_modules. This is important, as it allows for a great hot reloading experience, etc.But note, that inside
node_modules
we have .ts
files, which are not bundled at all - those are source files.Now, you simply import those like:
It just works. It is fully typed and hot reloaded. You don’t need
tsconfig
imports magic to make root import work. It works because our package is mirrored into node_modules/@s/ui
. It does not require .d.ts
files at all, because there is no src
or dist
folder, so it simply reads types directly from the source code.The only thing you have to do is to define a proper bundling config, as often by default bundlers expect things in
node_modules
to already be bundled.For example, our app called
web
is using next.js
and our next.config.ts
needs something like this:And that’s it. Next.js now knows that it needs to transpile those TypeScript modules when they are imported. Hot reloading works out of the box. Any change inside our
ui
package is instantly reflected when browsing localhost
, there is no need to rebuild, update type definitions etc.Some other tricks
At the root
package.json
, we have scripts like those:This allows you to manage the entire monorepo from the root folder. For example, our
web
package has its own development script that will start the Next.js development server.So from the root you can do
yarn web dev
and it will just work. Or if you need to add some dependency only to the web package, you do yarn web add some-package
Yarn 4.5 and corepack
We try to use the latest
yarn version
. Our goal, however, is to make the onboarding developer experience as smooth as possible.Not sure if you’re familiar with it, but
corepack enable
is a really powerful Node command. With this one command (which is available already if you have Node installed), it will replace your global Yarn with the local one defined in the package. Thus, you can do yarn set version 4.5.0 --yarn-path
, and it will create a plug-and-play copy of Yarn 4.5 inside your repo, creating .yarnrc.yml
like this:And you’re good to go. In your README, your Getting Started section can look like:
- The only requirement - have
nvm
installed on your machine
- Clone repo
- run
nvm install
(assume you have.nvmrc
which you should)
- run
corepack enable
to make sure you will useyarn
”binary” which is inside the repo
- run
yarn install
- optional - get your
.env
file from your colleagues
- run
yarn app dev
oryarn web dev
- enjoy coding