├── .gitignore
├── README.md
├── apps
├── react
│ ├── .eslintrc.cjs
│ ├── esbuild.js
│ ├── nodemon.json
│ ├── package.json
│ ├── postcss.config.cjs
│ ├── src
│ │ ├── App.tsx
│ │ ├── css
│ │ │ ├── main.css
│ │ │ └── tailwind.css
│ │ ├── index.html
│ │ └── index.tsx
│ ├── tailwind.config.cjs
│ └── tsconfig.json
├── server
│ ├── .eslintrc.cjs
│ ├── esbuild.js
│ ├── nodemon.json
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ └── tsconfig.json
└── web
│ ├── .eslintrc.cjs
│ ├── esbuild.js
│ ├── nodemon.json
│ ├── package.json
│ ├── src
│ ├── index.html
│ └── index.ts
│ └── tsconfig.json
├── nodemon.json
├── package-lock.json
├── package.json
├── packages
├── config
│ ├── esbuild
│ │ ├── build-browser.js
│ │ ├── build-node.js
│ │ ├── build-tailwind.js
│ │ ├── build.react.js
│ │ ├── index.js
│ │ └── plugins
│ │ │ └── rm-out-dir.js
│ ├── eslint
│ │ ├── rules-default.cjs
│ │ ├── rules-react.cjs
│ │ └── rules-typescript.cjs
│ ├── package.json
│ ├── typescript
│ │ ├── tsconfig.browser.json
│ │ ├── tsconfig.node.json
│ │ └── tsconfig.react.json
│ └── util
│ │ └── pkg-mismatch.js
└── shared
│ ├── .eslintrc.cjs
│ ├── esbuild.js
│ ├── package.json
│ ├── src
│ ├── main.ts
│ ├── server
│ │ ├── example1.ts
│ │ ├── example2.ts
│ │ └── index.ts
│ └── web
│ │ ├── example1.ts
│ │ ├── example2.ts
│ │ └── index.ts
│ └── tsconfig.json
└── turbo.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node modules
2 | /**/node_modules/
3 |
4 | # Turbo cache files
5 |
6 | # For turborepo caching to work, it's essential that all .turbo
7 | # directories across the monorepo are properly ignored.
8 | /cache/
9 | /**/.turbo/
10 |
11 | # Application dynamically generated files
12 | /apps/**/out/
13 | /packages/**/out/
14 |
15 | # Environmental variables and secrets
16 | /**/process.env
17 |
18 | # Other
19 | /.vscode/
20 | /notes.md
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ESBuild TypeScript Turborepo Monorepo starter/example
2 |
3 | ## Update April 2023:
4 |
5 | - Typescript has been updated to 5.0. This means you now have to export your common tsconfig files from `package.json` in case you want to extend other configurations elsewhere in your repository.
6 |
7 | ```js
8 | // package.json (@repo/config)
9 | {
10 | "name": "@repo/config",
11 | "exports": {
12 | "./typescript/*.json": "./typescript/*.json"
13 | }
14 | }
15 |
16 | // tsconfig.json (@repo/server)
17 | {
18 | "extends": "@repo/config/typescript/tsconfig.node.json",
19 | }
20 | ```
21 |
22 | ❗ If you are using a version of VSCode that ships with TypeScript <5.0.3, you need to change the version of TypeScript VSCode uses to 5.0.3 or higher, which [fixes this bug](https://github.com/microsoft/TypeScript/issues/53314). This monorepo currently comes with 5.0.4 so you can use that version instead.
23 |
24 | The easiest way to take care of this problem is by creating a `.vscode` directory in the project root, and then add the following configuration to `settings.json`:
25 |
26 | ```js
27 | {
28 | "typescript.tsdk": "node_modules/typescript/lib",
29 | }
30 | ```
31 |
32 | or open the command palette (ctrl+shift+p) and choose > "Typescript: Select typescript version" > "Use workspace version (5.0.4)".
33 |
34 | ## Update February 2023:
35 |
36 | - Added TailwindCSS to the React package
37 |
38 | ## Update December 2022:
39 |
40 | - The way of exporting/sharing packages has changed. Check the updated [section](#exportingsharing-packages).
41 |
42 | ## Update November 2022:
43 |
44 | - Added support for React with an example app in `apps/react`.
45 |
46 | ## Details:
47 |
48 | This is an example monorepository using ESBuild for it's near-instantaneous build times and Turborepo for it's caching capabilities. It's pre-configured for TypeScript (2 different configurations for browser and for node) and ESLint for linting.
49 |
50 | Additionally it's using NPM Workspaces, most examples I could find online were using YARN.
51 |
52 | ## Installation:
53 |
54 | ```sh
55 | git clone https://github.com/barca-reddit/typescript-vscode-esbuild.git
56 |
57 | cd typescript-vscode-esbuild
58 |
59 | npm run watch
60 | ```
61 |
62 | ## Tech stack:
63 |
64 | - [ESBuild](esbuild.github.io/)
65 |
66 | - [TypeScript](https://www.typescriptlang.org/)
67 |
68 | - [Turborepo](https://turborepo.org/)
69 |
70 | - [NPM Workspaces](https://docs.npmjs.com/cli/v8/using-npm/workspaces)
71 |
72 | - [Eslint](https://eslint.org/)
73 |
74 | - [Nodemon](https://nodemon.io/)
75 |
76 | ## Exporting/sharing packages:
77 |
78 | **NB**: I don't know if this is the best or the accepted way to do this, neither I consider myself an expert, so PR/issues/feedback of any kind is welcome.
79 |
80 | [Previously](https://github.com/barca-reddit/esbuild-typescript-turborepo/tree/0a22bbc5b0652a940caf5d6d45d60edbbebeeea7#exportingsharing-packages) we were making use of `typeVersions` in `package.json` to share code within the monorepository, but that caused some issues. Now, we're making use of `"moduleResolution": "NodeNext"` in `tsconfig.json`, so that makes things easier.
81 |
82 | To create a shared package and import it somewhere else in your monorepo, edit the contents of `package.json` of the package you want to export and add the following fields:
83 |
84 | ```json
85 | "exports": {
86 | ".": {
87 | "types": "./src/main.ts",
88 | "import": "./out/main.js"
89 | }
90 | }
91 | ```
92 |
93 | The first part of the `export` object is the path you want to import (details below).
94 |
95 | The `types` key should point out to an index file where all your exports live. For example:
96 |
97 | ```ts
98 | // src/main.ts
99 | export const foo = "foo";
100 | export const bar = "foo";
101 | ```
102 |
103 | The `import` key should point out to an index (main) file in your compiled `out` directory and it's there to server plain javascript imports.
104 |
105 | All of this allows you to do the following:
106 |
107 | ```ts
108 | // inside some other package
109 | import { foo, bar } from "@repo/shared";
110 | ```
111 |
112 | Don't forget to add the package you're exporting as a dependency to the package you're importing it to:
113 |
114 | ```json
115 | // package.json
116 | {
117 | // ...
118 | "dependencies": { "@repo/shared": "*" }
119 | }
120 | ```
121 |
122 | You can also have multiple import paths.
123 |
124 | ```json
125 | "exports": {
126 | ".": {
127 | "types": "./src/main.ts",
128 | "import": "./out/main.js"
129 | },
130 | "./server": {
131 | "types": "./src/server/index.ts",
132 | "import": "./out/server/index.js"
133 | },
134 | "./web": {
135 | "types": "./src/web/index.ts",
136 | "import": "./out/web/index.js"
137 | }
138 | }
139 | ```
140 |
141 | ```ts
142 | // inside some other package
143 | import { foo } from "@repo/shared/server";
144 | import { bar } from "@repo/shared/web";
145 | ```
146 |
147 | It is also possible to have wildcard exports like this:
148 |
149 | ```json
150 | "exports": {
151 | "./*": {
152 | "types": "./src/*.ts",
153 | "import": "./out/*.js"
154 | }
155 | }
156 | ```
157 |
158 | But unfortunately TypeScript is unable to find type declarations this way. If you have a solution or tips about this, issues and PRs are welcome!
159 |
160 | ## Notes:
161 |
162 | ### Turborepo
163 |
164 | For Turborepo caching to work, it's essential that all `.cache` directories it creates are git-ignored.
165 |
166 | If build order isn't important for your setup, add the `--parallel` flag to the `npm build` script to speed up compiling. You can probably get away with this if you don't bundle any code via `bundle: true` setting passed to esbuild.
167 |
168 | ### TSC
169 |
170 | The TypeScript compiler is used only for type checking, everything else is handled by ESBuild.
171 |
172 | ### Typescript/Eslint
173 |
174 | TypeScript and ESLint configurations are matter of personal preference and can easily be adjusted to one's requirements. The same applies for ESBuild, [you can also pass additional parameters](packages/config/esbuild/build-browser.mjs#L14) to `buildBrowser` or `buildNode` which will override the default ones.
175 |
176 | ### VSCode
177 |
178 | If the `.cache` directories become annoying, you can just hide them in VSCode, create/edit this file under `.vscode/settings.json`.
179 |
180 | ```json
181 | {
182 | "files.exclude": {
183 | "cache/": true,
184 | "**/.turbo": true
185 | }
186 | }
187 | ```
188 |
189 | ### Version mismatches
190 |
191 | You can quickly check whether your package dependencies are in sync, e.g, `@repo/a` and `@repo/b` are different versions of the same library.
192 |
193 | ```json
194 | // package.json (repo a)
195 | {
196 | "name": "repo/a",
197 | "dependencies": {
198 | "foo": "^1.0.0"
199 | }
200 | }
201 | // package.json (repo b)
202 | {
203 | "name": "repo/b",
204 | "dependencies": {
205 | "foo": "^2.0.0"
206 | }
207 | }
208 | ```
209 |
210 | ```sh
211 | npm run mismatch
212 |
213 | Error: Found version mismatch for the following package:
214 |
215 | foo - versions: ^1.0.0, ^2.0.0
216 | - apps/package-a/package.json (@repo/a) - ^1.0.0
217 | - apps/package-b/package.json (@repo/b) - ^2.0.0
218 | ```
219 |
220 | This is just a quick and dirty solution that will only report mismatches but won't fix them for you. For more advanced solutions, check out [syncpack](https://github.com/JamieMason/syncpack).
221 |
222 | ## Useful resources:
223 |
224 | - [Video: Turborepo Tutorial | Part 1 - Typescript, Eslint, Tailwind, Husky shared config setup in a Monorepo](https://www.youtube.com/watch?v=YQLw5kJ1yrQ) by Leo Roese.
225 |
226 | - [typescript-subpath-exports-workaround](https://github.com/teppeis/typescript-subpath-exports-workaround) by Teppei Sato.
227 |
--------------------------------------------------------------------------------
/apps/react/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | es2021: true,
5 | node: true,
6 | },
7 | extends: [
8 | "plugin:react/recommended",
9 | "plugin:react-hooks/recommended"
10 | ],
11 | plugins: [
12 | "react",
13 | "react-hooks",
14 | "@typescript-eslint",
15 | ],
16 | parser: "@typescript-eslint/parser",
17 | parserOptions: {
18 | ecmaVersion: "latest",
19 | ecmaFeatures: {
20 | jsx: true
21 | },
22 | sourceType: "module",
23 | jsxPragma: null,
24 | tsconfigRootDir: __dirname,
25 | project: ["./tsconfig.json"],
26 | },
27 | settings: {
28 | /* https://github.com/jsx-eslint/eslint-plugin-react/blob/master/README.md */
29 | react: {
30 | createClass: "createReactClass",
31 | pragma: "React",
32 | fragment: "Fragment",
33 | version: "detect",
34 | flowVersion: "0.53"
35 | }
36 | },
37 | ignorePatterns: ["*.*", "!src/**/*"],
38 | rules: {
39 | ...require("@repo/config/eslint/rules-default.cjs"),
40 | ...require("@repo/config/eslint/rules-typescript.cjs"),
41 | ...require("@repo/config/eslint/rules-react.cjs"),
42 | }
43 | };
--------------------------------------------------------------------------------
/apps/react/esbuild.js:
--------------------------------------------------------------------------------
1 | import { copyFile } from 'fs/promises';
2 | import { constants } from 'fs';
3 | import { buildReact, buildTailwind } from '@repo/config/esbuild';
4 |
5 | const config = {
6 | development: {
7 | define: { 'process.env.NODE_ENV': '"development"' },
8 | minify: false,
9 | target: 'esnext'
10 | },
11 | production: {
12 | define: { 'process.env.NODE_ENV': '"production"' },
13 | minify: true,
14 | target: 'es2020'
15 | }
16 | }
17 |
18 | await Promise.all([
19 | await buildReact({
20 | ...config.development,
21 | outfile: './out/index.js'
22 | }),
23 | await buildTailwind({}),
24 | await copyFile(
25 | './src/index.html',
26 | './out/index.html',
27 | constants.COPYFILE_FICLONE
28 | )
29 | ])
--------------------------------------------------------------------------------
/apps/react/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["/out/**/*.js"],
3 | "ext": "js",
4 | "verbose": false
5 | }
--------------------------------------------------------------------------------
/apps/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/react",
3 | "author": "decho",
4 | "version": "1.0.0",
5 | "description": "An example react application (browser)",
6 | "license": "ISC",
7 | "type": "module",
8 | "scripts": {
9 | "build": "node esbuild.js",
10 | "check": "npx tsc --watch",
11 | "lint": "npx eslint src/**/*.ts"
12 | },
13 | "dependencies": {
14 | "@repo/shared": "*",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0"
17 | },
18 | "devDependencies": {
19 | "@repo/config": "*",
20 | "@types/react": "^18.2.8",
21 | "@types/react-dom": "^18.2.4",
22 | "eslint-plugin-react": "^7.32.2",
23 | "eslint-plugin-react-hooks": "^4.6.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/apps/react/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | const tailwindcss = require('tailwindcss');
2 | const postCSSImport = require('postcss-import');
3 |
4 | module.exports = {
5 | plugins: [
6 | postCSSImport(),
7 | tailwindcss('./tailwind.config.cjs'),
8 | ]
9 | };
--------------------------------------------------------------------------------
/apps/react/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react';
2 |
3 | export const App = () => {
4 | return (
5 |
6 | Hello World
7 |
8 | );
9 | };
--------------------------------------------------------------------------------
/apps/react/src/css/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | @apply flex flex-col justify-center items-center h-screen w-screen font-ubuntu;
3 | @apply text-slate-200 bg-gradient-to-b from-slate-800 to-slate-900;
4 | }
5 |
6 | #root {
7 | @apply flex flex-col justify-center items-center gap-8 w-[90vw] min-h-[90vh] xl:w-[80vw] xl:min-h-[80vh];
8 | @apply bg-slate-800 rounded-md border border-slate-700;
9 | }
10 |
11 | h1 {
12 | @apply text-3xl font-bold tracking-tight my-4;
13 | }
14 |
15 | img.react-logo {
16 | @apply w-28 h-28 animate-[spin_8s_linear_infinite];
17 | }
18 |
--------------------------------------------------------------------------------
/apps/react/src/css/tailwind.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;500;700&display=swap");
2 |
3 | @import "tailwindcss/base";
4 | @import "tailwindcss/components";
5 | @import "tailwindcss/utilities";
6 |
7 | @import "./main.css";
8 |
--------------------------------------------------------------------------------
/apps/react/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Monorepo
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/apps/react/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client';
2 | import { App } from './App.js';
3 |
4 | createRoot(document.getElementById('root') as HTMLDivElement).render();
--------------------------------------------------------------------------------
/apps/react/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | const defaultTheme = require('tailwindcss/defaultTheme');
2 |
3 | /** @type {import('tailwindcss').Config} */
4 | module.exports = {
5 | content: ['./src/**/*.(tsx|html)'],
6 | theme: {
7 | extend: {
8 | fontFamily: {
9 | 'ubuntu': ['Ubuntu', ...defaultTheme.fontFamily.sans],
10 | },
11 | },
12 | },
13 | plugins: [],
14 | };
--------------------------------------------------------------------------------
/apps/react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/config/typescript/tsconfig.react.json",
3 | "compilerOptions": {
4 | "rootDir": "./src/",
5 | "outDir": "./out/"
6 | },
7 | "include": ["./src/"],
8 | "exclude": [
9 | "**/node_modules",
10 | "**/.*/"
11 | ]
12 | }
--------------------------------------------------------------------------------
/apps/server/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | es2021: true,
5 | node: true,
6 | },
7 | plugins: ["@typescript-eslint"],
8 | parser: "@typescript-eslint/parser",
9 | parserOptions: {
10 | ecmaVersion: "latest",
11 | sourceType: "module",
12 | tsconfigRootDir: __dirname,
13 | project: ["./tsconfig.json"],
14 | },
15 | ignorePatterns: ["*.*", "!src/**/*"],
16 | rules: {
17 | ...require("@repo/config/eslint/rules-default.cjs"),
18 | ...require("@repo/config/eslint/rules-typescript.cjs"),
19 | }
20 | };
--------------------------------------------------------------------------------
/apps/server/esbuild.js:
--------------------------------------------------------------------------------
1 | import { buildNode } from '@repo/config/esbuild'
2 | await buildNode({});
--------------------------------------------------------------------------------
/apps/server/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["/out/**/*.js"],
3 | "ext": "js",
4 | "verbose": false
5 | }
--------------------------------------------------------------------------------
/apps/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/server",
3 | "author": "decho",
4 | "version": "1.0.0",
5 | "description": "An example server application (nodejs)",
6 | "license": "ISC",
7 | "type": "module",
8 | "scripts": {
9 | "build": "node esbuild.js",
10 | "check": "npx tsc --watch",
11 | "watch": "nodemon out/index.js",
12 | "lint": "npx eslint src/**/*.ts"
13 | },
14 | "dependencies": {
15 | "@repo/shared": "*"
16 | },
17 | "devDependencies": {
18 | "@repo/config": "*",
19 | "@types/node": "^20.2.5"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/apps/server/src/index.ts:
--------------------------------------------------------------------------------
1 | import { createServer } from 'http';
2 | import { main } from '@repo/shared';
3 | import { getVersion, getPlatform } from '@repo/shared/server';
4 |
5 | console.log(main);
6 |
7 | const server = createServer((req, res) => {
8 | res
9 | .writeHead(200, { 'Content-Type': 'application/json' })
10 | .end(
11 | JSON.stringify({
12 | node: getVersion(),
13 | platform: getPlatform(),
14 | repository: 'https://github.com/barca-reddit/esbuild-typescript-turborepo'
15 | }),
16 | 'utf-8'
17 | );
18 | }).listen(8000);
19 |
20 | server.once('listening', () => {
21 | console.log('\nServer accepting requests at http://localhost:8000\n');
22 | });
23 |
--------------------------------------------------------------------------------
/apps/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/config/typescript/tsconfig.node.json",
3 | "compilerOptions": {
4 | "rootDir": "./src/",
5 | "outDir": "./out/"
6 | },
7 | "include": ["./src/"],
8 | "exclude": [
9 | "**/node_modules",
10 | "**/.*/"
11 | ]
12 | }
--------------------------------------------------------------------------------
/apps/web/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | es2021: true,
5 | node: true,
6 | },
7 | plugins: ["@typescript-eslint"],
8 | parser: "@typescript-eslint/parser",
9 | parserOptions: {
10 | ecmaVersion: "latest",
11 | sourceType: "module",
12 | tsconfigRootDir: __dirname,
13 | project: ["./tsconfig.json"],
14 | },
15 | ignorePatterns: ["*.*", "!src/**/*"],
16 | rules: {
17 | ...require("@repo/config/eslint/rules-default.cjs"),
18 | ...require("@repo/config/eslint/rules-typescript.cjs"),
19 | }
20 | };
--------------------------------------------------------------------------------
/apps/web/esbuild.js:
--------------------------------------------------------------------------------
1 | import { copyFile } from 'fs/promises';
2 | import { constants } from 'fs';
3 | import { buildBrowser } from '@repo/config/esbuild';
4 |
5 | await buildBrowser({});
6 | await copyFile(
7 | './src/index.html',
8 | './out/index.html',
9 | constants.COPYFILE_FICLONE
10 | );
--------------------------------------------------------------------------------
/apps/web/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["/out/**/*.js"],
3 | "ext": "js",
4 | "verbose": false
5 | }
--------------------------------------------------------------------------------
/apps/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/web",
3 | "author": "decho",
4 | "version": "1.0.0",
5 | "description": "An example web application (browser)",
6 | "license": "ISC",
7 | "type": "module",
8 | "scripts": {
9 | "build": "node esbuild.js",
10 | "check": "npx tsc --watch",
11 | "watch": "nodemon out/index.js",
12 | "lint": "npx eslint src/**/*.ts"
13 | },
14 | "dependencies": {
15 | "@repo/shared": "*"
16 | },
17 | "devDependencies": {
18 | "@repo/config": "*"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apps/web/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 | Open Devtools to see script output.
14 |
15 |
16 |
--------------------------------------------------------------------------------
/apps/web/src/index.ts:
--------------------------------------------------------------------------------
1 | import { getTitle, getURL } from '@repo/shared/web';
2 |
3 | if (typeof window !== 'undefined') {
4 | console.log(`Page title: ${getTitle()}`);
5 | console.log(`Page URL: ${getURL()}`);
6 | }
--------------------------------------------------------------------------------
/apps/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/config/typescript/tsconfig.browser.json",
3 | "compilerOptions": {
4 | "rootDir": "./src/",
5 | "outDir": "./out/"
6 | },
7 | "include": ["./src/"],
8 | "exclude": [
9 | "**/node_modules",
10 | "**/.*/"
11 | ]
12 | }
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": [
3 | "/apps/**/*",
4 | "/packages/**/*"
5 | ],
6 | "ignore": [
7 | "/cache",
8 | "/node_modules"
9 | ],
10 | "ext": "ts,tsx",
11 | "verbose": false
12 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "esbuild-typescript-turborepo-monorepo-starter",
3 | "author": "decho",
4 | "private": true,
5 | "version": "1.0.0",
6 | "description": "ESBuild + TypeScript + Turborepo + ESLint Monorepo starter template.",
7 | "license": "ISC",
8 | "type": "module",
9 | "engines": {
10 | "node": ">=16.0.0",
11 | "npm": ">=7.0.0"
12 | },
13 | "workspaces": [
14 | "apps/*",
15 | "packages/*"
16 | ],
17 | "scripts": {
18 | "build": "npx turbo run build --cache-dir='./cache'",
19 | "serve": "npm run watch --workspace=@repo/server",
20 | "check": "npm run check --workspace=@repo/shared & npm run check --workspace=@repo/server & npm run check --workspace=@repo/web & npm run check --workspace=@repo/react",
21 | "watch": "npx rimraf ./cache && npx nodemon --exec 'npm run build && npm run serve & npm run check'",
22 | "lint": "npx turbo run lint",
23 | "mismatch": "node packages/config/util/pkg-mismatch.js"
24 | },
25 | "devDependencies": {
26 | "@repo/react": "^1.0.0",
27 | "@typescript-eslint/eslint-plugin": "^5.59.9",
28 | "@typescript-eslint/parser": "^5.59.9",
29 | "nodemon": "^2.0.22",
30 | "rimraf": "^5.0.1",
31 | "tailwindcss": "^3.3.2",
32 | "turbo": "^1.10.2",
33 | "typescript": "^5.1.3"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/config/esbuild/build-browser.js:
--------------------------------------------------------------------------------
1 | import { build } from 'esbuild';
2 | import fg from 'fast-glob';
3 |
4 | export const buildBrowser = async ({ ...args }) => {
5 | await build({
6 | entryPoints: await fg('src/**/*.ts'),
7 | platform: 'browser',
8 | target: 'es6',
9 | format: 'iife',
10 | bundle: true,
11 | outdir: './out',
12 | sourcemap: false,
13 | logLevel: 'info',
14 | ...args,
15 | })
16 | };
--------------------------------------------------------------------------------
/packages/config/esbuild/build-node.js:
--------------------------------------------------------------------------------
1 | import { build } from 'esbuild';
2 | import fg from 'fast-glob';
3 | import { rmOutDirPlugin } from './plugins/rm-out-dir.js';
4 |
5 | export const buildNode = async ({ ...args }) => {
6 | await build({
7 | entryPoints: await fg('src/**/*.ts'),
8 | platform: 'node',
9 | target: 'node16',
10 | format: 'esm',
11 | bundle: false,
12 | outdir: './out',
13 | sourcemap: false,
14 | logLevel: 'info',
15 | plugins: [rmOutDirPlugin()],
16 | ...args
17 | })
18 | };
19 |
--------------------------------------------------------------------------------
/packages/config/esbuild/build-tailwind.js:
--------------------------------------------------------------------------------
1 | import { build } from 'esbuild';
2 | import postCSS from 'esbuild-postcss';
3 |
4 | export const buildTailwind = async ({ ...args }) => {
5 | await build({
6 | entryPoints: ['./src/css/tailwind.css'],
7 | outfile: './out/css/style.css',
8 | bundle: true,
9 | minify: false,
10 | external: ['*.svg'],
11 | logLevel: 'info',
12 | plugins: [postCSS()],
13 | ...args
14 | })
15 | }
--------------------------------------------------------------------------------
/packages/config/esbuild/build.react.js:
--------------------------------------------------------------------------------
1 | import { build } from 'esbuild';
2 |
3 | export const buildReact = async ({ ...args }) => {
4 | await build({
5 | entryPoints: ['./src/index.tsx'],
6 | platform: 'browser',
7 | target: 'es6',
8 | format: 'iife',
9 | jsx: 'automatic',
10 | bundle: true,
11 | outfile: './out/index.js',
12 | sourcemap: false,
13 | logLevel: 'info',
14 | ...args,
15 | })
16 | };
--------------------------------------------------------------------------------
/packages/config/esbuild/index.js:
--------------------------------------------------------------------------------
1 | export * from './build-browser.js';
2 | export * from './build-node.js';
3 | export * from './build.react.js';
4 | export * from './build-tailwind.js';
5 | export * from './plugins/rm-out-dir.js';
--------------------------------------------------------------------------------
/packages/config/esbuild/plugins/rm-out-dir.js:
--------------------------------------------------------------------------------
1 | import { rm } from 'fs/promises';
2 |
3 | const rmOutDir = async ({ path }) => {
4 | try {
5 | await rm(path, { recursive: true });
6 | } catch (error) { }
7 | }
8 |
9 | export const rmOutDirPlugin = () => ({
10 | name: 'rm out dir',
11 | setup({ onStart }) {
12 | onStart(async () => {
13 | await rmOutDir({ path: './out' });
14 | })
15 | }
16 | });
--------------------------------------------------------------------------------
/packages/config/eslint/rules-default.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'semi': ['warn', 'always'],
3 | 'quotes': ['warn', 'single'],
4 | 'eqeqeq': ['error', 'always', {
5 | 'null': 'ignore'
6 | }],
7 | 'no-unsafe-optional-chaining': ['error', {
8 | 'disallowArithmeticOperators': true
9 | }]
10 | };
--------------------------------------------------------------------------------
/packages/config/eslint/rules-react.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "react/react-in-jsx-scope": 0,
3 | "react/jsx-uses-react": 0,
4 | }
5 |
--------------------------------------------------------------------------------
/packages/config/eslint/rules-typescript.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | '@typescript-eslint/no-unused-vars': ['warn'],
3 | '@typescript-eslint/no-floating-promises': ['error', {
4 | 'ignoreVoid': false,
5 | 'ignoreIIFE': false
6 | }],
7 | '@typescript-eslint/no-misused-promises': ['error', {
8 | 'checksVoidReturn': false
9 | }],
10 | '@typescript-eslint/await-thenable': ['error'],
11 | '@typescript-eslint/consistent-type-imports': ['warn', {
12 | 'prefer': 'type-imports'
13 | }],
14 | '@typescript-eslint/consistent-type-exports': ['warn', {
15 | 'fixMixedExportsWithInlineTypeSpecifier': true
16 | }],
17 | '@typescript-eslint/explicit-member-accessibility': ['warn', {
18 | 'accessibility': 'explicit',
19 | 'overrides': {
20 | 'constructors': 'off'
21 | }
22 | }]
23 | };
--------------------------------------------------------------------------------
/packages/config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/config",
3 | "author": "decho",
4 | "version": "1.0.0",
5 | "description": "Configurations",
6 | "license": "ISC",
7 | "type": "module",
8 | "exports": {
9 | "./esbuild": {
10 | "import": "./esbuild/index.js"
11 | },
12 | "./eslint/*.cjs": {
13 | "require": "./eslint/*.cjs"
14 | },
15 | "./typescript/*.json": "./typescript/*.json"
16 | },
17 | "devDependencies": {
18 | "esbuild": "^0.17.19",
19 | "esbuild-postcss": "^0.0.4",
20 | "fast-glob": "^3.2.12"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/config/typescript/tsconfig.browser.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "CommonJS",
4 | "moduleResolution": "NodeNext",
5 | "target": "ESNext",
6 |
7 | "forceConsistentCasingInFileNames": true,
8 | "allowUnreachableCode": true,
9 |
10 | "esModuleInterop": true,
11 | "isolatedModules": true,
12 | "resolveJsonModule": true,
13 |
14 | "strict": true,
15 | "noEmit": true,
16 | "preserveWatchOutput": true
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/config/typescript/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "esnext",
4 | "moduleResolution": "NodeNext",
5 | "target": "esnext",
6 |
7 | "forceConsistentCasingInFileNames": true,
8 | "allowUnreachableCode": true,
9 |
10 | "esModuleInterop": true,
11 | "isolatedModules": true,
12 | "resolveJsonModule": true,
13 |
14 | "strict": true,
15 | "noEmit": true,
16 | "preserveWatchOutput": true
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/config/typescript/tsconfig.react.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "ESNext",
4 | "moduleResolution": "NodeNext",
5 | "target": "ESNext",
6 |
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "allowUnreachableCode": true,
10 |
11 | "esModuleInterop": true,
12 | "isolatedModules": true,
13 | "resolveJsonModule": true,
14 |
15 | "preserveWatchOutput": true,
16 | "noEmit": true,
17 |
18 | "jsx": "react-jsx",
19 | "allowJs": true,
20 | "allowSyntheticDefaultImports": true,
21 |
22 | "lib": ["dom", "dom.iterable", "esnext"]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/config/util/pkg-mismatch.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Do not call this script from anywhere else other than project root,
3 | * otherwise the context of process.cwd changes.
4 | */
5 |
6 | import { readFile } from 'fs/promises';
7 | import fg from 'fast-glob';
8 |
9 | class Dependencies {
10 | constructor() {
11 | this.packageList = [];
12 | this.misMatchList = [];
13 | }
14 |
15 | addDependencies({ dependencyList, pkgParent, pkgPath }) {
16 | for (const [pkgName, pkgVersion] of Object.entries(dependencyList)) {
17 | this.packageList.push({
18 | pkgName: pkgName,
19 | pkgVersion: pkgVersion,
20 | pkgParent: pkgParent,
21 | pkgPath: pkgPath
22 | })
23 | }
24 | }
25 |
26 | async getAll() {
27 | for (const pkgPath of await fg('**/package.json', { ignore: '**/node_modules/**' })) {
28 | const { name, dependencies, devDependencies, peerDependencies } = JSON.parse(
29 | await readFile(pkgPath, 'utf-8')
30 | );
31 |
32 | if (dependencies) {
33 | this.addDependencies({ dependencyList: dependencies, pkgParent: name, pkgPath: pkgPath })
34 | }
35 | if (devDependencies) {
36 | this.addDependencies({ dependencyList: devDependencies, pkgParent: name, pkgPath: pkgPath })
37 | }
38 | if (peerDependencies) {
39 | this.addDependencies({ dependencyList: peerDependencies, pkgParent: name, pkgPath: pkgPath })
40 | }
41 | }
42 | return this;
43 | }
44 |
45 | checkMismatches() {
46 | const packageNames = [...new Set(this.packageList.map(p => p.pkgName))];
47 |
48 | for (const pkgName of packageNames) {
49 | const pkgSiblings = this.packageList.filter(p => p.pkgName === pkgName);
50 | if (pkgSiblings.length > 1 && pkgSiblings.some(p => p.pkgVersion !== pkgSiblings[0].pkgVersion)) {
51 | this.misMatchList.push(pkgSiblings);
52 | }
53 | }
54 |
55 | if (this.misMatchList.length > 0) {
56 | throw new Error(
57 | `Found version mismatch for the following package${this.misMatchList.length > 1 ? 's' : ''}` + ':\n\n' +
58 | this.misMatchList
59 | .map(pkg =>
60 | `${pkg[0].pkgName} - versions: ${[...new Set(pkg.map(p => p.pkgVersion))].join(', ')}\n` +
61 | pkg.map((p, i) => `- ${p.pkgPath} (${pkg[i].pkgParent}) - ${pkg[i].pkgVersion}`).join('\n') + '\n'
62 | ).join('\n')
63 | )
64 | }
65 |
66 | console.log('All package versions appear to be in sync.\n');
67 | }
68 | }
69 |
70 | (await new Dependencies().getAll()).checkMismatches();
71 |
--------------------------------------------------------------------------------
/packages/shared/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | es2021: true,
5 | node: true,
6 | },
7 | plugins: ["@typescript-eslint"],
8 | parser: "@typescript-eslint/parser",
9 | parserOptions: {
10 | ecmaVersion: "latest",
11 | sourceType: "module",
12 | tsconfigRootDir: __dirname,
13 | project: ["./tsconfig.json"],
14 | },
15 | ignorePatterns: ["*.*", "!src/**/*"],
16 | rules: {
17 | ...require("@repo/config/eslint/rules-default.cjs"),
18 | ...require("@repo/config/eslint/rules-typescript.cjs"),
19 | }
20 | };
--------------------------------------------------------------------------------
/packages/shared/esbuild.js:
--------------------------------------------------------------------------------
1 | import { build } from 'esbuild';
2 | import fg from 'fast-glob';
3 |
4 | export const buildNode = async ({ ...args }) => {
5 | await build({
6 | entryPoints: await fg('src/**/*.ts'),
7 | platform: 'node',
8 | target: 'node16',
9 | format: 'esm',
10 | outdir: './out',
11 | sourcemap: false,
12 | logLevel: 'info',
13 | ...args
14 | })
15 | };
16 |
17 | await buildNode({});
--------------------------------------------------------------------------------
/packages/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/shared",
3 | "author": "decho",
4 | "version": "1.0.0",
5 | "description": "Package which can be shared across the monorepo",
6 | "license": "ISC",
7 | "type": "module",
8 | "exports": {
9 | ".": {
10 | "types": "./src/main.ts",
11 | "import": "./out/main.js"
12 | },
13 | "./server": {
14 | "types": "./src/server/index.ts",
15 | "import": "./out/server/index.js"
16 | },
17 | "./web": {
18 | "types": "./src/web/index.ts",
19 | "import": "./out/web/index.js"
20 | },
21 | "./*": {
22 | "types": "./src/*.ts",
23 | "import": "./out/*.js"
24 | }
25 | },
26 | "scripts": {
27 | "build": "rm -rf out && node esbuild.js",
28 | "check": "npx tsc --watch",
29 | "lint": "npx eslint src/**/*.ts"
30 | },
31 | "devDependencies": {
32 | "@repo/config": "*",
33 | "@types/node": "^20.2.5"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/shared/src/main.ts:
--------------------------------------------------------------------------------
1 | export const main = 'hello from the shared package';
--------------------------------------------------------------------------------
/packages/shared/src/server/example1.ts:
--------------------------------------------------------------------------------
1 | import { version } from 'process';
2 |
3 | /** Get the current NodeJS version */
4 | export const getVersion = () => {
5 | return version;
6 | };
--------------------------------------------------------------------------------
/packages/shared/src/server/example2.ts:
--------------------------------------------------------------------------------
1 | import { platform } from 'os';
2 |
3 | /** Get the current OS platform */
4 | export const getPlatform = () => {
5 | return platform();
6 | };
7 |
--------------------------------------------------------------------------------
/packages/shared/src/server/index.ts:
--------------------------------------------------------------------------------
1 | export * from './example1.js';
2 | export * from './example2.js';
--------------------------------------------------------------------------------
/packages/shared/src/web/example1.ts:
--------------------------------------------------------------------------------
1 | /** Get the title of the current page */
2 | export const getTitle = () => {
3 | return window.document.title;
4 | };
5 |
--------------------------------------------------------------------------------
/packages/shared/src/web/example2.ts:
--------------------------------------------------------------------------------
1 | /** Get the URL of the current page */
2 | export const getURL = () => {
3 | return window.location.href;
4 | };
--------------------------------------------------------------------------------
/packages/shared/src/web/index.ts:
--------------------------------------------------------------------------------
1 | export * from './example1.js';
2 | export * from './example2.js';
--------------------------------------------------------------------------------
/packages/shared/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/config/typescript/tsconfig.node.json",
3 | "compilerOptions": {
4 | "rootDir": "./src",
5 | "outDir": "./out",
6 | "baseUrl": ".",
7 | },
8 | "include": ["src/**/*"],
9 | "exclude": [
10 | "**/node_modules",
11 | "**/.*/"
12 | ]
13 | }
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turborepo.org/schema.json",
3 | "pipeline": {
4 | "build": {
5 | "dependsOn": ["^build"],
6 | "outputs": ["out/**"],
7 | "cache": true
8 | },
9 | "lint": {
10 | "outputs": []
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------