Setup Yarn workspaces with typescript.


Setup Yarn workspaces with typescript.

Divine Hycenth6 min read

Published on March 06, 2021.

Last edited: March 03, 2023 by: Divine Hycenth


Setup Yarn workspaces with typescript.

A complete guide on how to setup Yarn workspace for React web, and React-Native(expo) with Typescript.

Prerequisites#

  • You must have Typescript installed on your system and some basic knowledge of it.

  • Basic knowledge of React and React Native

Recently i started working on a Full stack Airbnb clone project from @benawad on Youtube that consists of a Nodejs + Graphql server, React, and React Native but i couldn't get it to work because of the resent changes in package versions.


Workspaces#

Workspaces are a new way to setup your package architecture that’s available by default starting from Yarn 1.0. It allows you to setup multiple packages in such a way that you only need to run yarn install once to install all of them in a single pass.

Your dependencies can be linked together, which means that your workspaces can depend on one another while always using the most up-to-date code available. This is also a better mechanism than yarn link since it only affects your workspace tree rather than your whole system.


Our repo structure#

  • Yarn handles the dependencies.

  • One folder per package inside packages directory.

  • All packages share the same structure.

  • Each package defines only its runtime dependencies.

  • All the tooling, dependencies and devDependencies are shared and live in its own package.

  • Each package contains the required configuration files for the tooling. Each file extends a common base configuration (we use Babel, Jest and ESlint + Prettier to compile, test and lint/prettify the code).

  • Each package symlinks a common task script that defines how the different tools must be invoked.

  • There is a “hub” package. It depends on all the other packages and allows easy usage of the framework (a single awesome dependency).

  • All packages share the version number.

  • Publication is handled by a custom publish script that will be used by the CI environment.

Let’s see each part in greater detail.


Monorepo base: Yarn#

The base package.json file looks like:

{
"private": true,
"workspaces": ["packages/*"],
"name": "expo-yarn-workspace-demo"
}
  • workspaces an array of directories

  • The build script tells lerna to run all the build commands in our /packages directory.


Expo with Typescript#

We will initialize our EXPO by running the following commands.

# Install the command line tools
npm install --global expo-cli
cd packages
expo init app

Use your arrow key to select blank (Typescript) for a typescript project and complete the steps you're prompted with.


expo init app


If you follow the above steps correctly its going to create an /packages/app folder in the packages directory and install all the necessary dependencies.

If you run into issues while generating your expo boilerplate then try to generate it in a folder and move it into your /packages folder.


React with Typescript#

Now we are going to generate our React-typescript boilerplate by running the following commands:

yarn create react-app web --template typescript

The above command will generate a typescript boilerplate of react and install all the necessary dependencies.

If you startup your expo project by running yarn start your app is going to crash because yarn is hoisting some expo packages which we can prevent by telling yarn not to do so. Read more about yarn workspace hoisting.

Add the following piece of code in the root package.json file.

// package.json
{
"private": true,
"workspaces": ["packages/*"],
"name": "expo-yarn-workspace-demo",
"scripts": {
"build": "lerna run build"
},
"devDependencies": {
"lerna": "^3.20.2"
},
"nohoist": [
"**/react-native",
"**/react-native/**",
"**/expo",
"**/expo/**",
"**/@babel/core",
"**/@babel/core/**"
]
}

The root tsconfig.json should look like:

{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"lib": [
"dom",
"ES2015",
"dom.iterable",
"ES6",
"ES2017",
"ESNext.AsyncIterable",
"ESNext"
],
"sourceMap": true,
"removeComments": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
}
}

Your packages/web/tsconfig.json file should look like:

{
"extends": "../../tsconfig.json",
"compilerOptions": {
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": ["src"]
}

Your packages/app/tsconfig.json file should look like:

{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"jsx": "react-native",
"lib": ["dom", "esnext"],
"moduleResolution": "node",
"noEmit": true,
"skipLibCheck": true,
"resolveJsonModule": true
}
}

Next thing is to configure babel in our packages/app folder.

create a file in your packages/app and call it babel.config.js and add the following piece of code:

// babel.config.js
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
};
};

The babel-preset-expo plugin extends the default React Native preset (metro-react-native-babel-preset) and adds support for decorators, tree-shaking web packages, and loading font icons with optional native dependencies if they're installed.

Create a metro.config.js file in your packages/app directory and paste the following configuration:

const { createMetroConfiguration } = require("expo-yarn-workspaces");
module.exports = createMetroConfiguration(__dirname);

🚀 Now you can run yarn start in your packages/app folder or packages/web and everything will work fine.

  • One great thing about yarn workspaces is we can create a local package and share it across our web, app or even a custom folder (server)

Let me show you how it works with Typescript.

Create a folder in your packages directory and call it common then create a package.json file with the default setup by running:

npm init -y

your packages/common/package.json file should look like:

{
"name": "@expoYarn/common",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"devDependencies": {
"@types/node": "^10.3.2",
"typescript": "^2.9.1"
},
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch"
},
"keywords": [],
"author": "",
"license": "ISC"
}

Note the name property is set to @expoYarn/common where common is the name of this current package.

Next create in the common directory and call it src/


We are going to add a simple code snippet that is going to be accessible across our application.

Create a file in the src directory, call it index.ts then paste the following code that accepts two parameters which are numbers and returns a sum of the two numbers:

export const add = (a: number, b: number): number => a + b;

We are going to build our project by running tsc in our common directory to build our code into a dist folder.


To be able to access this module in our web directory we are going to add it manually to our our package.json int the web directory:

{
...
"dependencies": {
...
"@expoYarn/common": "1.0.0"
}
}

run yarn install to link the newly added package then we will test it out by using it in our App.tsx to log the sum of two numbers. Your App.tsx should look like:

import React from "react";
import logo from "./logo.svg";
import { add } from "@expoYarn/common";
import "./App.css";
const App = () => {
// Testing out our add function
const result = add(10, 20);
console.log(result); // => Returns [30]
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
};
export default App;

You can now see how handy yarn workspaces can be because it saves allot of code which means we are following the Don't Repeat Your Self (DRY) principle of Object Oriented Programming (OOP) although there was allot of setup but No Pain No Gain.

Complete Code