An open API service indexing awesome lists of open source software.

https://github.com/saibotsivad/symlink-monorepo

CLI tool to setup a monorepo using symlinks.
https://github.com/saibotsivad/symlink-monorepo

monorepo symlink

Last synced: about 2 months ago
JSON representation

CLI tool to setup a monorepo using symlinks.

Awesome Lists containing this project

README

          

# symlink-monorepo

Single command to setup a monorepo using symlinks.

## install

The usual way:

```shell
npm install symlink-monorepo
```

## use

It's a CLI tool (see below for details) so use it like:

```shell
symlink-monorepo --root=example-monorepo --folderPrefix="_" --npmPrefix="@"
```

* `root: String` *[optional]* - the monorepo root, typically leave this blank.
* `folderPrefix: String` *[default: "_"]* - the folder name prefix to use for symlinking
* `npmPrefix: String` *[default: "$"]* - the module scope, e.g. `_shared` maps to `import('$/shared')`

With that primer, let me tell you why this exists.

## the problem

Many monorepo projects are set up like this:

```
root
├┬ shared
│└─ util.js
└┬ apps
├┬ app1
│└─ server.js
└┬ app2
└─ website.js
```

If you want to import `shared/util.js` from inside `apps/app1/server.js` you could use
relative paths:

```js
// apps/app1/server.js
import util from '../../shared/util.js'
```

But that definitely gets unwieldy pretty fast.

## partial solution

With the power of the NodeJS module resolution algorithm, you can embed a `node_modules` folder
within your project to use absolute paths:

```
root
└┬ apps
└┬ node_modules
├┬ shared
│└ util.js
├┬ app1
│└─ server.js
└┬ app2
└─ website.js
```

Then, to import `apps/node_modules/shared/util.js` from inside `apps/node_modules/app1/server.js` you
can use non-relative paths:

```js
// apps/node_modules/app1/server.js
import util from 'shared/util.js'
```

The good news: this is fully supported with native NodeJS, so no bundler required.

The bad news: it feels a little clumsy, and some bundlers and tooling get really hung
up on the sub-folder named `node_modules`–they often filter by *anything* that has
`node_modules` in the path.

## better solution

Using symlinks, you can use symlinks to point to the appropriate folders, which looks
like this:

```
root
├┬ shared
│└─ util.js
└┬ apps
├┬ app1
│├┬ node_modules
││└┬ prefix
││ └ shared ⇒ /shared
│└─ server.js
└┬ app2
├┬ node_modules
│└┬ prefix
│ └ shared ⇒ /shared
└─ website.js
```

So now we have the flat file paths again, without the embedded `node_modules`, and to import
`shared/util.js` from inside `apps/app1/server.js` we can also use non-relative paths:

```js
// apps/app1/server.js
import util from 'prefix/shared/util.js'
```

## this tool

This is a CLI tool that assumes the following folder structure (more about prefixes later):

```
root
├┬ _shared1
│└─ # files and/or folders
├┬ _shared2
│└─ # files and/or folders
└┬ apps_folder
└┬ app1
├┬ _lib1
│└ # files and/or folders
├┬ _lib2
│└ # files and/or folders
├┬ node_modules # generated by this CLI tool
│└┬ @ # the prefix
│ ├ shared1 ⇒ /_shared1
│ ├ shared2 ⇒ /_shared2
│ ├ lib1 ⇒ /apps_folder/app1/_lib1
│ └ lib2 ⇒ /apps_folder/app1/_lib2
└─ # files and/or folders
```

The `apps_folder/*/node_modules` folders in this case would be generated using:

```shell
symlink-monorepo --folderPrefix="_" --npmPrefix="@"
```

Now let's break down what this is all about:

### folder prefix

Folders that are at the repo root level, and at each app level, that are prefixed
with the `folderPrefix` property, are symlinked.

Most of the projects that I work with use the underscore (aka `_`) character, like the
earlier example, eg. `/_shared1` and `/apps_folder/app1/_lib1`.

Although it's common to have a single folder like `_shared` it is also common to have
multiple folders, e.g. one for services, one for controllers, etc.

### npm prefix

Each of these folders is symlinked to a single "scope" name, e.g. the [@angular/cli](https://www.npmjs.com/package/@angular/cli)
scope name is `@angular`, so if you set `--npmPrefix="@"` the import name would be e.g.
`@/shared` or `@/lib` etc.

Specifically, the `folderPrefix` gets stripped from the folder name as part of the symlink.

So if you set the `folderPrefix` to `_` and the `npmPrefix` to `$` than if you had a
file at `_controller/util.js` you would import it with `@/controller/util.js`. Or if you
had a file at `apps/app1/_lib/util.js` you would import with `@/lib/util.js`.

**Note:** because of this naming convention, you cannot have an app symlinked folder,
e.g. `apps/app1/_shared` that has the same name (in this case `_shared`) as a root folder.
To do so would not be possible, so it'll throw an error.