为 Quasar 项目添加 TypeScript 支持

under quasar vue typescript

in tech

Published: 2019-06-04

Last edit: 2019-06-08

为 Quasar 项目添加 TypeScript 支持

描述如何为一个 Vue 项目添加 TypeScript 支持的文章不少,Vue.js with TypeScript 可以作为起点。

解决软件依赖

开发环境需要添加以下 npm 软件包:

npm install typescript ts-loader --save-dev

Vue 类组件

下面两个依赖的作用是支持把 Vue 组件写成类的形式,Microsoft 提供了一篇文档说明如何使用类和属性装饰器编写 Vue 组件

npm install vue-class-component vue-property-decorator --save

根据我有限的经验,类组件本身就是导出的组件,不需要借助 Vue.extend 进行转化,比普通 JavaScript 对象更加直观,去除了 datamethodscomputed 这些包装后,这部分代码减少了一层缩进,更加简洁。

有2个不足:

  1. 在 SFC 中使用这个组件时,Vetur 不再提供该组件的 props 自动完成。
  2. 如果该中的其余代码中引用了 props 定义的属性,TypeScipt 会因为该属性不存在无法通过编译。这时如果非要使用类组件,就得先用 Vue.extend 定义一个父类,再让组件继承这个父类,摘自 vue-class-component 项目提供的例子

    ```typescript // We declare the props separately // to make props types inferable. const AppProps = Vue.extend({ props: { propMessage: String } })

    @Component({ / other stuff / }) export default class App extends AppProps { / Implement class App / } ```

vue-property-decorator 比较好的解决了第2个问题,可以通过使用 @Prop() 装饰类属性的方法定义 props,除了少写了一部分代码,现在可以给这些属性添加类型定义以便 TypeScript 对引用这些属性的代码进行类型检查:

import { Vue, Component, Prop } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Prop(Number) readonly propA?: number
  @Prop({ default: 'default value' }) readonly propB!: string
}

除此之外,对 props 定义的组件属性进行类型检查的方法我只想到一种,就是把每个引用组件属性的地方写成如下形式:

const propValue: PropType = this.propName
// Use propValue

想象一下这种方法可能带来的重复和工作量。

组件属性的值不是通过构造函数传递给组件的,这里引入了 Assignment Assertion Modifier 语法,即通过在变量名后面添加感叹号的方式(propB!: string)告诉 TypeScript 这个属性既不在定义类的时候初始化,也不在构造函数中初始化,让 TypeScript 不再提示属性没有初始化的类型错误。Quasar 默认配置 eslint 的 parser babel-eslint,不能识别这种语法,解决方法是安装 typescript-eslint/parser,把 .eslintrc.js 中的 parser 设置为 @typescript-eslint/parser:

   parserOptions: {
     parser: '@typescript-eslint/parser',
     sourceType: 'module'
   },

在项目中添加 tsconfig.json 文件

因为使用 VSCode 的缘故,直接把 jsconfig.json 重命名为 tsconfig.json,并添加如下设置:

{
  "compilerOptions": {
    "allowJs": true,
    "module": "es2015",
    "moduleResolution": "node",
    "outDir": "./dist/typescript",
    "target": "es5",
    "strict": true
  }
}

该设置来自于 Vue 文档,注意这里并没有设置 "lib" 选项,如果要在 TypeScript 代码中用到 ES6 特性,应该需要添加该设置。

因为原来的项目使用 JavaScript,这里添加了 "allowJs" 选项,达到 JavaScript 和 TypeScript 共存的目的,但这个选项必须和 "outDir" 配合使用,因为 TypeScript 默认将目标代码输出为输入文件同目录下的 .js 文件,这将导致输入文件和输出文件路径冲突,实际上不会,因为项目中使用 Webpack ts-loader 来生成目标代码,但 VSCode 不了解这个信息,会提示如下错误:

Cannot write file '/path/to/somefile.js' because it would overwrite input file.

况且,如果不小心在项目目录中输入了 tsc 命令,TypeScript 真的会生成目标代码,在没有设置 "outDir" 的情况下,项目文件会被弄得乱七八糟。

ES6 默认导出的问题

这篇文章提到了 "allowSyntheticDefaultImports" 选项,这个选项的作用是使类似下面的语句通过类型检查:

import Vue from 'vue'

它并不影响 TypeScript 输出的目标代码,如果 TypeScript 类型检查对此有疑问,并且也在项目中使用 TypeScript 生成目标代码,应该使用 "esModuleInterop" 选项,这篇文章详细解释了原因。Adding TypeScript Support to Quasar 没有使用这些选项,便增加了一个步骤:Fix Vue Imports & Modules,把以上 import 语句改成:

import * as Vue from 'vue'

现在新项目应该不需要设置这些选项了,一方面 TypeScript 默认打开 "esModuleInterop",参考 tsc --init 的输出,另外 Vue 也添加了默认导出:

export default Vue;

export as namespace Vue;

可见这个问题让大家操碎了心。

修改 Webpack 配置

Quasar 项目的 Webpack 配置由 Quasar 自动生成,在项目的 quasar.conf.js 中添加如下代码:

extendWebpack (cfg) {
  /* other stuff */
  cfg.resolve.extensions.push('.ts')
  cfg.resolve.extensions.push('.tsx')

  cfg.module.rules.push({
    test: /\.tsx?$/,
    loader: 'ts-loader',
    exclude: /node_modules/,
    options: {
      appendTsSuffixTo: [/\.vue$/]
    }
  })
}

添加类型定义

在 src/ 目录中添加 vue-shim.d.ts,内容如下:

declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}

这个文件的目的是告诉 TypeScript *.vue 文件的导出类型。VSCode 也会依据这个文件对 Vue 组件进行类型推断。这个文件需要放在 src/ 目录下,放在项目根目录无效,可能是因为 tsconfig.json 中设置了:

{
  "include": [
    "./src/**/*"
  ]
}

Adding TypeScript Support to Quasar 指出,Quasar 项目还需要一些额外的类型定义:

declare module "quasar"
declare const __THEME

Quasar 官方插件 增加了一个类型定义:

declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: string
    VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined
    VUE_ROUTER_BASE: string | undefined
  }
}

需要保留为 js 的源文件

Quasar Cli 会在编译项目时会对项目进行检查,如果读取不到特定的文件会终止编译,如 src/router/index.js,因此建议保留 Quasar Cli 生成的 JavaScript 源文件。

Quasar 官方支持

v1 版本以的 Quasar 开始支持 TypeScript,可以看到源代码里增加了 utils 模块的类型定义