diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 8a005721ac..5675d96c2f 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -320,6 +320,11 @@ jobs: node-version: 24 command: | vp check --fix + - name: vite-plus-monorepo-overrides + node-version: 24 + command: | + vp check --fix + vp run verify - name: varlet node-version: 22 command: | diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 932500090b..04c098330c 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -3,8 +3,8 @@ import { resolve } from 'node:path'; import type { VoidZeroThemeConfig } from '@voidzero-dev/vitepress-theme'; import { extendConfig } from '@voidzero-dev/vitepress-theme/config'; import { defineConfig, type HeadConfig } from 'vitepress'; +import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-icons'; import { withMermaid } from 'vitepress-plugin-mermaid'; -import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-icons' const taskRunnerGuideItems = [ { @@ -72,6 +72,7 @@ const guideSidebar = [ { text: 'IDE Integration', link: '/guide/ide-integration' }, { text: 'CI', link: '/guide/ci' }, { text: 'Commit Hooks', link: '/guide/commit-hooks' }, + { text: 'Monorepo Guide', link: '/guide/monorepo' }, { text: 'Troubleshooting', link: '/guide/troubleshooting' }, ], }, @@ -126,9 +127,9 @@ export default extendConfig( plugins: [ groupIconVitePlugin({ customIcon: { - tsdown: 'https://tsdown.dev/tsdown.svg' - } - }) + tsdown: 'https://tsdown.dev/tsdown.svg', + }, + }), ], }, themeConfig: { @@ -196,24 +197,24 @@ export default extendConfig( footer: { copyright: `© ${new Date().getFullYear()} VoidZero Inc. and Vite+ contributors.`, nav: [ - { - title: "Company", + { + title: 'Company', items: [ - { text: "VoidZero", link: "https://voidzero.dev" }, - { text: "Vite", link: "https://vite.dev" }, - { text: "Vitest", link: "https://vitest.dev" }, - { text: "Rolldown", link: "https://rolldown.rs" }, - { text: "Oxc", link: "https://oxc.rs" }, + { text: 'VoidZero', link: 'https://voidzero.dev' }, + { text: 'Vite', link: 'https://vite.dev' }, + { text: 'Vitest', link: 'https://vitest.dev' }, + { text: 'Rolldown', link: 'https://rolldown.rs' }, + { text: 'Oxc', link: 'https://oxc.rs' }, ], }, ], - social: [ - { icon: "github", link: "https://github.com/voidzero-dev/vite-plus" }, - { icon: "x", link: "https://x.com/voidzerodev" }, - { icon: "discord", link: "https://discord.gg/cC6TEVFKSx" }, - { icon: "bluesky", link: "https://bsky.app/profile/voidzero.dev" }, - ], - } + social: [ + { icon: 'github', link: 'https://github.com/voidzero-dev/vite-plus' }, + { icon: 'x', link: 'https://x.com/voidzerodev' }, + { icon: 'discord', link: 'https://discord.gg/cC6TEVFKSx' }, + { icon: 'bluesky', link: 'https://bsky.app/profile/voidzero.dev' }, + ], + }, }, transformHead({ page, pageData }) { const url = 'https://viteplus.dev/' + page.replace(/\.md$/, '').replace(/index$/, ''); @@ -249,7 +250,7 @@ export default extendConfig( }, markdown: { config(md) { - md.use(groupIconMdPlugin) + md.use(groupIconMdPlugin); }, }, }), diff --git a/docs/.vitepress/theme/components/Footer.vue b/docs/.vitepress/theme/components/Footer.vue index 70ff6a950f..03a1ce344a 100644 --- a/docs/.vitepress/theme/components/Footer.vue +++ b/docs/.vitepress/theme/components/Footer.vue @@ -6,19 +6,18 @@ sponsorLink="https://github.com/voidzero-dev/vite-plus/blob/main/CONTRIBUTING.md" />
-
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 329cef7bfd..9d50a93e76 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -4,7 +4,7 @@ import type { Theme } from 'vitepress'; import Layout from './Layout.vue'; import './styles.css'; -import 'virtual:group-icons.css' +import 'virtual:group-icons.css'; export default { extends: BaseTheme, diff --git a/docs/config/fmt.md b/docs/config/fmt.md index d034398509..254ed73b2a 100644 --- a/docs/config/fmt.md +++ b/docs/config/fmt.md @@ -16,3 +16,5 @@ export default defineConfig({ }, }); ``` + +For package-specific formatting settings in a workspace, use [`fmt.overrides`](/guide/monorepo#format-overrides) from the root `vite.config.ts`. diff --git a/docs/config/lint.md b/docs/config/lint.md index 718d2d7d9f..3d097b6603 100644 --- a/docs/config/lint.md +++ b/docs/config/lint.md @@ -22,3 +22,5 @@ export default defineConfig({ ``` We recommend enabling both `options.typeAware` and `options.typeCheck` so `vp lint` and `vp check` can use the full type-aware path. + +For package-specific lint rules in a workspace, use [`lint.overrides`](/guide/monorepo#root-config-with-overrides) from the root `vite.config.ts`. diff --git a/docs/guide/monorepo.md b/docs/guide/monorepo.md new file mode 100644 index 0000000000..def5b9aa08 --- /dev/null +++ b/docs/guide/monorepo.md @@ -0,0 +1,176 @@ +# Monorepo + +Vite+ supports monorepos with `vite.config.ts` at the root. You can define the defaults for `lint`, `fmt`, etc. at the root, and use `overrides` to apply package-specific lint and format settings. + +Because `vite.config.ts` is just JavaScript, you can choose to put your entire config into this file or compose it using regular JavaScript imports. You can still have separate `vite.config.ts` files in each package for the Vite, Vitest, framework or runtime configuration. + +## Root Config With Overrides + +Use `lint.overrides` for Oxlint rules that only apply to some packages: + +```ts [vite.config.ts] +import { defineConfig } from 'vite-plus'; + +export default defineConfig({ + lint: { + plugins: ['typescript'], + options: { + typeAware: true, + typeCheck: true, + }, + rules: { + 'no-console': ['error', { allow: ['warn', 'error'] }], + }, + overrides: [ + { + files: ['apps/web/**', 'packages/ui/**'], + plugins: ['typescript', 'react'], + rules: { + 'react/self-closing-comp': 'error', + }, + }, + { + files: ['apps/api/**'], + env: { + node: true, + }, + rules: { + 'no-console': 'off', + }, + }, + { + files: ['**/*.test.ts', '**/*.spec.ts'], + plugins: ['typescript', 'vitest'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + 'vitest/no-disabled-tests': 'error', + }, + }, + ], + }, +}); +``` + +Globs are resolved from the root `vite.config.ts`, so use workspace paths such as `apps/web/**`, `apps/api/**`, and `packages/ui/**`. + +::: tip +When a `lint.overrides` entry sets `plugins`, that list replaces the base `lint.plugins` list for matched files. Include every plugin needed by that file group, such as `['typescript', 'react']`. Omit `plugins` only when the override should inherit the base list unchanged. +::: + +## Format Overrides + +Use `fmt.overrides` for file or package-specific Oxfmt options. Formatter overrides put their settings under `options`: + +```ts [vite.config.ts] +import { defineConfig } from 'vite-plus'; + +export default defineConfig({ + fmt: { + singleQuote: true, + semi: true, + overrides: [ + { + files: ['apps/api/**'], + options: { + printWidth: 120, + }, + }, + { + files: ['**/*.md'], + options: { + proseWrap: 'always', + }, + }, + ], + }, +}); +``` + +## Composing Configuration Files + +You can split configuration across your repository and compose them using JavaScript imports. Export JavaScript objects from nearby files or packages, import them in the root config, and merge them into the matching override. + +```ts [tooling/lint/react.ts] +import type { OxlintOverride } from 'vite-plus/lint'; + +export const reactLint = { + plugins: ['typescript', 'react'], + rules: { + 'react/self-closing-comp': 'error', + }, +} satisfies Omit; +``` + +```ts [tooling/lint/node.ts] +import type { OxlintOverride } from 'vite-plus/lint'; + +export const nodeLint = { + env: { + node: true, + }, + rules: { + 'no-console': 'off', + }, +} satisfies Omit; +``` + +```ts [vite.config.ts] +import { defineConfig } from 'vite-plus'; + +import { nodeLint } from './tooling/lint/node'; +import { reactLint } from './tooling/lint/react'; + +export default defineConfig({ + lint: { + plugins: ['typescript'], + options: { + typeAware: true, + typeCheck: true, + }, + overrides: [ + { + files: ['apps/web/**', 'packages/ui/**'], + ...reactLint, + }, + { + files: ['apps/api/**'], + ...nodeLint, + }, + ], + }, +}); +``` + +This keeps the behavior centralized while letting each team or package own the pieces of config it needs. + +## App Commands + +The root `vite.config.ts` is most valuable for shared linting, formatting, staged checks, and task definitions. For project-specific development, build, and test behavior, use the setup that best matches each app: + +- Pass a folder to built-in Vite commands when you want to target one app: + +```bash +vp dev apps/web +vp build apps/web +``` + +- Keep package-specific scripts in each package when the command differs per app: + +```json [apps/api/package.json] +{ + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc -p tsconfig.json" + } +} +``` + +- Run scripts across the workspace with `vp run`: + +```bash +vp run -r build +vp run -r --parallel dev +vp run --filter ./apps/web build +``` + +See the [Run guide](/guide/run) for recursive, parallel, filtered, and cached workspace tasks. diff --git a/ecosystem-ci/repo.json b/ecosystem-ci/repo.json index e762c5d1e1..e184aaabf8 100644 --- a/ecosystem-ci/repo.json +++ b/ecosystem-ci/repo.json @@ -120,6 +120,12 @@ "hash": "6192f60653c124ae068efaf5d7d0a4134c95edbd", "forceFreshMigration": true }, + "vite-plus-monorepo-overrides": { + "repository": "https://github.com/why-reproductions-are-required/vite-plus-monorepo-overrides.git", + "branch": "main", + "hash": "813ddcf84ff2f9da34e4fe5bcaaade05c64d27f8", + "forceFreshMigration": true + }, "varlet": { "repository": "https://github.com/varletjs/varlet.git", "branch": "dev", diff --git a/vite.config.ts b/vite.config.ts index 093cde7a97..5b208c4458 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -71,6 +71,7 @@ export default defineConfig({ 'packages/cli/snap-tests/fmt-ignore-patterns/src/ignored', 'packages/cli/snap-tests-global/migration-lint-staged-ts-config', 'docs/**', + '!docs/.vitepress/**', 'ecosystem-ci/*/**', 'packages/test/**.cjs', 'packages/test/**.cts',