Creating a TypeScript monorepo for React and React Native with Yarn Workspaces (and optionally Webpack)
Introduction
Creating a Yarn monorepo that works well with React and React Native can be challenging and isn’t very well documented. This guide and the accompanying repository (typescript-yarn-monorepo) can help you set up such a monorepo and ultimately improve your productivity.
Example Monorepo Architecture
To demonstrate a cross-platform React and React Native app with shared components, this guide uses the following architecture and directory structure:
Core
: a standalone NPM package using TypeScript. It exposesIExampleService
with a basic implementation,BasicExampleService
.Extended
: another NPM package using TypeScript. It consumesCore
and provides a different implementation ofIExampleService
, calledExtendedExampleService
.Web
: a React web app that consumesCore
andExtended
.Native
: a React Native app that consumesCore
andExtended
.
Guide Organization
This guide is composed of three parts, each building on the previous part and mapping to a corresponding branch in the repository:
- TypeScript React and React Native monorepo with Yarn Workspaces: use-yarn-nohoist
- Using Webpack for bundling components: use-webpack
- Using Paths with TypeScript and Webpack use-webpack-with-paths
TypeScript React and React Native monorepo with Yarn Workspaces
Steps
- Create and change to a directory for your project
git init
- Add an appropriate .gitignore
- See .gitignore for a good starting point
-
mkdir packages/core
yarn init
yarn add -D typescript
tsc --init
- add
es6
anddom
libs to core/tsconfig.json
{
"compilerOptions": {
...
- // "lib": [],
+ "lib": [
+ "es6",
+ "dom"
+ ],
...
}
}
- Set
sourceMap
totrue
in core/tsconfig.json
{
"compilerOptions": {
...
- // "sourceMap": true,
+ "sourceMap": true,
...
}
}
- Enable stricter TypeScript checking
{
"compilerOptions": {
...
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
...
}
}
- Set
outDir
in core/tsconfig.json
{
"compilerOptions": {
...
- // "outDir": "./",
+ "outDir": "./dist",
...
}
}
- Set
rootDir
in core/tsonfig.json
{
"compilerOptions": {
...
- // "rootDir": "./",
+ "rootDir": "./src",
...
}
}
- Set
moduleResolution
tonode
in core/tsconfig.json
{
"compilerOptions": {
...
- // "moduleResolution": "node",
+ "moduleResolution": "node",
...
}
}
- Set
skipLibCheck
totrue
in core/tsconfig.json
{
"compilerOptions": {
...
+ "skipLibCheck": true,
...
}
}
- Add the
IExampleService
interface in core/src/IExampleService.ts
export interface IExampleService {
doExampleWork(input: string): Uint8Array;
}
- Implement
IExampleService
withBasicExampleService
in core/src/BasicExampleService.ts
import { IExampleService } from "./IExampleService";
export class BasicExampleService implements IExampleService {
doExampleWork(input: string): Uint8Array {
let result: Uint8Array = new Uint8Array(input.length);
for (let i: number = 0; i < input.length; i++) {
result[i] = input.charCodeAt(i);
}
return result;
}
}
- Export
IExampleService
andBasicExampleService
in core/src/index.ts for easier consumption
export * from "./IExampleService";
export * from "./BasicExampleService";
- Specify “dist/index.js” (the output from building src/index.ts) as the main entrypoint in core/package.json
{
...
"version": "1.0.0",
- "main": "index.js",
+ "main": "dist/index.js",
"author": ...
...
}
- Set
declaration
totrue
in core/tsconfig.json
{
"compilerOptions": {
...
- // "declaration": true
+ "declaration": true,
...
}
}
- Specify “dist/index” (the location of index.d.ts, the compiled declaration) as the entrypoint for typings in core/package.json
{
...
"main": "index.js",
+ "typings": "dist/index",
"author": ...
...
}
- add .npmrc to core directory to prevent files in src/ from being included in the npm package
- This is necessary to avoid compilation issues for consumers of the package
src/
- At this point you can run
tsc
in the core directory and the compilation should succeed. You’re directory structure will like this:
-root/
-packages/
-core/
-src/
-IExampleService.ts
-BasicExampleService.ts
-dist/
-BasicExampleService.d.ts
-BasicExampleService.js
-BasicExampleService.js.map
-IExampleService.d.ts
-IExampleService.js
-IExampleService.js.map
-index.d.ts
-index.js
-index.js.map
-node_modules/
-.npmrc
-package.json
-tsconfig.json
Repository Notes
- Each commit message describes the command that was run or the change that was made.