diff --git a/.eslintignore b/.eslintignore
index 1905aa4a8..a0c203ada 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -4,5 +4,8 @@
/tests/fixtures
/tests/integrations/eslint-plugin-import
-!.vuepress
-/docs/.vuepress/dist
+!.vitepress
+/docs/.vitepress/dist
+/docs/.vitepress/build-system/shim/eslint.mjs
+/docs/.vitepress/build-system/shim/assert.mjs
+/docs/.vitepress/.temp
diff --git a/.gitignore b/.gitignore
index e1401b951..06fb2db3b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,5 +7,8 @@
/test.*
yarn.lock
yarn-error.log
-docs/.vuepress/dist
+/docs/.vitepress/dist
+/docs/.vitepress/build-system/shim/eslint.mjs
+/docs/.vitepress/build-system/shim/assert.mjs
+/docs/.vitepress/.temp
typings/eslint/lib/rules
diff --git a/docs/.vitepress/build-system/build.ts b/docs/.vitepress/build-system/build.ts
new file mode 100644
index 000000000..698b0809d
--- /dev/null
+++ b/docs/.vitepress/build-system/build.ts
@@ -0,0 +1,66 @@
+/**
+ * Pre-build cjs packages that cannot be bundled well.
+ */
+import esbuild from 'esbuild'
+import path from 'path'
+import fs from 'fs'
+import { fileURLToPath } from 'url'
+
+const dirname = path.dirname(
+ fileURLToPath(
+ // @ts-expect-error -- Cannot change `module` option
+ import.meta.url
+ )
+)
+
+build(
+ path.join(dirname, './src/eslint.mjs'),
+ path.join(dirname, './shim/eslint.mjs'),
+ ['path', 'assert', 'util']
+)
+build(
+ path.join(dirname, '../../../node_modules/assert'),
+ path.join(dirname, './shim/assert.mjs'),
+ ['path']
+)
+
+function build(input: string, out: string, injects: string[] = []) {
+ // eslint-disable-next-line no-console -- ignore
+ console.log(`build@ ${input}`)
+ let code = bundle(input, injects)
+ code = transform(code, injects)
+ fs.mkdirSync(path.dirname(out), { recursive: true })
+ fs.writeFileSync(out, code, 'utf8')
+}
+
+function bundle(entryPoint: string, externals: string[]) {
+ const result = esbuild.buildSync({
+ entryPoints: [entryPoint],
+ format: 'esm',
+ bundle: true,
+ external: externals,
+ write: false,
+ inject: [path.join(dirname, './src/process-shim.mjs')]
+ })
+
+ return `${result.outputFiles[0].text}`
+}
+
+function transform(code: string, injects: string[]) {
+ const newCode = code.replace(/"[a-z]+" = "[a-z]+";/u, '')
+ return `
+${injects
+ .map(
+ (inject) =>
+ `import $inject_${inject.replace(/-/gu, '_')}$ from '${inject}';`
+ )
+ .join('\n')}
+const $_injects_$ = {${injects
+ .map((inject) => `${inject.replace(/-/gu, '_')}:$inject_${inject}$`)
+ .join(',\n')}};
+function require(module, ...args) {
+ return $_injects_$[module] || {}
+}
+${newCode}
+`
+}
diff --git a/docs/.vitepress/build-system/shim/globby.mjs b/docs/.vitepress/build-system/shim/globby.mjs
new file mode 100644
index 000000000..57d1de9f1
--- /dev/null
+++ b/docs/.vitepress/build-system/shim/globby.mjs
@@ -0,0 +1,2 @@
+export {}
+export default {}
diff --git a/docs/.vitepress/build-system/shim/path.mjs b/docs/.vitepress/build-system/shim/path.mjs
new file mode 100644
index 000000000..544792b00
--- /dev/null
+++ b/docs/.vitepress/build-system/shim/path.mjs
@@ -0,0 +1,38 @@
+// @ts-nocheck
+export const sep = '/'
+export function basename(path, ext) {
+ const b = (/[^\/]*$/u.exec(path) || [''])[0]
+ return ext && b.endsWith(ext) ? b.slice(0, -ext.length) : b
+}
+export function extname(path) {
+ return (/[^.\/]*$/u.exec(path) || [''])[0]
+}
+export function isAbsolute() {
+ return false
+}
+export function join(...args) {
+ return args.length > 0 ? normalize(args.join('/')) : '.'
+}
+
+function normalize(path) {
+ const result = []
+ for (const part of path.replace(/\/+/gu, '/').split('/')) {
+ if (part === '..') {
+ if (result[0] && result[0] !== '..' && result[0] !== '.') result.shift()
+ } else if (part === '.' && result.length > 0) {
+ // noop
+ } else {
+ result.unshift(part)
+ }
+ }
+ return result.reverse().join('/')
+}
+const posix = {
+ sep,
+ basename,
+ extname,
+ isAbsolute,
+ join
+}
+posix.posix = posix
+export default posix
diff --git a/docs/.vitepress/build-system/src/eslint.mjs b/docs/.vitepress/build-system/src/eslint.mjs
new file mode 100644
index 000000000..6604e0bb4
--- /dev/null
+++ b/docs/.vitepress/build-system/src/eslint.mjs
@@ -0,0 +1,5 @@
+// @ts-nocheck
+import * as all from '../../../../node_modules/eslint/lib/linter/linter.js'
+const Linter = all.Linter
+export { Linter }
+export default { Linter }
diff --git a/docs/.vitepress/build-system/src/process-shim.mjs b/docs/.vitepress/build-system/src/process-shim.mjs
new file mode 100644
index 000000000..6b75bf56e
--- /dev/null
+++ b/docs/.vitepress/build-system/src/process-shim.mjs
@@ -0,0 +1,10 @@
+/* globals window */
+// @ts-nocheck
+export const process = {
+ env: {},
+ cwd: () => '',
+ stdout: {}
+}
+if (typeof window !== 'undefined') {
+ window.process = process
+}
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
new file mode 100644
index 000000000..9990ed7bd
--- /dev/null
+++ b/docs/.vitepress/config.ts
@@ -0,0 +1,222 @@
+import type { DefaultTheme } from 'vitepress'
+import { defineConfig } from 'vitepress'
+import { BUNDLED_LANGUAGES } from 'shiki'
+import path from 'path'
+import { fileURLToPath } from 'url'
+import rules from '../../tools/lib/rules'
+import { viteCommonjs, vitePluginRequireResolve } from './vite-plugin'
+
+// Pre-build cjs packages that cannot be bundled well.
+import './build-system/build'
+
+const dirname = path.dirname(
+ fileURLToPath(
+ // @ts-expect-error -- Cannot change `module` option
+ import.meta.url
+ )
+)
+
+// Include `json5` as alias for jsonc
+const jsonc = BUNDLED_LANGUAGES.find((lang) => lang.id === 'jsonc')
+if (jsonc) {
+ jsonc.aliases = [...(jsonc.aliases ?? []), 'json5']
+}
+
+const uncategorizedRules = rules.filter(
+ (rule) =>
+ !rule.meta.docs.categories &&
+ !rule.meta.docs.extensionRule &&
+ !rule.meta.deprecated
+)
+const uncategorizedExtensionRule = rules.filter(
+ (rule) =>
+ !rule.meta.docs.categories &&
+ rule.meta.docs.extensionRule &&
+ !rule.meta.deprecated
+)
+const deprecatedRules = rules.filter((rule) => rule.meta.deprecated)
+
+const sidebarCategories = [
+ { title: 'Base Rules', categoryIds: ['base'] },
+ {
+ title: 'Priority A: Essential',
+ categoryIds: ['vue3-essential', 'essential']
+ },
+ {
+ title: 'Priority A: Essential for Vue.js 3.x',
+ categoryIds: ['vue3-essential']
+ },
+ { title: 'Priority A: Essential for Vue.js 2.x', categoryIds: ['essential'] },
+ {
+ title: 'Priority B: Strongly Recommended',
+ categoryIds: ['vue3-strongly-recommended', 'strongly-recommended']
+ },
+ {
+ title: 'Priority B: Strongly Recommended for Vue.js 3.x',
+ categoryIds: ['vue3-strongly-recommended']
+ },
+ {
+ title: 'Priority B: Strongly Recommended for Vue.js 2.x',
+ categoryIds: ['strongly-recommended']
+ },
+ {
+ title: 'Priority C: Recommended',
+ categoryIds: ['vue3-recommended', 'recommended']
+ },
+ {
+ title: 'Priority C: Recommended for Vue.js 3.x',
+ categoryIds: ['vue3-recommended']
+ },
+ {
+ title: 'Priority C: Recommended for Vue.js 2.x',
+ categoryIds: ['recommended']
+ }
+]
+
+const categorizedRules: DefaultTheme.SidebarGroup[] = []
+for (const { title, categoryIds } of sidebarCategories) {
+ const categoryRules = rules
+ .filter((rule) => rule.meta.docs.categories && !rule.meta.deprecated)
+ .filter((rule) =>
+ categoryIds.every((categoryId) =>
+ rule.meta.docs.categories.includes(categoryId)
+ )
+ )
+ const children: DefaultTheme.SidebarItem[] = categoryRules
+ .filter(({ ruleId }) => {
+ const exists = categorizedRules.some(({ items }) =>
+ items.some(({ text: alreadyRuleId }) => alreadyRuleId === ruleId)
+ )
+ return !exists
+ })
+ .map(({ ruleId, name }) => {
+ return {
+ text: ruleId,
+ link: `/rules/${name}`
+ }
+ })
+
+ if (children.length === 0) {
+ continue
+ }
+ categorizedRules.push({
+ text: title,
+ collapsible: false,
+ items: children
+ })
+}
+
+const extraCategories: DefaultTheme.SidebarGroup[] = []
+if (uncategorizedRules.length > 0) {
+ extraCategories.push({
+ text: 'Uncategorized',
+ collapsible: false,
+ items: uncategorizedRules.map(({ ruleId, name }) => ({
+ text: ruleId,
+ link: `/rules/${name}`
+ }))
+ })
+}
+if (uncategorizedExtensionRule.length > 0) {
+ extraCategories.push({
+ text: 'Extension Rules',
+ collapsible: false,
+ items: uncategorizedExtensionRule.map(({ ruleId, name }) => ({
+ text: ruleId,
+ link: `/rules/${name}`
+ }))
+ })
+}
+if (deprecatedRules.length > 0) {
+ extraCategories.push({
+ text: 'Deprecated',
+ collapsible: false,
+ items: deprecatedRules.map(({ ruleId, name }) => ({
+ text: ruleId,
+ link: `/rules/${name}`
+ }))
+ })
+}
+
+export default defineConfig({
+ base: '/',
+ title: 'eslint-plugin-vue',
+ description: 'Official ESLint plugin for Vue.js',
+ head: [['link', { rel: 'icon', href: '/favicon.png' }]],
+
+ vite: {
+ publicDir: path.resolve(dirname, './public'),
+ plugins: [vitePluginRequireResolve(), viteCommonjs()],
+ resolve: {
+ alias: {
+ eslint: path.join(dirname, './build-system/shim/eslint.mjs'),
+ assert: path.join(dirname, './build-system/shim/assert.mjs'),
+ path: path.join(dirname, './build-system/shim/path.mjs'),
+
+ tslib: path.join(dirname, '../../node_modules/tslib/tslib.es6.js'),
+ globby: path.join(dirname, './build-system/shim/globby.mjs')
+ }
+ },
+ define: {
+ 'process.env.NODE_DEBUG': 'false',
+ 'require.cache': '{}'
+ }
+ },
+
+ lastUpdated: true,
+ themeConfig: {
+ editLink: {
+ pattern:
+ 'https://github.com/vuejs/eslint-plugin-vue/edit/master/docs/:path'
+ },
+ socialLinks: [
+ {
+ icon: 'github',
+ link: 'https://github.com/vuejs/eslint-plugin-vue'
+ }
+ ],
+
+ nav: [
+ { text: 'User Guide', link: '/user-guide/' },
+ { text: 'Developer Guide', link: '/developer-guide/' },
+ { text: 'Rules', link: '/rules/' },
+ {
+ text: 'Demo',
+ link: 'https://ota-meshi.github.io/eslint-plugin-vue-demo/'
+ }
+ ],
+
+ sidebar: {
+ '/rules/': [
+ {
+ text: 'Rules',
+ items: [{ text: 'Available Rules', link: '/rules/' }]
+ },
+
+ // Rules in each category.
+ ...categorizedRules,
+
+ // Rules in no category.
+ ...extraCategories
+ ],
+
+ '/': [
+ {
+ text: 'Guide',
+ items: [
+ { text: 'Introduction', link: '/' },
+ { text: 'User Guide', link: '/user-guide/' },
+ { text: 'Developer Guide', link: '/developer-guide/' },
+ { text: 'Rules', link: '/rules/' }
+ ]
+ }
+ ]
+ },
+
+ algolia: {
+ appId: '2L4MGZSULB',
+ apiKey: 'fdf57932b27a6c230d01a890492ab76d',
+ indexName: 'eslint-plugin-vue'
+ }
+ }
+})
diff --git a/docs/.vuepress/public/favicon.png b/docs/.vitepress/public/favicon.png
similarity index 100%
rename from docs/.vuepress/public/favicon.png
rename to docs/.vitepress/public/favicon.png
diff --git a/docs/.vitepress/theme/Layout.vue b/docs/.vitepress/theme/Layout.vue
new file mode 100644
index 000000000..3be00694b
--- /dev/null
+++ b/docs/.vitepress/theme/Layout.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
diff --git a/docs/.vuepress/components/eslint-code-block.vue b/docs/.vitepress/theme/components/eslint-code-block.vue
similarity index 78%
rename from docs/.vuepress/components/eslint-code-block.vue
rename to docs/.vitepress/theme/components/eslint-code-block.vue
index b95e0d070..77d4fd7cf 100644
--- a/docs/.vuepress/components/eslint-code-block.vue
+++ b/docs/.vitepress/theme/components/eslint-code-block.vue
@@ -3,7 +3,7 @@
diff --git a/docs/.vuepress/components/rules-table.vue b/docs/.vitepress/theme/components/rules-table.vue
similarity index 95%
rename from docs/.vuepress/components/rules-table.vue
rename to docs/.vitepress/theme/components/rules-table.vue
index 29d31c4c6..c517d4591 100644
--- a/docs/.vuepress/components/rules-table.vue
+++ b/docs/.vitepress/theme/components/rules-table.vue
@@ -77,10 +77,10 @@ export default {