diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..02f791d
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,5 @@
+/** @type {import("eslint").Linter.Config} */
+module.exports = {
+ root: true,
+ extends: ["@repo/eslint-config/index.js"],
+};
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a627497
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+.DS_Store
+node_modules
+.turbo
+*.log
+dist
+dist-ssr
+*.local
+.env
+.cache
+server/dist
+public/dist
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..ded82e2
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+auto-install-peers = true
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..88e40c4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,33 @@
+# `Turborepo` Vite starter
+
+This is an official starter Turborepo.
+
+## Using this example
+
+Run the following command:
+
+```sh
+npx create-turbo@latest -e with-vite
+```
+
+## What's inside?
+
+This Turborepo includes the following packages and apps:
+
+### Apps and Packages
+
+- `docs`: a vanilla [vite](https://vitejs.dev) ts app
+- `web`: another vanilla [vite](https://vitejs.dev) ts app
+- `@repo/ui`: a stub component & utility library shared by both `web` and `docs` applications
+- `@repo/eslint-config`: shared `eslint` configurations
+- `@repo/typescript-config`: `tsconfig.json`s used throughout the monorepo
+
+Each package and app is 100% [TypeScript](https://www.typescriptlang.org/).
+
+### Utilities
+
+This Turborepo has some additional tools already setup for you:
+
+- [TypeScript](https://www.typescriptlang.org/) for static type checking
+- [ESLint](https://eslint.org/) for code linting
+- [Prettier](https://prettier.io) for code formatting
diff --git a/apps/docs/.eslintrc.cjs b/apps/docs/.eslintrc.cjs
new file mode 100644
index 0000000..02f791d
--- /dev/null
+++ b/apps/docs/.eslintrc.cjs
@@ -0,0 +1,5 @@
+/** @type {import("eslint").Linter.Config} */
+module.exports = {
+ root: true,
+ extends: ["@repo/eslint-config/index.js"],
+};
diff --git a/apps/docs/index.html b/apps/docs/index.html
new file mode 100644
index 0000000..f86e483
--- /dev/null
+++ b/apps/docs/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite + TS
+
+
+
+
+
+
diff --git a/apps/docs/package.json b/apps/docs/package.json
new file mode 100644
index 0000000..7da2c8e
--- /dev/null
+++ b/apps/docs/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "docs",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite --clearScreen false",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "lint": "eslint \"src/**/*.ts\""
+ },
+ "dependencies": {
+ "@repo/ui": "*"
+ },
+ "devDependencies": {
+ "@repo/eslint-config": "*",
+ "@repo/typescript-config": "*",
+ "eslint": "^8.57.0",
+ "typescript": "^5.3.3",
+ "vite": "^5.1.4"
+ }
+}
diff --git a/apps/docs/public/typescript.svg b/apps/docs/public/typescript.svg
new file mode 100644
index 0000000..d91c910
--- /dev/null
+++ b/apps/docs/public/typescript.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/docs/public/vite.svg b/apps/docs/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/apps/docs/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/docs/src/main.ts b/apps/docs/src/main.ts
new file mode 100644
index 0000000..02ac44b
--- /dev/null
+++ b/apps/docs/src/main.ts
@@ -0,0 +1,22 @@
+import { Header } from "@repo/ui/header";
+import "./style.css";
+import typescriptLogo from "/typescript.svg";
+import { Counter } from "@repo/ui/counter";
+import { setupCounter } from "@repo/ui/setup-counter";
+
+document.querySelector("#app")!.innerHTML = `
+
+
+
+
+
+
+
+ ${Header({ title: "Docs" })}
+
+ ${Counter()}
+
+
+`;
+
+setupCounter(document.querySelector("#counter")!);
diff --git a/apps/docs/src/style.css b/apps/docs/src/style.css
new file mode 100644
index 0000000..1232080
--- /dev/null
+++ b/apps/docs/src/style.css
@@ -0,0 +1,97 @@
+:root {
+ font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
+ font-size: 16px;
+ line-height: 24px;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-text-size-adjust: 100%;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+#app {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.vanilla:hover {
+ filter: drop-shadow(0 0 2em #f7df1eaa);
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/apps/docs/src/vite-env.d.ts b/apps/docs/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/apps/docs/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json
new file mode 100644
index 0000000..140bfb5
--- /dev/null
+++ b/apps/docs/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "@repo/typescript-config/vite.json",
+ "include": ["src"]
+}
diff --git a/apps/web/.eslintrc.cjs b/apps/web/.eslintrc.cjs
new file mode 100644
index 0000000..02f791d
--- /dev/null
+++ b/apps/web/.eslintrc.cjs
@@ -0,0 +1,5 @@
+/** @type {import("eslint").Linter.Config} */
+module.exports = {
+ root: true,
+ extends: ["@repo/eslint-config/index.js"],
+};
diff --git a/apps/web/index.html b/apps/web/index.html
new file mode 100644
index 0000000..f86e483
--- /dev/null
+++ b/apps/web/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite + TS
+
+
+
+
+
+
diff --git a/apps/web/package.json b/apps/web/package.json
new file mode 100644
index 0000000..376a96f
--- /dev/null
+++ b/apps/web/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "web",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite --clearScreen false",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "lint": "eslint \"src/**/*.ts\""
+ },
+ "dependencies": {
+ "@repo/ui": "*"
+ },
+ "devDependencies": {
+ "@repo/eslint-config": "*",
+ "@repo/typescript-config": "*",
+ "eslint": "^8.57.0",
+ "typescript": "^5.3.3",
+ "vite": "^5.1.4"
+ }
+}
diff --git a/apps/web/public/typescript.svg b/apps/web/public/typescript.svg
new file mode 100644
index 0000000..d91c910
--- /dev/null
+++ b/apps/web/public/typescript.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/web/public/vite.svg b/apps/web/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/apps/web/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts
new file mode 100644
index 0000000..cbb7116
--- /dev/null
+++ b/apps/web/src/main.ts
@@ -0,0 +1,22 @@
+import { Header } from "@repo/ui/header";
+import "./style.css";
+import typescriptLogo from "/typescript.svg";
+import { Counter } from "@repo/ui/counter";
+import { setupCounter } from "@repo/ui/setup-counter";
+
+document.querySelector("#app")!.innerHTML = `
+
+
+
+
+
+
+
+ ${Header({ title: "Web" })}
+
+ ${Counter()}
+
+
+`;
+
+setupCounter(document.querySelector("#counter")!);
diff --git a/apps/web/src/style.css b/apps/web/src/style.css
new file mode 100644
index 0000000..1232080
--- /dev/null
+++ b/apps/web/src/style.css
@@ -0,0 +1,97 @@
+:root {
+ font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
+ font-size: 16px;
+ line-height: 24px;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-text-size-adjust: 100%;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+#app {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.vanilla:hover {
+ filter: drop-shadow(0 0 2em #f7df1eaa);
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/apps/web/src/vite-env.d.ts b/apps/web/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/apps/web/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
new file mode 100644
index 0000000..140bfb5
--- /dev/null
+++ b/apps/web/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "@repo/typescript-config/vite.json",
+ "include": ["src"]
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..dd35c76
--- /dev/null
+++ b/package.json
@@ -0,0 +1,20 @@
+{
+ "private": true,
+ "scripts": {
+ "build": "turbo build",
+ "dev": "turbo dev",
+ "lint": "turbo lint",
+ "format": "prettier --write \"**/*.{ts,tsx,md}\""
+ },
+ "devDependencies": {
+ "eslint": "^8.57.0",
+ "@repo/eslint-config": "*",
+ "prettier": "^3.2.5",
+ "turbo": "latest"
+ },
+ "packageManager": "npm@10.2.4",
+ "workspaces": [
+ "apps/*",
+ "packages/*"
+ ]
+}
diff --git a/packages/config-eslint/index.js b/packages/config-eslint/index.js
new file mode 100644
index 0000000..9417d1c
--- /dev/null
+++ b/packages/config-eslint/index.js
@@ -0,0 +1,19 @@
+module.exports = {
+ env: {
+ node: true,
+ },
+ parser: "@typescript-eslint/parser",
+ extends: [
+ "eslint:recommended",
+ "plugin:@typescript-eslint/recommended",
+ "prettier",
+ ],
+ plugins: ["@typescript-eslint"],
+ parserOptions: {
+ sourceType: "module",
+ ecmaVersion: 2020,
+ },
+ rules: {
+ "@typescript-eslint/no-non-null-assertion": "off",
+ },
+};
diff --git a/packages/config-eslint/package.json b/packages/config-eslint/package.json
new file mode 100644
index 0000000..fc2acf1
--- /dev/null
+++ b/packages/config-eslint/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "@repo/eslint-config",
+ "version": "0.0.0",
+ "main": "index.js",
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "^7.1.0",
+ "@typescript-eslint/parser": "^7.1.0",
+ "eslint-config-prettier": "^9.1.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/config-typescript/base.json b/packages/config-typescript/base.json
new file mode 100644
index 0000000..5209769
--- /dev/null
+++ b/packages/config-typescript/base.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "display": "Default",
+ "compilerOptions": {
+ "composite": false,
+ "declaration": true,
+ "declarationMap": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "inlineSources": false,
+ "isolatedModules": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "preserveWatchOutput": true,
+ "skipLibCheck": true,
+ "strict": true
+ },
+ "exclude": ["node_modules"]
+}
diff --git a/packages/config-typescript/package.json b/packages/config-typescript/package.json
new file mode 100644
index 0000000..27c0e60
--- /dev/null
+++ b/packages/config-typescript/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@repo/typescript-config",
+ "version": "0.0.0",
+ "private": true,
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/config-typescript/vite.json b/packages/config-typescript/vite.json
new file mode 100644
index 0000000..3c507ed
--- /dev/null
+++ b/packages/config-typescript/vite.json
@@ -0,0 +1,16 @@
+{
+ "extends": "./base.json",
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ESNext", "DOM"],
+ "sourceMap": true,
+ "resolveJsonModule": true,
+ "noEmit": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noImplicitReturns": true
+ },
+ "exclude": ["node_modules"]
+}
diff --git a/packages/ui/.eslintrc.cjs b/packages/ui/.eslintrc.cjs
new file mode 100644
index 0000000..1ba520f
--- /dev/null
+++ b/packages/ui/.eslintrc.cjs
@@ -0,0 +1,10 @@
+/** @type {import("eslint").Linter.Config} */
+module.exports = {
+ root: true,
+ ignorePatterns: [".eslintrc.cjs"],
+ extends: ["@repo/eslint-config/index.js"],
+ parser: "@typescript-eslint/parser",
+ parserOptions: {
+ project: true,
+ },
+};
diff --git a/packages/ui/components/counter.ts b/packages/ui/components/counter.ts
new file mode 100644
index 0000000..d58f883
--- /dev/null
+++ b/packages/ui/components/counter.ts
@@ -0,0 +1,3 @@
+export function Counter() {
+ return ``;
+}
diff --git a/packages/ui/components/header.ts b/packages/ui/components/header.ts
new file mode 100644
index 0000000..14e0ab9
--- /dev/null
+++ b/packages/ui/components/header.ts
@@ -0,0 +1,7 @@
+export function Header({ title }: { title: string }) {
+ return `
+
+ `;
+}
diff --git a/packages/ui/index.ts b/packages/ui/index.ts
new file mode 100644
index 0000000..438ff06
--- /dev/null
+++ b/packages/ui/index.ts
@@ -0,0 +1,6 @@
+// utils
+export { setupCounter } from "./utils/counter";
+
+// components
+export { Header } from "./components/header";
+export { Counter } from "./components/counter";
diff --git a/packages/ui/package.json b/packages/ui/package.json
new file mode 100644
index 0000000..31acb48
--- /dev/null
+++ b/packages/ui/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@repo/ui",
+ "version": "0.0.0",
+ "exports": {
+ "./counter": "./components/counter.ts",
+ "./header": "./components/header.ts",
+ "./setup-counter": "./utils/counter.ts"
+ },
+ "license": "MIT",
+ "scripts": {
+ "lint": "eslint \"**/*.ts\""
+ },
+ "devDependencies": {
+ "@repo/eslint-config": "*",
+ "@repo/typescript-config": "*",
+ "eslint": "^8.57.0",
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json
new file mode 100644
index 0000000..317a25a
--- /dev/null
+++ b/packages/ui/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "extends": "@repo/typescript-config/base.json",
+ "include": ["."],
+ "exclude": ["node_modules"]
+}
diff --git a/packages/ui/utils/counter.ts b/packages/ui/utils/counter.ts
new file mode 100644
index 0000000..0de7dcf
--- /dev/null
+++ b/packages/ui/utils/counter.ts
@@ -0,0 +1,9 @@
+export function setupCounter(element: HTMLButtonElement) {
+ let counter = 0;
+ const setCounter = (count: number) => {
+ counter = count;
+ element.innerText = `count is ${counter}`;
+ };
+ element.addEventListener("click", () => setCounter(++counter));
+ setCounter(0);
+}
diff --git a/turbo.json b/turbo.json
new file mode 100644
index 0000000..d3e52ca
--- /dev/null
+++ b/turbo.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://turbo.build/schema.json",
+ "pipeline": {
+ "build": {
+ "dependsOn": ["^build"],
+ "outputs": ["dist/**"]
+ },
+ "lint": {},
+ "dev": {
+ "cache": false,
+ "persistent": true
+ }
+ }
+}