Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated outdated Docs "Getting Started" > "Create a New Monorepo" #6786

Merged
merged 5 commits into from
Dec 15, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 91 additions & 104 deletions docs/pages/repo/docs/getting-started/create-new.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ You might have noticed something in the terminal. `create-turbo` gave you a desc
- apps/web: Next.js with TypeScript
- apps/docs: Next.js with TypeScript
- packages/ui: Shared React component library
- packages/eslint-config-custom: Shared configuration (ESLint)
- packages/tsconfig: Shared TypeScript `tsconfig.json`
- packages/eslint-config: Shared configuration (ESLint)
- packages/typescript-config: Shared TypeScript `tsconfig.json`
```

Each of these is a _workspace_ - a folder containing a `package.json`. Each workspace can declare its own dependencies, run its own scripts, and export code for other workspaces to use.
Expand All @@ -111,18 +111,18 @@ Open the root folder - `./my-turborepo` - in your favourite code editor.

#### Understanding `packages/ui`

First, open `./packages/ui/package.json`. You'll notice that the package's name is `"name": "ui"` - right at the top of the file.
First, open `./packages/ui/package.json`. You'll notice that the package's name is `"name": "@repo/ui"` - right at the top of the file.

Next, open `./apps/web/package.json`. You'll notice that this package's name is `"name": "web"`. But also - take a look in its dependencies.

You'll see that `"web"` depends on a package called `"ui"`.
You'll see that `"web"` depends on a package called `"@repo/ui"`.

<Tabs items={['npm', 'yarn', 'pnpm']} storageKey="selected-pkg-manager">
<Tab>
```json filename="apps/web/package.json"
{
"dependencies": {
"ui": "*"
"@repo/ui": "*"
}
}
```
Expand All @@ -131,7 +131,7 @@ You'll see that `"web"` depends on a package called `"ui"`.
```json filename="apps/web/package.json"
{
"dependencies": {
"ui": "*"
"@repo/ui": "*"
}
}
```
Expand All @@ -140,70 +140,84 @@ You'll see that `"web"` depends on a package called `"ui"`.
```json filename="apps/web/package.json"
{
"dependencies": {
"ui": "workspace:*"
"@repo/ui": "workspace:*"
}
}
```
</Tab>
</Tabs>

This means that our **web app depends on our local `ui` package**.
This means that our **web app depends on our local `@repo/ui` package**.

If you look inside `apps/docs/package.json`, you'll see the same thing. Both `web` and `docs` depend on `ui` - a shared component library.
If you look inside `apps/docs/package.json`, you'll see the same thing. Both `web` and `docs` depend on `@repo/ui` - a shared component library.

This pattern of sharing code across applications is extremely common in monorepos - and means that multiple apps can share a single design system.

#### Understanding imports and exports

Take a look inside `./apps/docs/app/page.tsx`. Both `docs` and `web` are [Next.js](https://nextjs.org/) applications, and they both use the `ui` library in a similar way:
Take a look inside `./apps/docs/app/page.tsx`. Both `docs` and `web` are [Next.js](https://nextjs.org/) applications, and they both use the `@repo/ui` library in a similar way:

```tsx filename="apps/docs/app/page.tsx"
import { Button, Header } from "ui";
// ^^^^^^^^^^^^^^ ^^
import { Button } from "@repo/ui/button";
// ^^^^^^ ^^^^^^^^^^^^^^^

export default function Page() {
return (
<>
<Header text="Docs">
<Button />
<Button appName="web" className={styles.button}>
Click me!
</Button>
<>
);
}
```

They're importing `Button` directly from a dependency called `ui`! How does that work? Where is `Button` coming from?
They're importing `Button` directly from a dependency called `@repo/ui/button`! How does that work? Where is `Button` coming from?

Open `packages/ui/package.json`. You'll notice these two attributes:
Open `packages/ui/package.json`. You'll notice the `exports` field:

```json filename="packages/ui/package.json"
{
"main": "./index.tsx",
"types": "./index.tsx"
"exports": {
"./button": "./src/button.tsx",
"./card": "./src/card.tsx",
"./code": "./src/code.tsx"
},
}
```

When workspaces import from `ui`, `main` tells them where to access the code they're importing. `types` tells them where the TypeScript types are located.
When workspaces import from `@repo/ui/button`, `exports` tells them where to access the code they're importing.

So, let's look inside `packages/ui/index.tsx`:
So, let's look inside `packages/ui/src/button.tsx`:

```tsx filename="packages/ui/index.tsx"
import * as React from "react";
export * from "./Button";
```

Everything inside this file will be able to be used by workspaces that depend on `ui`.
```tsx filename="packages/ui/src/button.tsx"
"use client";

`index.tsx` is exporting everything from a file called `./Button`, so let's go there:
import { ReactNode } from "react";

```tsx filename="packages/ui/Button.tsx"
import * as React from "react";
interface ButtonProps {
children: ReactNode;
className?: string;
appName: string;
}

export const Button = () => {
return <button onClick={() => alert("boop")}>Boop</button>;
export const Button = ({ children, className, appName }: ButtonProps) => {
return (
<button
className={className}
onClick={() => alert(`Hello from your ${appName} app!`)}
>
{children}
</button>
);
};
```

We've found our button! Any changes we make in this file will be shared across `web` and `docs`. Pretty cool!
We've found our button!

Everything inside this file will be able to be used by workspaces that depend on `@repo/ui/button`.

Any changes we make in this file will be shared across `web` and `docs`. Pretty cool!

<Callout type="idea">

Expand All @@ -215,100 +229,68 @@ This can then be imported by `web` and `docs`.

#### Understanding `tsconfig`

We have two more workspaces to look at, `tsconfig` and `eslint-config-custom`. Each of these allow for shared configuration across the monorepo. Let's look in `tsconfig`:
We have two more workspaces to look at, `typescript-config` and `eslint-config`. Each of these allow for shared configuration across the monorepo. Let's look in `typescript-config`:

```json filename="packages/tsconfig/package.json"
```json filename="packages/typescript-config/package.json"
{
"name": "tsconfig",
"files": ["base.json", "nextjs.json", "react-library.json"]
"name": "@repo/typescript-config",
}
```

Here, we specify three files to be exported, inside `files`. Packages which depend on `tsconfig` can then import them directly.

For instance, `packages/ui` depends on `tsconfig`:

<Tabs items={['npm', 'yarn', 'pnpm']} storageKey="selected-pkg-manager">
<Tab>
```json filename="packages/ui/package.json"
{
"devDependencies": {
"tsconfig": "*"
}
}
```
</Tab>
<Tab>
```json filename="packages/ui/package.json"
{
"devDependencies": {
"tsconfig": "*"
}
}
```
</Tab>
<Tab>
```json filename="packages/ui/package.json"
{
"devDependencies": {
"tsconfig": "workspace:*"
}
}
```
</Tab>
</Tabs>
Here we see the name of the package is `@repo/typescript-config`.

And inside its `tsconfig.json` file, it imports it using `extends`:
New, let's take a look in the `tsconfig.json` file located in our `web` app.

```json filename="packages/ui/tsconfig.json"
```json filename="apps/web/tsconfig.json"
{
"extends": "tsconfig/react-library.json"
"extends": "@repo/typescript-config/nextjs.json",
}
```

As you can see, we're importing `@repo/typescript-config/nextjs.json` directly into our `tsconfig.json` file.

This pattern allows for a monorepo to share a single `tsconfig.json` across all its workspaces, reducing code duplication.

#### Understanding `eslint-config-custom`
#### Understanding `eslint-config`

Our final workspace is `eslint-config-custom`.
Our final workspace is `eslint-config`.

You'll notice that this is named slightly differently to the other workspaces. It's not as concise as `ui` or `tsconfig`. Let's take a look inside `.eslintrc.js` in the root of the monorepo to figure out why.
Let's start with taking a look inside `packages/eslint-config/package.json`:

```ts filename=".eslintrc.js"
module.exports = {
// This tells ESLint to load the config from the package `eslint-config-custom`
extends: ["custom"],
};
```json filename="packages/eslint-config/package.json"
{
"name": "@repo/eslint-config",
"files": [
"library.js",
"next.js",
"react-internal.js"
],
}
```

[ESLint](https://eslint.org/) resolves configuration files by looking for workspaces with the name `eslint-config-*`. This lets us write `extends: ['custom']` and have ESLint find our local workspace.

But why is this in the root of the monorepo?
As you can see, the package is named `@repo/eslint-config`, and it exposes three files: `library.js`, `next.js` and `react-internal.js`.

The way ESLint finds its configuration file is by looking at the closest `.eslintrc.js`. If it can't find one in the current directory, it'll look in the directory above until it finds one.
To understand how we can use custom ESLint configs, let's take a look inside `apps/docs/.eslintrc.js`:

So that means that if we're working on code inside `packages/ui` (which doesn't have a `.eslintrc.js`) it'll refer to the _root_ instead.

Apps that _do_ have an `.eslintrc.js` can refer to `custom` in the same way. For instance, in `docs`:

```ts filename="apps/docs/.eslintrc.js"
```js filename="apps/docs/.eslintrc.js"
module.exports = {
root: true,
extends: ["custom"],
extends: ["@repo/eslint-config/next.js"],
};
```

Just like `tsconfig`, `eslint-config-custom` lets us share ESLint configs across our entire monorepo, keeping things consistent no matter what project you're working on.
Here you can see that we're importing `@repo/eslint-config/next.js` directly into our `.eslintrc.js` file.

Just like `typescript-config`, `eslint-config` lets us share ESLint configs across our entire monorepo, keeping things consistent no matter what project you're working on.

#### Summary

It's important to understand the dependencies between these workspaces. Let's map them out:

- `web` - depends on `ui`, `tsconfig` and `eslint-config-custom`
- `docs` - depends on `ui`, `tsconfig` and `eslint-config-custom`
- `ui` - depends on `tsconfig` and `eslint-config-custom`
- `tsconfig` - no dependencies
- `eslint-config-custom` - no dependencies
- `web` - depends on `ui`, `typescript-config` and `eslint-config`
- `docs` - depends on `ui`, `typescript-config` and `eslint-config`
- `ui` - depends on `typescript-config` and `eslint-config`
- `typescript-config` - no dependencies
- `eslint-config` - no dependencies

Note that **the Turborepo CLI is not responsible for managing these dependencies**. All of the things above are handled by the package manager you chose (`npm`, `pnpm` or `yarn`).

Expand All @@ -327,7 +309,9 @@ Let's take a look inside `turbo.json`, at the root:
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
},
"lint": {},
"lint": {
"dependsOn": ["^lint"]
},
"dev": {
"cache": false,
"persistent": true
Expand Down Expand Up @@ -368,7 +352,7 @@ turbo lint

You'll notice several things happen in the terminal.

1. Several scripts will be run at the same time, each prefixed with either `docs:lint`, `ui:lint` or `web:lint`.
1. Several scripts will be run at the same time, each prefixed with either `docs:lint`, `@repo/ui:lint` or `web:lint`.
2. They'll each succeed, and you'll see `3 successful` in the terminal.
3. You'll also see `0 cached, 3 total`. We'll cover what this means later.

Expand Down Expand Up @@ -404,7 +388,7 @@ When we run `turbo lint`, Turborepo looks at each `lint` script in each workspac

Let's run our `lint` script one more time. You'll notice a few new things appear in the terminal:

1. `cache hit, replaying logs` appears for `docs:lint`, `web:lint` and `ui:lint`.
1. `cache hit, replaying logs` appears for `docs:lint`, `web:lint` and `@repo/ui:lint`.
2. You'll see `3 cached, 3 total`.
3. The total runtime should be under `100ms`, and `>>> FULL TURBO` appears.

Expand All @@ -414,15 +398,18 @@ It had saved the logs from the previous run, so it just replayed them.

Let's try changing some code to see what happens. Make a change to a file inside `apps/docs`:


```diff filename="apps/docs/app/page.tsx"
import { Button, Header } from "ui";
import { Button } from "@repo/ui/button";
// ^^^^^^ ^^^^^^^^^^^^^^^

export default function Page() {
return (
<>
- <Header text="Docs" />
+ <Header text="My great docs" />
<Button />
<Button appName="web" className={styles.button}>
- Click me!
+ Click me now!
</Button>
<>
);
}
Expand Down
Loading