Skip to content

Instantly share code, notes, and snippets.

@alexspeller
Created February 3, 2026 19:25
Show Gist options
  • Select an option

  • Save alexspeller/8b2d5b07a663158f58a5d36425341c17 to your computer and use it in GitHub Desktop.

Select an option

Save alexspeller/8b2d5b07a663158f58a5d36425341c17 to your computer and use it in GitHub Desktop.

Project Template: TypeScript + Biome + Vite

mise (Runtime Management)

mise manages Node.js and Yarn versions per-project.

mise.toml

[tools]
node = "latest"
yarn = "latest"

Install with brew install mise, then run mise install in the project root to activate the correct runtimes. mise replaces nvm/fnm/volta for Node version management.

Package Manager

Yarn 4 (Berry) with node-modules linker.

.yarnrc.yml

nodeLinker: node-modules

Set in package.json:

{
  "packageManager": "yarn@4.9.2"
}

Enable with corepack enable then yarn set version stable.

package.json Essentials

{
  "private": true,
  "type": "module",
  "main": "dist/index.js",
  "scripts": {
    "format:fix": "biome check --error-on-warnings --write .",
    "check": "biome check --error-on-warnings .",
    "typecheck": "tsc --noEmit"
  }
}

TypeScript

Version: ^5.8.3

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ES2022",
    "lib": ["ES2022"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "moduleResolution": "node",
    "allowJs": false,
    "noEmit": true,
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "allowImportingTsExtensions": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Key choices:

  • ES2022 target/module — modern baseline with top-level await, Array.at(), etc.
  • strict: true plus extra strictness flags (noUnusedLocals, noUnusedParameters, noImplicitReturns, noFallthroughCasesInSwitch)
  • noEmit: true — TypeScript is used for type checking only; Vite/ts-node handle compilation
  • allowImportingTsExtensions: true — allows import "./foo.ts" style imports (requires noEmit)
  • moduleResolution: "node" — standard Node resolution

Biome

Version: ^2.1.2

biome.json

{
  "$schema": "https://biomejs.dev/schemas/2.1.2/schema.json",
  "files": {
    "includes": [
      "src/**/*",
      "*.ts",
      "*.tsx",
      "*.js",
      "*.jsx",
      "*.json"
    ],
    "ignoreUnknown": true
  },
  "linter": {
    "domains": {
      "project": "recommended",
      "test": "recommended"
    },
    "rules": {
      "style": "warn",
      "correctness": "error",
      "nursery": {
        "noTsIgnore": "error",
        "noFloatingPromises": "error",
        "noShadow": "error",
        "noSecrets": {
          "level": "error",
          "options": {
            "entropyThreshold": 60
          }
        }
      }
    }
  },
  "formatter": {
    "indentStyle": "space",
    "indentWidth": 2
  },
  "overrides": [
    {
      "includes": ["**/*"],
      "linter": {
        "rules": {
          "correctness": {
            "noNodejsModules": "off"
          },
          "style": {
            "noProcessEnv": "off"
          }
        }
      }
    }
  ]
}

Key choices:

  • --error-on-warnings flag in scripts — treats warnings as errors in CI/scripts so nothing slips through
  • Nursery rules enabled: noTsIgnore, noFloatingPromises, noShadow, noSecrets — catches common bugs that the stable ruleset misses
  • noNodejsModules: off — needed for Node.js backend code (Biome defaults to browser context)
  • noProcessEnv: off — allows process.env access directly
  • Space indent, width 2

Vite

Version: ^7.0.5

Dev dependencies for a React + Tailwind setup:

{
  "@vitejs/plugin-react": "^4.7.0",
  "@tailwindcss/vite": "^4.1.11",
  "autoprefixer": "^10.4.21",
  "postcss": "^8.5.6",
  "tailwindcss": "^4.1.11",
  "vite": "^7.0.5"
}

No vite.config.ts is present in this project yet — use Vite defaults or configure as needed.

ts-node (for scripts)

Used to run TypeScript scripts directly:

{
  "dependencies": {
    "ts-node": "^10.9.2"
  }
}

Run scripts with: yarn ts-node src/scripts/my-script.ts

Lefthook (Git Hooks)

lefthook.yml

pre-commit:
  parallel: true
  commands:
    biome:
      glob: "*.{js,ts,jsx,tsx,json,jsonc,css}"
      run: ./node_modules/.bin/biome check --error-on-warnings --staged

    tsc:
      glob: "*.{js,ts,tsx,jsx}"
      run: ./node_modules/.bin/tsc --noEmit

    todos:
      tags: style
      run: OUTPUT=$(grep -r "TODO" {staged_files} | tee /dev/stderr | wc -l) && test $OUTPUT -eq 0

Three parallel pre-commit checks:

  1. Biome — lint + format on staged files only
  2. TypeScript — full type check
  3. TODO check — prevents committing TODO comments

Install with brew install lefthook then lefthook install.

Setup Checklist for a New Project

  1. brew install mise lefthook
  2. Create mise.toml with Node and Yarn tools, then mise install
  3. corepack enable && yarn init
  4. yarn set version stable
  5. Create .yarnrc.yml with nodeLinker: node-modules
  6. yarn add -D typescript @biomejs/biome
  7. Copy tsconfig.json and biome.json from above
  8. Add scripts to package.json (check, format:fix, typecheck)
  9. lefthook install and copy lefthook.yml from above
  10. For frontend: yarn add -D vite @vitejs/plugin-react tailwindcss @tailwindcss/vite autoprefixer postcss
  11. For scripts: yarn add ts-node
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment