Coder Social home page Coder Social logo

typescript-monorepo-lib's Introduction

Typescript monorepo for Libraries

What I want to achieve?

  • Monorepo project, to be able to comfortably to develop several packages, which can be used separately but as well together
  • Typescript
  • Testing library. I want to start with Jest, but as well we can choose something else
  • (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

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

Tools

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": {
      "@my-lib/*": ["packages/*/src"]
    }
  }
}

Create packages/d/, 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": "../d" }]
}

In package.json for packages b and c add:

"peerDependencies": {
  "@my-lib/d": "0.0.1"
},
"devDependencies": {
  "@my-lib/d": "*"
}

We need peerDependencies to make sure that when packages (d, b, c) installed by the end user they will use the same instance of package d, 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 --scope=@my-lib/{d,b,c}"
}

and to package.json for d, b, c

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

Problem 1: because of sub-dependencies (packages b and c depend on d, stories depends on d, b, c) we need to build packages accordingly, e.g. first d, 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-dom, react, react-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-renderer, jest, react-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": {
  "@my-lib/(.*)$": "<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": {
  "@my-lib/d": "*",
  "@my-lib/b": "*",
  "@my-lib/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 c package.

Storybook will watch for changes in stories/src, but not for changes in d/src, b/src, c/src. We will need to use TypeScript to watch for changes in other packages.

Add to package.json of d, b 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 d, b 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.

typescript-monorepo-lib's People

Contributors

stereobooster avatar kristianmandrup avatar scalder27 avatar a1300 avatar

Watchers

 avatar James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.