TypeScript monorepo for React project

TypeScript monorepo for React project

TypeScript monorepo for React project - I want to create TypeScript monorepo for React project. I tried and I'm not happy with my result. This post describes what I did...👏👏👏👏👏

 Any advice on how to improve setup? As well there is a small rant in the end. Source code is here.

What I want to achieve?
  • Monorepo project, to be able comfortably develop several packages, which can be used separately but as well together
  • with TypeScript
  • for React project
  • with a testing library, I want to start with Jest, but as well we can choose something else
  • with Storybook (or similar tool) for React components development and showcasing
  • (nice to have, but optional) ESlint with eslint-config-react-app
  • (nice to have, but optional) Rollup to bundle and minify
  • (nice to have, but optional) pre-commit hooks with prettier

Packages structure

  • a - utility library
  • b - React components library, which depends on a
  • c - another React components library, which depends on a
  • stories - showcase of b and c package's components as well used for development (initial plan, can change later)
What I did

yarn

yarn instead of npm, because it supports workspaces to link cross-dependencies.

Create package.json in the root without version because we not going to publish it and with workspaces:

"workspaces": [
  "packages/*"
]

lerna

We will use lerna to run commands across all packages and "elevate" common dependencies.

Create lerna.json:

{
  "packages": ["packages/*"],
  "npmClient": "yarn",
  "useWorkspaces": true,
  "version": "0.0.1"
}

TypeScript


We will use typescript to check types and compile TS down to desired JS files (ES5 or ES2015, CommonJS or ES modules).

Create tsconfig.base.json. This is what you need to add to enable monorepo:

{
  "include": ["packages/*/src"],
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "baseUrl": ".",
    "paths": {
      "@stereobooster/*": ["packages/*/src"]
    }
  }
}

Create packages/a/packages/b/packages/c/packages/stories/. Add tsconfig.json to each one:

{
  "include": ["src"],
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    // to override config from tsconfig.base.json
    "outDir": "lib",
    "rootDir": "src",
    // for references
    "baseUrl": "src"
  },
  // references required for monorepo to work
  "references": [{ "path": "../a" }]
}

In package.json for packages b and c add:

"peerDependencies": {
  "@stereobooster/a": "0.0.1"
},
"devDependencies": {
  "@stereobooster/a": "*"
}

We need peerDependencies to make sure that when packages (abc) installed by the end user they will use the same instance of package a, otherwise, TypeScript can complain about incompatible types (especially if use inheritance and private fields). In peerDependencies we specify a version, but in devDependencies we don't need to, because we need simply to instruct yarn to use whatever version of package we have locally.

Now we can build projects. Add to root package.json:

"scripts": {
  "build": "lerna run build --stream [email protected]/{a,b,c}"
}

and to package.json for abc

"scripts": {
  "build": "tsc"
}

Problem 1: because of sub-dependencies (packages b and c depend on astories depends on abc) we need to build packages accordingly, e.g. first a, second b and c, third stories. That is why we can't use --parallel flag for lerna for build command.

React

Install @types/react@types/react-domreactreact-dom.

Add to tsconfig.base.json:

"compilerOptions": {
  "lib": ["dom", "esnext"],
  "jsx": "react",
}

Add to subpackage's package.json:

"peerDependencies": {
  "react": "^16.8.0",
  "react-dom": "^16.8.0"
}
Jest


We will use jest to run tests. Install @types/jest@types/react-test-rendererjestreact-test-renderer. Add jest.json. To eanbale TypeScript:

{
  "moduleFileExtensions": ["ts", "tsx", "js"],
  "transform": {
    "\\.tsx?$": "ts-jest"
  },
  "testMatch": ["**/__tests__/**/*.test.*"],
  "globals": {
    "ts-jest": {
      "tsConfig": "tsconfig.base.json"
    }
  }
}

to enable monorepo:

"moduleNameMapper": {
  "@stereobooster/(.*)$": "<rootDir>/packages/$1"
}

As well we will need to change tsconfig.base.json, because Jest doesn't support ES modules:

"compilerOptions": {
  "target": "es5",
  "module": "commonjs",
}

Add command to package.json

"scripts": {
  "pretest": "yarn build",
  "test": "jest --config=jest.json"
}

Problem 2: we will publish modules as ES5 + CommonJS, which makes no sense for React package, which would require some kind of bundler to consume packages, like Parcel or Webpack.

Problem 3: there are sub-dependencies, so we need to build all packages first and only after we can run tests. That is why we need pretest script.

Storybook

Install storybook according to official instruction.

We will need the following things in package.json:

"scripts": {
  "start": "start-storybook -p 8080",
  "build": "build-storybook -o dist"
},
"dependencies": {
  "@stereobooster/a": "*",
  "@stereobooster/b": "*",
  "@stereobooster/c": "*"
},
"devDependencies": {
  "@babel/core": "7.4.3",
  "@storybook/addon-info": "^5.0.11",
  "@storybook/addons": "5.0.6",
  "@storybook/core": "5.0.6",
  "@storybook/react": "5.0.6",
  "@types/storybook__addon-info": "^4.1.1",
  "@types/storybook__react": "4.0.1",
  "awesome-typescript-loader": "^5.2.1",
  "babel-loader": "8.0.5",
  "react-docgen-typescript-loader": "^3.1.0"
}

Create configurations in .storybook (again, based on official instruction). Now we can create stories in /src/b for b packages, in /src/c for cpackage.

Storybook will watch for changes in stories/src, but not for changes in a/srcb/srcc/src. We will need to use TypeScript to watch for changes in other packages.

Add to package.json of ab and c packages:

"scripts": {
  "start": "tsc -w"
}

and to the root package.json:

"scripts": {
  "prestart": "yarn build",
  "start": "lerna run start --stream --parallel"
}

Now a developer can run yarn start (in one terminal) and yarn test --watch (in another terminal) to get development environment - scripts will watch for changes and reload.

Problem 3: there are sub-dependencies, so we need to build all packages first and only after we can run the start script. That is why we need prestart script.

Problem 4: If there is type error in stories it will show up in the browser, but if there is type error in ab or c packages it will only show up in terminal, which spoils all DX, because instead of switching between editor and browser you will need to switch to terminal as well to check if there is an error or not.

Rant

So I spent quite some time (half of a day?) to figure out all the details and the result is disappointing. Especially I disappointed by problem 2 and problem 4. Even more, I didn't write a line of actual code. It is so frustrating that the JS ecosystem doesn't appreciate convention over configuration principle more. We need more create-react-apps and Parcels in the ecosystem. Tools should be built with cross-integration in mind.

There is probably the solution to the problem, maybe I need to try ava and esm to fix problem 2, but I'm so disappointed that I spent all that time to fight with incidental complexity. Instead, I decided to pause and write the post.

===============================================================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More


☞ Understanding TypeScript

☞ Typescript Masterclass & FREE E-Book

☞ React - The Complete Guide (incl Hooks, React Router, Redux)

☞ Modern React with Redux [2019 Update]

☞ The Complete React Developer Course (w/ Hooks and Redux)

☞ React JS Web Development - The Essentials Bootcamp

☞ React JS, Angular & Vue JS - Quickstart & Comparison

☞ The Complete React Js & Redux Course - Build Modern Web Apps

☞ React JS and Redux Bootcamp - Master React Web Development


Which is recommended, TypeScript or ES6 for ReactJS, and why?

Improving language means we can improve our code. Our apps can have fewer bugs. And some of these features enable the runtimes to run code faster. React with Typescript. It is more than just being a replacement of React Prop Types, as Typescript brings several more massive key benefits to the React ecosystem. Now let’s see how we can apply Typescript-based type definitions into our React development project to get most of it.

TypeScript interface signature for the onClick event in ReactJS

The official&nbsp;<a href="https://reactjs.org/tutorial/tutorial.html" target="_blank">reactjs.org</a>&nbsp;website contains an excellent introductory tutorial.

The official reactjs.org website contains an excellent introductory tutorial.

The tutorial snippets are written in JavaScript and I am trying to convert these to TypeScript.

I have managed to get the code working but have a question about using interfaces.

What should the correct "function signature" be for the onClick callback.

Is there a way to replace the 'any' keyword in the IProps_Square interface with an explicit function signature ?

Any help or suggestions would be really appreciated, many thanks Russell

index.html

<!DOCTYPE html>
<html lang="en">
<body>
<div id="reactjs-tutorial"></div>
</body>
</html> 

index.tsx

import * as React from 'react';   
import * as ReactDOM from 'react-dom'; 

interface IProps_Square {
message: string,
onClick: any,
}

class Square extends React.Component < IProps_Square > {
render() {
return (
<button onClick={this.props.onClick}>
{this.props.message}
</button>
);
}
}

class Game extends React.Component {
render() {
return (
<Square
message = { 'click this' }
onClick = { () => alert('hello') }
/>
);
}
}

ReactDOM.render(
<Game />,
document.getElementById('reactjs-tutorial')
);


Learn TypeScript | TypeScript Crash Course | TypeScript Tutorial for Beginners

Learn TypeScript | TypeScript Crash Course | TypeScript Tutorial for Beginners

Learn TypeScript | TypeScript Crash Course | TypeScript Tutorial for Beginners: My goal with this courses is just give your the fundamentals of the language, show you what TypeScript is and how to use it. We as developers don't have time neither can we afford to spend too much time on any tehcnology.

TypeScript is a superset of JavaScript, which means that is language that was created to add features to JavaScript. You might be asking yourself why it was created and why not just add those features directly to JavaScript.

Sometimes language foundatons take time to implement features because the committee has to approve it, test and get feedback before requests are answered. The soultion for that is to create another language that adds functionality to the language we need, and thats where TypesScript comes in.

TypeScript has many advanced features that plain JavaScript doesnt have yet but the good news is that we can start using it now since TypeScript compiles JavaScript ES5 which at this moment is the most compatible version of JavaScript for all browsers.

Most people that want to learn TypeScript its because they need the skills to use with some Frameworks like Angular.

My goal with this courses is just give your the fundamentals of the language, show you what TypeScript is and how to use it. We as developers don't have time neither can we afford to spend too much time on any tehcnology.

Thanks for reading

If you liked this post, please do share/like it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading about JavaScript and TypeScript

The Complete JavaScript Course 2019: Build Real Projects!

Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

JavaScript Bootcamp - Build Real World Applications

The Web Developer Bootcamp

New ES2019 Features Every JavaScript Developer Should Know

Best JavaScript Frameworks, Libraries and Tools to Use in 2019

Using Typescript with modern React (i.e. hooks, context, suspense)

WebSocket + Node.js + Express — Step by step tutorial using Typescript

From Javascript to Typescript to Elm

Angular + Typescript = Powerful Web Apps

React + TypeScript : Why and How

How to use TypeScript with Vue.js