Skip to content

使用 Turborepo 构建 Monorepo

项目目录

bash

├── apps
   ├── web(业务文件夹)
   └── docs(业务文件夹)
├── packages
   ├── ui
   ├── tsconfig.json
   ├── package.json
   ├── src
    ├── index.js
    ├── Button
      ├── index.vue
   ├── typescript-config
   ├── base.json
   ├── package.json
   ├── vue.json
├── pnpm-workspace.yaml
├── turbo.json
├── .editorconfig
├── .env
├── .gitignore
├── .npmrc
├── .prettierrc.cjs
├── .turbo.json
├── package.json
├── pnpm-lock.yaml

根目录 创建几个初始文件

初始化命令

bash
pnpm init

package.json

  • name 换成你自己的项目名称
json
{
  "name": "result",
  "private": true,
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "format": "prettier --write \"**/*.{ts,tsx,md}\"",
    "check-types": "turbo run check-types"
  },
  "devDependencies": {
    "@antfu/eslint-config": "3.0.0",
    "eslint": "^9.39.1",
    "eslint-config-prettier": "^10.1.8",
    "eslint-plugin-prettier": "^5.5.4",
    "prettier": "^3.7.4",
    "turbo": "^2.7.3",
    "typescript": "5.9.2"
  },
  "packageManager": "pnpm@9.0.0",
  "engines": {
    "node": ">=18"
  }
}

turbo.json

json
{
  "$schema": "https://turborepo.com/schema.json",
  "ui": "tui",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", ".env*"],
      "outputs": ["dist/**", "build/**", "output/**", "public/**", ".nuxt/**"]
    },
    "lint": {
      "dependsOn": ["^lint"]
    },
    "check-types": {
      "dependsOn": ["^check-types"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

pnpm-workspace.yaml

yaml
packages:
  - "apps/*"
  - "packages/*"

eslint.config.js

js
import antfu from "@antfu/eslint-config";

const apps = "apps";
const histoirePath = `${apps}/histoire`;

const packages = "packages";
const packagesPath = `${packages}/ui`;

export default await antfu(
  {
    typescript: true,
    vue: true,
  },

  // Histoire Overrides
  {
    files: [`${histoirePath}/**/*.{js,ts,vue}`],
    rules: {
      "import/default": "off",
    },
  },
  {
    files: [`${packagesPath}/**/*.{js,ts,vue}`],
    rules: {
      "import/default": "off",
    },
  }
);

.prettierrc.cjs

js
module.exports = {
  /*打印宽度,超过后,会将属性换行*/

  printWidth: 120,

  /*禁止使用尾随逗号,对象和数组最后一个逗号去掉*/

  trailingComma: "none",

  /*在对象字面量中的括号之间添加空格*/

  bracketSpacing: true,

  /*使用单引号而不是双引号来定义字符串*/

  singleQuote: true,

  /*当箭头函数只有一个参数时,省略参数前后的括号*/

  arrowParens: "avoid",

  /*script和style标签中间的内容缩进*/

  vueIndentScriptAndStyle: true,

  // 将>多行 HTML(HTML、JSX、Vue、Angular)元素放在最后一行的末尾,而不是单独放在下一行(不适用于自闭合元素

  bracketSameLine: false,
};

.npmrc

bash
shamefully-hoist=true
strict-peer-dependencies=false
# 设置淘宝镜像
registry=https://registry.npmmirror.com

.gitignore

bash
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# Dependencies
node_modules
.pnp
.pnp.js

# Local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Testing
coverage

# Turbo
.turbo

# Vercel
.vercel

# Build Outputs
.next/
out/
build
dist


# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Misc
.DS_Store
*.pem

.env

  • 空着

.editorconfig

bash
# http://editorconfig.org

root = true



[*] # 表示所有文件

charset = utf-8 # 设置文件字符集为 utf-8

indent_style = space # 缩进风格(tab | space)

indent_size = 4 # 缩进大小

end_of_line = lf # 换行符(lf | cr | crlf)

insert_final_newline = true # 在文件结尾插入新行

trim_trailing_whitespace = true # 去除行尾空白



[*.md] # 表示对 md 文件应用以下规则

insert_final_newline = false # 不在文件结尾插入新行

trim_trailing_whitespace = false # 不去除行尾空白

packages/typescript-config

  • base.json
json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "esModuleInterop": true,
    "incremental": false,
    "isolatedModules": true,
    "lib": ["es2022", "DOM", "DOM.Iterable"],
    "module": "NodeNext",
    "moduleDetection": "force",
    "moduleResolution": "NodeNext",
    "noUncheckedIndexedAccess": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "target": "ES2022"
  }
}
  • package.json
json
{
  "name": "@repo/typescript-config",
  "version": "0.0.0",
  "private": true,
  "license": "MIT",
  "publishConfig": {
    "access": "public"
  }
}
  • vue.json
json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "esModuleInterop": true,
    "incremental": false,
    "isolatedModules": true,
    "lib": ["es2022", "DOM", "DOM.Iterable"],
    "module": "NodeNext",
    "moduleDetection": "force",
    "moduleResolution": "NodeNext",
    "noUncheckedIndexedAccess": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "target": "ES2022",
    "types": ["vue"],
    "baseUrl": ".",
    "paths": {
      "vue": ["node_modules/vue"]
    }
  },
  "include": [
    "apps/**/*",
    "packages/**/*",
    "src/**/*.ts",
    "src/**/*.vue",
    "src/**/*.d.ts"
  ]
}

packages/ui

  • package.json
json
{
  "name": "@repo/vueui",
  "type": "module",
  "version": "0.0.1",
  "private": true,
  "main": "./src/index.ts",
  "scripts": {
    "lint": "eslint . --max-warnings 0"
  },
  "dependencies": {
    "@vue/runtime-dom": "^3.5.26",
    "vue": "^3.5.24"
  },
  "devDependencies": {
    "@repo/typescript-config": "workspace:*",
    "@types/node": "^22.15.3",
    "eslint": "^9.39.1",
    "typescript": "5.9.2"
  }
}
  • tsconfig.json
json
{
  "extends": "@repo/typescript-config/vue.json",
  "compilerOptions": {
    "outDir": "dist"
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}
  • src/index.ts

注意

  1. 这个文件就是导出组件汇总方便调用

代码如下

ts
import VButton from "./Button/index.vue";

export { VButton };
  • src/Button/index.vue

代码如下

vue
<script lang="ts" setup>
import type { PropType } from "vue";

const props = defineProps({
  type: {
    type: String as PropType<"button" | "submit" | "reset">,
    default: "button",
  },
  text: {
    type: String,
    required: true,
  },
  onClick: {
    type: Function as PropType<() => void>,
    default: () => {},
  },
});

function handleClick() {
  if (props.onClick) {
    props.onClick(); // 调用传入的点击事件
  }
}
</script>

<template>
  <button :type="type" @click="handleClick">
    {{ text }}
  </button>
</template>

<style scoped>
button {
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}
</style>

然后进入到 packages/ui

运行pnpm install

apps

apps/web

  • 先安装 vue
ts
npm create vite@latest
  • tsconfig.node.json
ts

{
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
    "target": "ES2023",
    "lib": ["ES2023"],
    "module": "ESNext",
    "types": ["node"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "moduleDetection": "force",
    "noEmit": true,

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "erasableSyntaxOnly": true,
    "noFallthroughCasesInSwitch": true,
  },
  "include": ["vite.config.ts"]
}
  • App.vue 里面引入
html
<script setup lang="ts">
  import { VButton } from "@repo/vueui";
  import { ref, type Ref } from "vue";
  const msg: Ref<string> = ref("点击我");
  const typeName: Ref<"reset" | "button" | "submit" | undefined> = ref("reset");
  function handleClick() {
    window.alert("出来吧");
  }
</script>

<template>
  <div>
    <VButton :text="msg" :type="typeName" :onClick="handleClick"></VButton>
  </div>
</template>

<style scoped></style>