├── .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 | } --------------------------------------------------------------------------------