Giter Club home page Giter Club logo

lerna-yarn-workspaces-example's Introduction

How to build TypeScript mono-repo project

CircleCI

Tools

  • yarn: NPM client.
  • Lerna: Multiple packages management tool.
  • TypeScript: ^3.0.0 supports project references.

Directory Structure

Put each package under the packages directory.

.
├── README.md
├── lerna.json
├── package.json
├── packages
│   ├── x-cli
│   │   ├── lib
│   │   │   ├── main.d.ts
│   │   │   ├── main.js
│   │   │   └── main.js.map
│   │   ├── package.json
│   │   ├── src
│   │   │   └── main.ts
│   │   └── tsconfig.json
│   └── x-core
│       ├── lib
│       │   ├── index.d.ts
│       │   ├── index.js
│       │   └── index.js.map
│       ├── package.json
│       ├── src
│       │   └── index.ts
│       └── tsconfig.json
├── tsconfig.json
└── yarn.lock

Workspaces

Using yarn workspace feature, configure the following files:

  • /package.json

Append the workspaces key.

{
  "private": true,
  "workspaces": [
    "packages/*"
  ]
}
  • lerna.json

Set npmClient "yarn" and turn useWorkspaces on.

{
  "lerna": "2.2.0",
  "packages": [
    "packages/*"
  ],
  "npmClient": "yarn",
  "useWorkspaces": true,
  "version": "1.0.0"
}

Exec yarn install(or lerna bootstrap). After successful running, all dependency packages are downloaded under the repository root node_modules directory.

Dependencies between packages

In this example, the x-cli package depends on another package, x-core. So to execute (or test) x-cli, x-core packages should be installed. But in development the x-core package is not published so you can't install it.

yarn solves this problem. This command creates sim-links of each package into the top-level node_modules dir.

Resolve Dependencies as TypeScript Modules

As mentioned above, Lerna resolves dependencies between packages. It's enough for "runtime". However considering TypeScript sources, in other words "static", it's not.

For example, the following code depends a module x-core located at other package.

/* packages/x-cli/src/main.ts */
import { awesomeFn } from "@quramy/x-core";

export function cli() {
  awesomeFn();
  return Promise.resolve(true);
}

If you compile this code, TypeScript compiler emits a "Cannot find module" error until building x-core package and creating x-core/index.d.ts. And it's silly to compile dependent packages(e.g. x-core) in the same repository after each editing them.

TypeScript's path mapping is the best solution. Path mappings are declared such as:

/* tsconfig.json */
{
  "compilerOptions": {
    /* other options */
    "baseUrl": "./packages",
    "paths": {
      "@quramy/*": ["./*/src"]
    }
  }
}

The above setting means import { awesomeFn } from "@quramy/x-core" is mapped to import { awesomeFn } from "../../x-core/src"(it's relative from "packages/x-cli/src/main.ts"). In other words, path mapping allows to treat developing packages' sources as published(compiled) modules.

References between packages

TypeScript 3.0 supports Project reference feature. You can tell tsc that x-cli depends on x-core project using this.

/* packages/x-cli/tsconfig.json */
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "lib"
  },
  "references": [
    { "path": "../x-core" }
  ]
}

In the above json, the key references means the dependency.

Compile all packages

Our repository has multiple tsconfig.json files. We can compile all packages with "-b" option:

$ tsc -b packages/x-core packages/x-cli

License

The MIT License (MIT)

Copyright 2017 Quramy

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

lerna-yarn-workspaces-example's People

Contributors

quramy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lerna-yarn-workspaces-example's Issues

Nested Module Resolution?

I'm trying to figure out how to get nested module resolution working with this project.

What I mean is modifying the structure to a more real-world example with multiple nested modules, in a domain driven architecture where all our exports are in a domain and referenced by e.g. import { sleep } from '@quramy/x-core/messages/core'

Structure:

- x-cli
-- src
--- main.ts
- x-core
-- src
--- messages
---- core
----- sleep
------ sleep.ts
------ sleep-and-snore.ts
------ index.ts
----- wake
------ wake.ts
------ wake-and-eat.ts
------ index.ts (exports wake and wake-and-eat)

I have modified /tsconfig.json: and then updated main.ts:

{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "baseUrl": "./packages",
    "paths": {
      "@quramy/x-core/messages/*": ["./x-core/src/messages/*"],
      "@quramy/*": ["./*/src"]
    }
  }
}
// main.ts (this works fine in the IDE, just doesn't build...)
import { sleep, sleepAndSnore } from '@quramy/x-core/messages/core'

export function cli() {
  sleep();
  sleepAndSnore()
  return Promise.resolve(true);
}
cli()

yarn prepare then gives me this error:
src/main.ts(1,38): error TS2307: Cannot find module '@quramy/x-core/messages/core'.

If I add the paths/baseUrl to the tsconfig.build.json, it will build, but it doesn't run because it can't find the module.

I also get the "source files not under the same rootDir message if I have that compilerOption set under this case: TS6059: File '/home/bthompson/source/lerna-yarn-workspaces-example/packages/x-core/src/messages/core/index.ts' is not under 'rootDir' '/home/bthompson/source/lerna-yarn-workspaces-example/packages/x-cli/src'. 'rootDir' is expected to contain all source files.

Any help would be greatly appreciated!

Build & watch all packages?

Hi @Quramy! 👋

I'm curious how to tsc --watch all packages in parallel with one command? This can be useful if, say, my packages/x-cli needs to be linked and tested externally before everything is released. I'd like to be able to change packages/x-core and see the difference propagated to x-cli when I use it. Of course, there can be more than two packages in a monorepo, which makes it harder to run tsc -w in all subfolders one by one.

vscode error Cannot find module '@quramy/x-core'.ts(2307)

I clone the repo and yarn install,then open x-cli/src/main.ts, vscode error Cannot find module '@quramy/x-core'.ts(2307)

image

Version: 1.40.0-insider
Commit: 86405ea23e3937316009fc27c9361deee66ffbf5
Date: 2019-11-06T16:56:25.487Z
Electron: 6.1.2
Chrome: 76.0.3809.146
Node.js: 12.4.0
V8: 7.6.303.31-electron.0
OS: Darwin x64 18.7.0

why do build json files not include baseUrl?

Your build json file looks like:

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "lib"
  }
}

Which means that a baseUrl is not defined. So how are relative package imports like import * as foo from 'x-cli' going to be resolved?

Any reason you did define it for the "editor" config?

master branch README inconsistency

TypeScript's path mapping is the best solution. Path mappings are declared such as:

The section about path mapping should be either removed from master branch readme or annotated as a deprecated solution for ts 2.x - its not even used anywhere in the repository files, creating confusion for novice users of ts/lerna.

How to avoid yarn workspace full reinstall per build?

I see that every time a build goes to circleci it has to rebuild the whole workspace. Takes about a 1.5 minutes for me.

That is despite saving the node_modules to cache.
And not just the root node_modules. But globbing all the package node_modules too.

circleci

Is path mapping for "Resolve Dependencies as TypeScript Modules" really the best solution?

[...]

TypeScript's path mapping is the best solution.

How so? If you have a more elaborated (TM) file layout such as:

  • components/services/* having modules like @scope/service1
  • shared/runtime/*... having modules like `@scope/runtime1
  • models/* ... having modules like @scope/model1
    You would need to add them (path mappings) explicitly, because a generic match like the one you propose only works, if all modules are having the same folder depth/layout.

This hinders scalability and it in general a path mapping does not allow to refactor the file layout, without modifying the path mapping.

I think, this is the wrong approach; lerna bootstrap is explicitly abstracting the file layout of your modules away by sym-linking them correctly. Thus enabling "preserveSymlinks": true in each individual module's tsconfig.json is to me the more flexible (and hence better) solution.

However I like your point of view, why you settled with the path mapping option. Care to elaborate?

About using create-react-native-app in lerna using workspaces

Good morning my friends

If you can help

I'm having trouble using create-react-native-app on lerna using workspaces

Below I do the step by step, but we see that this way the node_modules folder in the package is not stored

It is necessary to have the node_modules folder in the folder in packages, because you have an android folder with requests to that folder

Environment info:

$ lerna info

System:
    OS: Linux 5.4 Ubuntu 20.04 LTS (Focal Fossa)
    CPU: (4) x64 Intel(R) Core(TM) i7-5500U CPU @ 2.40GHz
Binaries:
    lerna: v3.22.1
    Node: 12.18.1 - ~/.nvm/versions/node/v12.18.1/bin/node
    Yarn: 1.22.4 - /usr/bin/yarn
    npm: 6.14.5 - ~/.nvm/versions/node/v12.18.1/bin/npm
Utilities:
    Git: 2.25.1 - /usr/bin/git 

$ react-native info

info Fetching system and libraries information...
System:
    OS: Linux 5.4 Ubuntu 20.04 LTS (Focal Fossa)
    CPU: (4) x64 Intel(R) Core(TM) i7-5500U CPU @ 2.40GHz
    Memory: 1.07 GB / 15.56 GB
    Shell: 5.0.16 - /bin/bash
Binaries:
    Node: 12.18.1 - ~/.nvm/versions/node/v12.18.1/bin/node
    Yarn: 1.22.4 - /usr/bin/yarn
    npm: 6.14.5 - ~/.nvm/versions/node/v12.18.1/bin/npm
    Watchman: 4.9.0 - /home/linuxbrew/.linuxbrew/bin/watchman
SDKs:
    Android SDK:
    API Levels: 28, 30
    Build Tools: 28.0.3, 30.0.0
    System Images: android-30 | Google APIs Intel x86 Atom, android-30 | Google Play Intel x86 Atom
    Android NDK: Not Found
IDEs:
    Android Studio: Not Found
Languages:
    Java: 11.0.7 - /usr/bin/javac
    Python: 2.7.18 - /usr/bin/python
npmPackages:
    @react-native-community/cli: Not Found
    react: ~16.11.0 => 16.11.0 
    react-native: ~0.62.2 => 0.62.2 
npmGlobalPackages:
    *react-native*: Not Found

$ python --version

Python 2.7.18rc1

$ python3 --version

Python 3.8.2

$ npm list -g --depth 0

/home/marcio/.nvm/versions/node/v12.18.1/lib
├── @prettier/[email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
└── [email protected]

Steṕs to reproduce

#

cd ~/Downloads

rm -fr ~/Downloads/monorepo-3

mkdir -p ~/Downloads/monorepo-3

cd ~/Downloads/monorepo-3

pwd && ls

echo '{
"version": "independent",
"npmClient": "yarn",
"useWorkspaces": true,
"packages": [
"packages/*"
]
}' >lerna.json

cat lerna.json

echo '{
"name": "root",
"private": true,
"workspaces": [
"packages/*"
],
"devDependencies": {
"lerna": "^3.22.1"
}
}' >package.json

cat package.json

lerna init

#

cd packages

npx create-react-native-app default-create-react-native-app --yes

# OR

# react-native init defaultreactnative

# clean

cd ..

yarn check --integrity --verify-tree || true

lerna list --all --long || true

lerna clean --yes || true

pwd && ls

rm -fr node_modules

find . -name "node_modules" -type d

find \
. \
-name "node_modules" \
-o -name ".yarncache" \
-type d -prune | while read ITEM; do
(du -sh "$ITEM")
(rm -fr "$ITEM")
done

find \
. \
-name "*.touch" \
-o -name "*error.log" \
-o -name "*debug.log" \
-o -name "*lock.json" \
-o -name "*lock.yaml" \
-o -name "audit-resolve.json" \
-o -name ".yarnclean" \
-o -name "yarn.lock" \
-type f | while read ITEM; do
(du -sh "$ITEM")
(rm -fr "$ITEM")
done

#

lerna init

lerna info

lerna list --all --long

lerna bootstrap

#

ls && ls ./packages/*

du -sh ./packages/default-create-react-native-app/node_modules/react-native-unimodules/gradle.groovy

du -sh ./packages/default-create-react-native-app/node_modules/react-native/react.gradle

du -sh ./packages/default-create-react-native-app/node_modules/expo-updates/scripts/create-manifest-android.gradle

du -sh ./packages/default-create-react-native-app/node_modules/@react-native-community/cli-platform-android/native_modules.gradle

# 

Where can you specify typeRoots?

Hey, thanks for making this example project. It's been really helpful.

There's a package I'm trying to work with (execa), but some of the typings are incorrect. I'd like to override the @types/execa typings with my own types in a /package/foo/types folder, at least temporarily. From what I understand, this should be possible through setting the typeRoots property in tsconfig like this:

"typeRoots": [
  "./node_modules/@types",
  "./types/"
]

But I'm not sure which tsconfig file this should be put in. I think I've tried all of them (and restarted vscode in between) but the types in my types folder aren't being picked up. Where can I place the typeRoots property so it can be picked up by vscode and tsc when building?

Why do you have two tsconfig setting?

After I look into your code, I found that in each package you have 2 tsconfig. First is tsconfig.json and the other is tsconfig.base.json

When you run npm script prepublish it will compile using tsconfig.base.json configuration.
"prepublish": "tsc -p tsconfig.build.json"
So, Do we still need the tsconfig.json since you don't use it?

VSCode auto complete not finding symbols

If you comment out the import

import { awesomeFn } from "@quramy/x-core";

from x-cli/src/main.ts, and try to auto import it again by typing awesomeFn somewhere in that file, VSCode doesn't find that symbol nor suggest it for auto import. Why is this?

discrepancy between readme and code

Hi, have been looking at this code, seems to work, though there are significant differences between the readme and the codebase.

(1) the readme describes use of tsconfig @paths, yet none of the tsconfig files use this?

(2) instead the packages seem to be mapped as package.json devDependencies, ie; nothing to do with tsconfig ?

(3) the lone tsconfig.json file is not referenced by any other config files, instead they all reference the tsconfig.settings.json file.

So, it seems that the readme describes use of tsconfig references and paths, yet the code (currently) actually uses package.json devDependency references.

What am I missing here?
Given that your solution actually works, what are you thoughts on which way to go?

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.