August 2019
The original article below is fairly old, the community is moving away from Lerna due to the complexity it introduces.
If you need to share code between apps I recommend for you to go with TypeScript project references. Check this repo by Ryan Cavanaugh for a working example.
If you are using javascript in different parts of your platform (mobile app, SPA web app, node server, etc.) at some point you might end up duplicating code between them, sometimes it makes a lot of sense to get rid of duplication, sharing models for example.
The deployments for this example are:
All of them reside within a single repository, from which individual docker containers are built and deployed to the cloud.
Lernajs is a tool that shares codes within a monorepo, it handles your package dependencies by hoisting them to the root folder of the repo.
Imagine using ramda
in all of your applications but installing it only once at the root. By hoisting, Lernajs does exactly that and symlinks it to the application node_modules
folder. Furthermore, it has some nice little utilities that you can use to manage your workflow like adding new packages to applications, running multiple applications in parallel, etc.
Specifically what I wanted to share between the deployments: redux actions, redux reducers and epics, typescript models and some utility code, I crammed all this code into a shared
package which is being used by our frontend
and mobile
packages, declared as a dependency.
So what remains is mostly UI, routing and platform specific code. Below is how our folder structure looks like.
Our refactored code, shared contains common code
These are then imported in the frontend
and mobile
packages where we create our store (combing all the reducers from shared
) and root epic (combining all the epics from shared
). The reason for creating the store inside the frontend
and mobile
packages rather than in shared
is simple; we want to have flexibility so that we can add other platforms specific modules or enhance reducers on a per application basis.
Assuming you have a
UserModel
class inshared
you would simple import it like so_
For the redux actions, they are used as you would normally dispatch any action (the only difference being that your actions are now imported from the shared
package). You can of course, add more actions inside the frontend
/mobile
packages and supply your own epic
to handle them and it’ll all work seamlessly™.
You can read more about lerna on their website, but here are a couple of steps that we followed and should get you halfway there:
npm install -g lerna
Extract your code and place it at the root of your mono repository, you should also add a ‘prepare’ npm script, in our case we do typescript compilation to output to a lib folder
Run lerna init in your root folder
lerna init
lerna add wa-core — scope=frontend
lerna add wa-core — scope=mobile
lerna bootstrap
There might be some cases where you don’t want some packages to be hoisted e.g. react-native. You can use a nice feature of yarn workspaces called nohoist
, which would make lerna not hoist them to the root.
The latest release of typescript has what they call project references
, this essentially is the same thing that we want, you can read more about it in their announcement post but here are a couple of steps to get you started.
After refactoring and extracting the shared code, your shared folder should contain a tsconfig.json
file, now in the package were you want to import this code, you should modify your tsconfig.json
to contain the following:
{
…
"composite": true,
"declaration": true,
"references": [
{ "path": "../shared" }
],
…
}
Once that is done, you can import your code by doing:
import { Foo } from ‘../shared/Foo’;
To compile your project you now have to pass the — build flag (or -b) and that should be it, typescript should hoist and transpile the referenced code for you.