让vue老项目支持js与ts混用

今年国庆前(2019),终于把我司的一个老项目重构完成(his前端重构经验),开发体验和效率上都有了质的飞越。不过由于一些不可抗原因(可用于重构时间不够充裕、历史代码量较大),还是没能将项目由 JavaScript 迁移到 TypeScript。

动机

对于喜欢ts好久好久的我来说(没错,我就是馋ts的类型系统,我下贱😝),不用ts我浑身难受。这周也是抽了些时间,让该系统支持ts与js混用了。当然并不是出于个人偏爱ts才做这样的改动,主要的动机有如下几点:

  • 该项目属于中大型的后台项目,确实需要类型系统来提升代码的可维护性和可读性
  • 在多人协作开发中,有类型提示可以减去很多不必要的沟通成本
  • 我司的小程序已经采用ts,未来ts也是我们团队的主要方向之一

如果读者也想对项目进行升级,一定要先确定是否真的有必要,切莫为了满足个人爱好,从而给整个团队带来额外的成本。

项目介绍

需要改造的项目是通过 vue-cli3 生成的模板项目,开启了eslint。

升级过程

安装 typescript , ts-loader

npm i typescript ts-loader -D

修改 webpack 配置

由于 vue-cli3 将 webpack 的配置全部隐藏起来了,只能通过在项目根目录下建立 vue.config.js 来修改配置,这里有两种方式来编写配置

  1. 通过直接修改configureWebpack选项,该方式和写webpack配置一样,在编译的时候,这份配置会通过 webpack-merge 合并到最终的配置中
  2. 通过 webpack-chain 链式修改配置,vue-cli3 内部也是通过这样的方式来维护

我选择的是第二种方式,因为它有友好的类型提示,不用边写边翻文档,关键的配置如下:

// vue.config.js
module.exports = {
/** 其他与本次改动无关的配置 */
/**
* @param {import('webpack-chain')} config
*/
chainWebpack: (config) => {
config
.resolve.extensions.add('.ts').add('.tsx')
.end().end()
.module
.rule('typescript')
.test(/\.tsx?$/)
.use('babel-loader')
.loader('babel-loader')
.end()
.use('ts-loader')
.loader('ts-loader')
.options({
transpileOnly: true,
appendTsSuffixTo: [
'\\.vue$',
],
happyPackMode: false,
})
.end();
}
}

主要的配置就是对于 .ts.tsx文件先通过 ts-loader 解析,然后再交由babel处理。

如果你的项目是 vue-cli2 生成的话,直接在 build/webpack.base.conf.js 中做相关的修改即可,关键代码如下

// build/webpack.base.conf.js
module.exports = {
/** 其他与本次改动无关的配置 */
resolve: {
/** other code */
extensions: ['.js', '.vue', '.json', '.ts'], // 添加 .ts 扩展名
},
 module: {
rules: [
/** other code */
{
test: /\.tsx?$/,
use: [
{ loader: 'babel-loader' },
{
loader: 'ts-loader',
exclude: /node_modules/,
options: {
transpileOnly: true,
appendTsSuffixTo: [
'\\.vue$'
],
happyPackMode: false
}
}
]
}
]
}
}

配置 tsconfig.json

ts的编译需要读取 tsconfig.json 文件,在根目录下创建 tsconfig.json 文件

// tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.js",
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
],
}

进行到这一步,如果你只是想简单的在项目中混用ts的话(比如在.js、.vue中引入.ts),配置就算是全部完成了,你可以新建一个 .ts 文件,然后再入口文件中导入开发测试了;如果你还需要eslint进行代码规范、编写类组件等需求的,则需要继续进行下面的步骤

配置eslint

因为eslint的生态圈比较繁荣,typescript团队已经放弃了tslint从而转向eslint,所以这里我们也是用eslint作为代码规范校验的工具。需要安装以下工具

npm i @typescript-eslint/eslint-plugin @typescript-eslint/parser @vue/eslint-config-typescript -D

然后修改eslint配置文件

module.exports = {
// 关键配置
plugins: ['@typescript-eslint'],
extends: [
'plugin:vue/essential',
'@vue/airbnb',
'@vue/typescript',
],
parserOptions: {
parser: '@typescript-eslint/parser',
},
}

支持在ts文件中导入.vue文件

默认情况下,typescript是无法识别 .vue 文件,当你需要在ts导入vue的单文件组件时(比如路由配置),编辑器会报错:找不到模块。为了让ts能将 .vue 当成模块识别,需要在项目中创建shims-vue.d.ts文件,这样ts就会把 .vue 文件当成模块来解析了。

// src/shims-vue.d.ts
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}

用装饰器注册组件(不推荐再使用)

npm i vue-property-decorator -S

由于vue的api设计原因,导致其很难使用ts来编写vue组件(无法正确的推导出this),所以还需要安装vue-property-decorator,通过它提供的一列装饰器,来编写类组件,用起来大有一种angular的感觉,具体可以移步到这里👉 https://github.com/kaorun343/vue-property-decorator,详细的参考demo👉TodoMvc-vue

不过vue官方已经决定放弃这种写法,学习react的hook并推出了composition-api,虽然社区对此颇有不满。所现阶段不推荐装饰器写法,等vue3正式发布后再逐步迁移vue组件吧。

在ts中导入js

既然是ts和js混用,那就会存在js中导入ts,或ts中导入js的情况。前者一般情况经过上面的配置,是不会有太多问题出现的。而后者(ts中导入js)可能在编写代码的时候,编辑器可能会提示一些小错误。这时候就需要你为相关的js文件编写类型提示文件了。

// a.js
export function sum(m, n) {
return m + n;
}

为了在ts中导入a.js不报错,我们要在相同的目录下创建a.d.ts类型提示文件

export type sum = (m: number, n:number) => numer;

如果导入的js文件代码量不是很多,建议直接修改源文件。

删除jsconfig.json

如果你使用了vscode作为开发工具,并且也配置了jsconfig.json文件。那么在引入ts之后,你完全可以删除这个文件,然后将tsconfig.json中的compilerOptions.allowJs设置为true即可。https://code.visualstudio.com/docs/languages/jsconfig

下一步计划

接下来会逐步将所有的 .js 文件加上类型系统,转成 .ts 文件。对于 .vue 单文件组件,继续保持原有写法,等到vue3发布后再做迁移的打算。当然也有可能会迁移到其他的框架,在这期间只要不断弱化 vue 的比重,让它之负责 ui 部分。

总结

  1. 安装 typescriptts-laoder,修改webpack配置,支持对.ts文件的解析
  2. 配置 tsconfig.json
  3. 配置 eslint
  4. 添加 shims-vue.d.ts 支持对 .vue 文件的识别
  5. 如果需要编写类组件,安装 vue-property-decorato
  6. 为需要导入到ts文件中的js文件编写类型文件,或者直接修改该文件为 .ts 文件
请我喝杯咖啡
请我喝杯咖啡