Vue 3の既存のプロジェクトに後からTypeScriptを追加したら”Parsing error: The keyword ‘interface’ is reserved”

タイトルの通りの問題に悩んだのでメモを残します。

TypeScriptの追加

pnpmの場合、まず次をします。

$ pnpm add typescript -D

tsconfig.json(及びtsconfig.app.json、tsconfig.node.json)を追加したほうが良いかもしれませんが、今回の問題には関係ないようです。次で生成したファイルを追加しても大丈夫です。

$ pnpm create vue
:
√ Project name: ... vue-project
√ Add TypeScript? ... No / Yes
√ Add JSX Support? ... No / Yes
√ Add Vue Router for Single Page Application development? ... No / Yes
√ Add Pinia for state management? ... No / Yes
√ Add Vitest for Unit Testing? ... No / Yes
√ Add an End-to-End Testing Solution? » No
√ Add ESLint for code quality? ... No / Yes
√ Add Prettier for code formatting? ... No / Yes
:

問題発生手順

今回は次のファイル等を追加してみました。<script lang="ts" setup>のように指定しています。

<template>
  <div class="container mx-auto px-2">
    <h1 class="font-bold">ツリー</h1>
    <el-tree-v2 :data="data" :props="props" :height="208" />
  </div>
</template>

<script lang="ts" setup>
interface Tree {
  id: string
  label: string
  children?: Tree[]
}

const getKey = (prefix: string, id: number) => {
  return `${prefix}-${id}`
}

const createData = (
  maxDeep: number,
  maxChildren: number,
  minNodesNumber: number,
  deep = 1,
  key = 'node'
): Tree[] => {
  let id = 0
  return Array.from({ length: minNodesNumber })
    .fill(deep)
    .map(() => {
      const childrenNumber = deep === maxDeep ? 0 : Math.round(Math.random() * maxChildren)
      const nodeKey = getKey(key, ++id)
      return {
        id: nodeKey,
        label: nodeKey,
        children: childrenNumber
          ? createData(maxDeep, maxChildren, childrenNumber, deep + 1, nodeKey)
          : undefined
      }
    })
}

const props = {
  value: 'id',
  label: 'label',
  children: 'children'
}
const data = createData(4, 30, 40)
</script>

<style></style>

発生したエラー

Parsing error: The keyword 'interface' is reserved

解決方法

.eslintrc.cjsに以下の1行を追加しました。

/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')

module.exports = {
  root: true,
  'extends': [
    'plugin:vue/vue3-essential',
    'eslint:recommended',
    '@vue/eslint-config-prettier/skip-formatting'
  ],
  parser: "vue3", <--- ★この1行を追加
  parserOptions: {
    ecmaVersion: 'latest'
  },
  rules: {
    "no-multi-spaces": ["error"],
    "vue/no-multiple-template-root": "off",
    "no-unused-vars": "off",
    "no-use-before-define": "off"
  }
}