Mono repositories is something that I have been kept in mind for a couple of years. It is highly used be companies like Google and Facebook. Nowadays I am working in a project splitted in multiple repositories, which I believe could be a good fit for a mono repo.
We are using yarn, typescript and react with react-dom for the web and react-native for mobile. I have started experimenting with NX doing this cool course on egghead, and it has really impressed me. However, I usually prefer to understand the technology and build my own recipes / conventios for each case. NX is great, but sometimes it looks like magic. I will try it in more detail later.
With that, I have started doing this frontend masters course to organize my mind and connect some of the tools I have been using during the last years. I highly recommend anyone interested in mono repositories, with javascript, to subscribe frontend masters.
Here I am going to start a mono repo using yark workspaces with two small libraries built with typescript.
Pre requisites #
This dependencies should be installed globally:
- node (v14)
- yarn (v1.22)
Starting Repository #
mkdir my-mono-repocd my-mono-repoyarn init-yThis will create a package.json with standard information. If you prefer you could use yarn init only, and an interative cli will be started.
Configuring yarn workspace #
We create a packages where will be our libraries.
mkdir packagesConfigure yarn workspace adding the following to package.json:
"private": true,
"workspaces": [
"packages/*"
],
Create types package #
We are going to create a simple library for our types (models) in typescript
mkdir packages/types./packages/types will become your first package: @mr/types.
It needs a package.json, so create a new one with the following:
{
"name": "@mr/types",
"version": "0.0.1",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsc -b ."
},
"devDependencies": {
"typescript": "^4.0.3"
}
}
Here we are configuring the main on npm to point to our entry point on dist (dist/index.js). dist will be generated on our build. For types we use dist/index.d.ts, generated by our build as well.
Other than that, we have configured our build script to use typescript. For that we need to specify typescript as a dependency.
Typescript #
We need to configure a tsconfig.json to use typescript:
{
"compilerOptions": {
"module": "CommonJS",
"types": [],
"sourceMap": true,
"target": "ES2018",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"declaration": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
Types code #
Now we are ready to create our code. It is a simple code, just to exemplify. Create a src folder and the following files:
- src/Book.ts
- src/User.ts
- src/index.ts
src/Book.ts #
export interface IBook {
id: string;
title: string;
author: string;
}
export function bookToString(book: IBook): string {
return `${book.title}`;
}
src/User.ts #
export interface IUser {
id: string;
firstName: string;
lastName: string;
}
export function userToString(user: IUser): string {
return `${user.firstName} ${user.lastName}`;
}
src/index.ts #
export * from "./Book"; export * from "./User";
Finally run yarn to install and link dependencies, and then try to build the @mr/types package from within its directory.
The interesting thing here is we are executing yarn on the root of our workspace and it is installing the depedencies for all our packages as well and linking it. If you go to each node_modules folder you will see the common libs are on the roo of our workspace and not in each library.
We are now ready to build our code for our types library:
cd packages/typesyarn buildyou should see that a packages/types/dist is created for you, and there are a few .js and .d.ts files within it (your build output)
Create utils package #
Now Create your own package for random utils
Examples of utils functions: #
- sum
- sub
- times
mkdir packages/utilsutils will become your first package: @mr/utils.
It needs a package.json, so create one, similar to the one for @mr/types
Typescript configs #
Replicate those configs on utils packages.
We now have a similar tsconfig.json for our @mr/types and @mr/utils packages. I could be risky and hard to maintain, so we are going to create a general tsconfig, and extend from it in multiple places.
We create ./packages/tsconfig.settings.json:
{
"compilerOptions": {
"module": "CommonJS",
"types": [],
"sourceMap": true,
"target": "ES2018",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"declaration": true
}
}
And update ./packages/types/tsconfig.json:
{
"extends": "../tsconfig.settings.json",
"compilerOptions": {
"composite": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
The important parts here are extends, using the general configs create on tsconfig.settings.json and composite: true. From typescript documentation we get the following explanation about composite:
"Referenced projects must have the new composite setting enabled. This setting is needed to ensure TypeScript can quickly determine where to find the outputs of the referenced project. Enabling the composite flag changes a few things:
- The rootDir setting, if not explicitly set, defaults to the directory containing the tsconfig file
- All implementation files must be matched by an include pattern or listed in the files array. If this constraint is violated, tsc will inform you which files weren’t specified
- declaration must be turned on"
Do the same for @mr/utils.
Finally, create a packages/tsconfig.json that refers to each package:
{
"files": [],
"references": [{ "path": "utils" }, { "path": "types" }]
}
Try running this from the root of your project now:
yarn tsc -b packagesBoth types and utils should build! You could havfe some problems and need to clean *.tsbuildinfogenerated in each package. For now, do it by hand, if needed. Next section will make it automated.
Clean script for builds #
Each package we create will put its build output in its own folder, so we should set ourselves up for an easy way to destroy it and "build again from scratch".
We can install a workspace dependency (at the root of the project, not a dependency of any package) to handle this in a platform-independent way:
yarn add -WD rimrafThe -WD makes it a workspace level (W) dependency, in this case a dev depedency (D).
Then, go to types/package.json and utils/package.json and add a clean script at scripts
"clean": "rimraf dist *.tsbuildinfo"Now, we can go to either of these projects and run yarn clean to delete build output, and yarn build to make a fresh build.
Published