diff --git a/README.md b/README.md
index 31c3140..1da1806 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,13 @@ import {
} from '@vue/eslint-config-typescript'
configureVueProject({
+ // Whether to parse TypeScript syntax in Vue templates.
+ // Defaults to `true`.
+ // Setting it to `false` could improve performance.
+ // But TypeScript syntax in Vue templates will then lead to syntax errors.
+ // Also, type-aware rules won't be applied to expressions in templates in that case.
+ tsSyntaxInTemplates: true,
+
// Optional: specify the script langs in `.vue` files
// Defaults to `['ts']`.
scriptLangs: [
diff --git a/examples/disable-ts-in-templates/.gitignore b/examples/disable-ts-in-templates/.gitignore
new file mode 100644
index 0000000..8ee54e8
--- /dev/null
+++ b/examples/disable-ts-in-templates/.gitignore
@@ -0,0 +1,30 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo
diff --git a/examples/disable-ts-in-templates/.vscode/extensions.json b/examples/disable-ts-in-templates/.vscode/extensions.json
new file mode 100644
index 0000000..64db0b2
--- /dev/null
+++ b/examples/disable-ts-in-templates/.vscode/extensions.json
@@ -0,0 +1,6 @@
+{
+ "recommendations": [
+ "Vue.volar",
+ "dbaeumer.vscode-eslint"
+ ]
+}
diff --git a/examples/disable-ts-in-templates/README.md b/examples/disable-ts-in-templates/README.md
new file mode 100644
index 0000000..ebf2961
--- /dev/null
+++ b/examples/disable-ts-in-templates/README.md
@@ -0,0 +1,39 @@
+# disable-ts-in-templates
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
+
+## Type Support for `.vue` Imports in TS
+
+TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vitejs.dev/config/).
+
+## Project Setup
+
+```sh
+npm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+npm run dev
+```
+
+### Type-Check, Compile and Minify for Production
+
+```sh
+npm run build
+```
+
+### Lint with [ESLint](https://eslint.org/)
+
+```sh
+npm run lint
+```
diff --git a/examples/disable-ts-in-templates/env.d.ts b/examples/disable-ts-in-templates/env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/examples/disable-ts-in-templates/env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/examples/disable-ts-in-templates/eslint.config.js b/examples/disable-ts-in-templates/eslint.config.js
new file mode 100644
index 0000000..c6b63eb
--- /dev/null
+++ b/examples/disable-ts-in-templates/eslint.config.js
@@ -0,0 +1,25 @@
+import pluginVue from 'eslint-plugin-vue'
+import {
+ configureVueProject,
+ defineConfigWithVueTs,
+ vueTsConfigs,
+} from '@vue/eslint-config-typescript'
+
+configureVueProject({
+ tsSyntaxInTemplates: false,
+})
+
+export default defineConfigWithVueTs(
+ {
+ name: 'app/files-to-lint',
+ files: ['**/*.ts', '**/*.mts', '**/*.vue'],
+ },
+
+ {
+ name: 'app/files-to-ignore',
+ ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
+ },
+
+ pluginVue.configs['flat/essential'],
+ vueTsConfigs.recommended,
+)
diff --git a/examples/disable-ts-in-templates/index.html b/examples/disable-ts-in-templates/index.html
new file mode 100644
index 0000000..a888544
--- /dev/null
+++ b/examples/disable-ts-in-templates/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite App
+
+
+
+
+
+
diff --git a/examples/disable-ts-in-templates/package.json b/examples/disable-ts-in-templates/package.json
new file mode 100644
index 0000000..f365a06
--- /dev/null
+++ b/examples/disable-ts-in-templates/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "disable-ts-in-templates",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "run-p type-check \"build-only {@}\" --",
+ "preview": "vite preview",
+ "build-only": "vite build",
+ "type-check": "vue-tsc --build --force",
+ "lint": "eslint . --fix"
+ },
+ "dependencies": {
+ "vue": "^3.5.13"
+ },
+ "devDependencies": {
+ "@tsconfig/node20": "^20.1.4",
+ "@types/node": "^20.17.12",
+ "@vitejs/plugin-vue": "^5.2.1",
+ "@vue/eslint-config-typescript": "workspace:*",
+ "@vue/tsconfig": "^0.7.0",
+ "eslint": "^9.18.0",
+ "eslint-plugin-vue": "^9.32.0",
+ "npm-run-all2": "^7.0.2",
+ "typescript": "~5.7.3",
+ "vite": "^6.0.7",
+ "vue-tsc": "^2.2.0"
+ }
+}
diff --git a/examples/disable-ts-in-templates/public/favicon.ico b/examples/disable-ts-in-templates/public/favicon.ico
new file mode 100644
index 0000000..df36fcf
Binary files /dev/null and b/examples/disable-ts-in-templates/public/favicon.ico differ
diff --git a/examples/disable-ts-in-templates/src/App.vue b/examples/disable-ts-in-templates/src/App.vue
new file mode 100644
index 0000000..d05208d
--- /dev/null
+++ b/examples/disable-ts-in-templates/src/App.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/disable-ts-in-templates/src/assets/base.css b/examples/disable-ts-in-templates/src/assets/base.css
new file mode 100644
index 0000000..8816868
--- /dev/null
+++ b/examples/disable-ts-in-templates/src/assets/base.css
@@ -0,0 +1,86 @@
+/* color palette from */
+:root {
+ --vt-c-white: #ffffff;
+ --vt-c-white-soft: #f8f8f8;
+ --vt-c-white-mute: #f2f2f2;
+
+ --vt-c-black: #181818;
+ --vt-c-black-soft: #222222;
+ --vt-c-black-mute: #282828;
+
+ --vt-c-indigo: #2c3e50;
+
+ --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
+ --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
+ --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
+ --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
+
+ --vt-c-text-light-1: var(--vt-c-indigo);
+ --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
+ --vt-c-text-dark-1: var(--vt-c-white);
+ --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
+}
+
+/* semantic color variables for this project */
+:root {
+ --color-background: var(--vt-c-white);
+ --color-background-soft: var(--vt-c-white-soft);
+ --color-background-mute: var(--vt-c-white-mute);
+
+ --color-border: var(--vt-c-divider-light-2);
+ --color-border-hover: var(--vt-c-divider-light-1);
+
+ --color-heading: var(--vt-c-text-light-1);
+ --color-text: var(--vt-c-text-light-1);
+
+ --section-gap: 160px;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --color-background: var(--vt-c-black);
+ --color-background-soft: var(--vt-c-black-soft);
+ --color-background-mute: var(--vt-c-black-mute);
+
+ --color-border: var(--vt-c-divider-dark-2);
+ --color-border-hover: var(--vt-c-divider-dark-1);
+
+ --color-heading: var(--vt-c-text-dark-1);
+ --color-text: var(--vt-c-text-dark-2);
+ }
+}
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+ margin: 0;
+ font-weight: normal;
+}
+
+body {
+ min-height: 100vh;
+ color: var(--color-text);
+ background: var(--color-background);
+ transition:
+ color 0.5s,
+ background-color 0.5s;
+ line-height: 1.6;
+ font-family:
+ Inter,
+ -apple-system,
+ BlinkMacSystemFont,
+ 'Segoe UI',
+ Roboto,
+ Oxygen,
+ Ubuntu,
+ Cantarell,
+ 'Fira Sans',
+ 'Droid Sans',
+ 'Helvetica Neue',
+ sans-serif;
+ font-size: 15px;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
diff --git a/examples/disable-ts-in-templates/src/assets/logo.svg b/examples/disable-ts-in-templates/src/assets/logo.svg
new file mode 100644
index 0000000..7565660
--- /dev/null
+++ b/examples/disable-ts-in-templates/src/assets/logo.svg
@@ -0,0 +1 @@
+
diff --git a/examples/disable-ts-in-templates/src/assets/main.css b/examples/disable-ts-in-templates/src/assets/main.css
new file mode 100644
index 0000000..36fb845
--- /dev/null
+++ b/examples/disable-ts-in-templates/src/assets/main.css
@@ -0,0 +1,35 @@
+@import './base.css';
+
+#app {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ font-weight: normal;
+}
+
+a,
+.green {
+ text-decoration: none;
+ color: hsla(160, 100%, 37%, 1);
+ transition: 0.4s;
+ padding: 3px;
+}
+
+@media (hover: hover) {
+ a:hover {
+ background-color: hsla(160, 100%, 37%, 0.2);
+ }
+}
+
+@media (min-width: 1024px) {
+ body {
+ display: flex;
+ place-items: center;
+ }
+
+ #app {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ padding: 0 2rem;
+ }
+}
diff --git a/examples/disable-ts-in-templates/src/components/HelloWorld.vue b/examples/disable-ts-in-templates/src/components/HelloWorld.vue
new file mode 100644
index 0000000..e1a721c
--- /dev/null
+++ b/examples/disable-ts-in-templates/src/components/HelloWorld.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
{{ msg }}
+
+ You’ve successfully created a project with
+ Vite +
+ Vue 3 .
+
+
+
+
+
diff --git a/examples/disable-ts-in-templates/src/components/TheWelcome.vue b/examples/disable-ts-in-templates/src/components/TheWelcome.vue
new file mode 100644
index 0000000..49d8f73
--- /dev/null
+++ b/examples/disable-ts-in-templates/src/components/TheWelcome.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+ Documentation
+
+ Vue’s
+ official documentation
+ provides you with all information you need to get started.
+
+
+
+
+
+
+ Tooling
+
+ This project is served and bundled with
+ Vite . The
+ recommended IDE setup is
+ VSCode +
+ Volar . If
+ you need to test your components and web pages, check out
+ Cypress and
+ Cypress Component Testing .
+
+
+
+ More instructions are available in README.md
.
+
+
+
+
+
+
+ Ecosystem
+
+ Get official tools and libraries for your project:
+ Pinia ,
+ Vue Router ,
+ Vue Test Utils , and
+ Vue Dev Tools . If
+ you need more resources, we suggest paying
+ Awesome Vue
+ a visit.
+
+
+
+
+
+
+ Community
+
+ Got stuck? Ask your question on
+ Vue Land , our official
+ Discord server, or
+ StackOverflow . You should also subscribe to
+ our mailing list and follow
+ the official
+ @vuejs
+ twitter account for latest news in the Vue world.
+
+
+
+
+
+
+ Support Vue
+
+ As an independent project, Vue relies on community backing for its sustainability. You can help
+ us by
+ becoming a sponsor .
+
+
diff --git a/examples/disable-ts-in-templates/src/components/WelcomeItem.vue b/examples/disable-ts-in-templates/src/components/WelcomeItem.vue
new file mode 100644
index 0000000..6d7086a
--- /dev/null
+++ b/examples/disable-ts-in-templates/src/components/WelcomeItem.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
diff --git a/examples/disable-ts-in-templates/src/components/icons/IconCommunity.vue b/examples/disable-ts-in-templates/src/components/icons/IconCommunity.vue
new file mode 100644
index 0000000..2dc8b05
--- /dev/null
+++ b/examples/disable-ts-in-templates/src/components/icons/IconCommunity.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/examples/disable-ts-in-templates/src/components/icons/IconDocumentation.vue b/examples/disable-ts-in-templates/src/components/icons/IconDocumentation.vue
new file mode 100644
index 0000000..6d4791c
--- /dev/null
+++ b/examples/disable-ts-in-templates/src/components/icons/IconDocumentation.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/examples/disable-ts-in-templates/src/components/icons/IconEcosystem.vue b/examples/disable-ts-in-templates/src/components/icons/IconEcosystem.vue
new file mode 100644
index 0000000..c3a4f07
--- /dev/null
+++ b/examples/disable-ts-in-templates/src/components/icons/IconEcosystem.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/examples/disable-ts-in-templates/src/components/icons/IconSupport.vue b/examples/disable-ts-in-templates/src/components/icons/IconSupport.vue
new file mode 100644
index 0000000..7452834
--- /dev/null
+++ b/examples/disable-ts-in-templates/src/components/icons/IconSupport.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/examples/disable-ts-in-templates/src/components/icons/IconTooling.vue b/examples/disable-ts-in-templates/src/components/icons/IconTooling.vue
new file mode 100644
index 0000000..660598d
--- /dev/null
+++ b/examples/disable-ts-in-templates/src/components/icons/IconTooling.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/examples/disable-ts-in-templates/src/main.ts b/examples/disable-ts-in-templates/src/main.ts
new file mode 100644
index 0000000..0ac3a5f
--- /dev/null
+++ b/examples/disable-ts-in-templates/src/main.ts
@@ -0,0 +1,6 @@
+import './assets/main.css'
+
+import { createApp } from 'vue'
+import App from './App.vue'
+
+createApp(App).mount('#app')
diff --git a/examples/disable-ts-in-templates/tsconfig.app.json b/examples/disable-ts-in-templates/tsconfig.app.json
new file mode 100644
index 0000000..e14c754
--- /dev/null
+++ b/examples/disable-ts-in-templates/tsconfig.app.json
@@ -0,0 +1,14 @@
+{
+ "extends": "@vue/tsconfig/tsconfig.dom.json",
+ "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
+ "exclude": ["src/**/__tests__/*"],
+ "compilerOptions": {
+ "composite": true,
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/examples/disable-ts-in-templates/tsconfig.json b/examples/disable-ts-in-templates/tsconfig.json
new file mode 100644
index 0000000..66b5e57
--- /dev/null
+++ b/examples/disable-ts-in-templates/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "files": [],
+ "references": [
+ {
+ "path": "./tsconfig.node.json"
+ },
+ {
+ "path": "./tsconfig.app.json"
+ }
+ ]
+}
diff --git a/examples/disable-ts-in-templates/tsconfig.node.json b/examples/disable-ts-in-templates/tsconfig.node.json
new file mode 100644
index 0000000..f094063
--- /dev/null
+++ b/examples/disable-ts-in-templates/tsconfig.node.json
@@ -0,0 +1,19 @@
+{
+ "extends": "@tsconfig/node20/tsconfig.json",
+ "include": [
+ "vite.config.*",
+ "vitest.config.*",
+ "cypress.config.*",
+ "nightwatch.conf.*",
+ "playwright.config.*"
+ ],
+ "compilerOptions": {
+ "composite": true,
+ "noEmit": true,
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "types": ["node"]
+ }
+}
diff --git a/examples/disable-ts-in-templates/vite.config.ts b/examples/disable-ts-in-templates/vite.config.ts
new file mode 100644
index 0000000..5c45e1d
--- /dev/null
+++ b/examples/disable-ts-in-templates/vite.config.ts
@@ -0,0 +1,16 @@
+import { fileURLToPath, URL } from 'node:url'
+
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [
+ vue(),
+ ],
+ resolve: {
+ alias: {
+ '@': fileURLToPath(new URL('./src', import.meta.url))
+ }
+ }
+})
diff --git a/examples/type-checked/eslint.config.js b/examples/type-checked/eslint.config.js
index d7c1e1d..456b067 100644
--- a/examples/type-checked/eslint.config.js
+++ b/examples/type-checked/eslint.config.js
@@ -16,7 +16,20 @@ export default defineConfigWithVueTs(
},
pluginVue.configs['flat/essential'],
- vueTsConfigs.recommendedTypeChecked,
+ vueTsConfigs.strictTypeChecked,
+ {
+ name: 'overrides',
+ files: ['**/*.{ts,vue}'],
+ rules: {
+ '@typescript-eslint/restrict-template-expressions': [
+ 'error',
+ {
+ allowNumber: true,
+ allowBoolean: true,
+ },
+ ],
+ },
+ },
{
...pluginVitest.configs.recommended,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 654e297..be34669 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -220,6 +220,46 @@ importers:
specifier: ^2.2.0
version: 2.2.0(typescript@5.7.3)
+ examples/disable-ts-in-templates:
+ dependencies:
+ vue:
+ specifier: ^3.5.13
+ version: 3.5.13(typescript@5.7.3)
+ devDependencies:
+ '@tsconfig/node20':
+ specifier: ^20.1.4
+ version: 20.1.4
+ '@types/node':
+ specifier: ^20.17.12
+ version: 20.17.14
+ '@vitejs/plugin-vue':
+ specifier: ^5.2.1
+ version: 5.2.1(vite@6.0.10(@types/node@20.17.14)(tsx@4.19.2)(yaml@2.7.0))(vue@3.5.13(typescript@5.7.3))
+ '@vue/eslint-config-typescript':
+ specifier: workspace:*
+ version: link:../..
+ '@vue/tsconfig':
+ specifier: ^0.7.0
+ version: 0.7.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))
+ eslint:
+ specifier: ^9.18.0
+ version: 9.18.0
+ eslint-plugin-vue:
+ specifier: ^9.32.0
+ version: 9.32.0(eslint@9.18.0)
+ npm-run-all2:
+ specifier: ^7.0.2
+ version: 7.0.2
+ typescript:
+ specifier: ~5.7.3
+ version: 5.7.3
+ vite:
+ specifier: ^6.0.7
+ version: 6.0.10(@types/node@20.17.14)(tsx@4.19.2)(yaml@2.7.0)
+ vue-tsc:
+ specifier: ^2.2.0
+ version: 2.2.0(typescript@5.7.3)
+
examples/minimal:
dependencies:
vue:
@@ -6155,7 +6195,7 @@ snapshots:
'@types/yauzl@2.10.3':
dependencies:
- '@types/node': 22.10.7
+ '@types/node': 20.17.14
optional: true
'@typescript-eslint/eslint-plugin@8.21.0(@typescript-eslint/parser@8.21.0(eslint@9.18.0)(typescript@5.7.3))(eslint@9.18.0)(typescript@5.7.3)':
diff --git a/src/internals.ts b/src/internals.ts
index 3e0c431..e48b0dc 100644
--- a/src/internals.ts
+++ b/src/internals.ts
@@ -1,6 +1,7 @@
import * as tseslint from 'typescript-eslint'
import vueParser from 'vue-eslint-parser'
import pluginVue from 'eslint-plugin-vue'
+import type { Parser } from '@typescript-eslint/utils/ts-eslint'
export type ScriptLang = 'ts' | 'tsx' | 'js' | 'jsx'
@@ -31,10 +32,30 @@ export const additionalRulesRequiringParserServices = [
]
export function createBasicSetupConfigs(
+ tsSyntaxInTemplates: boolean,
scriptLangs: ScriptLang[],
): ConfigArray {
const mayHaveJsxInSfc =
scriptLangs.includes('jsx') || scriptLangs.includes('tsx')
+
+ const parser: Record = {
+ // Fallback to espree for js/jsx scripts, as well as SFCs without scripts
+ // for better performance.
+ js: 'espree',
+ jsx: 'espree',
+
+ ts: tseslint.parser,
+ tsx: tseslint.parser,
+
+ // Leave the template parser unspecified,
+ // so that it could be determined by `