1. 90前端首页
  2. 前端框架

无缝改造vue项目,支持typescript

改造 vue_cli3+js 版本支持 ts

生成vue项目的vue_cli版本为 4.0.5

应用场景:

  • 目前已经在开发的项目, 后续想要摸摸 ts
  • 刚开始学习 ts, 不敢完全入坑

安装

yarn add typescript ts-loader --dev // 编译用
yarn add vue-property-decorator // 写vue组件时用
yarn add fork-ts-checker-webpack-plugin --dev // typescript 类型检查的webpack插件
yarn add @types/webpack-env // 包含webpack的类型定义(在tsconfig.json中定义types用,目前没有测试出有什么影响)
yarn add @typescript-eslint/parser --dev // eslint中的parse依赖r

书写 vue.config.js 修改 webpackloader

vue.config.js 必须是 js 文件

const path = require(\"path\");
function resolve(dir) {
  return path.join(__dirname, dir);
}
const ForkTsCheckerWebpackPlugin = require(\'fork-ts-checker-webpack-plugin\')

module.exports = {
    // lintOnSave: process.env.NODE_ENV === \"development\",
    lintOnSave: true,
    configureWebpack: {
        resolve: {
          extensions: [\'.tsx\',\'.ts\', \'.mjs\', \'.js\', \'.jsx\', \'.vue\', \'.json\', \'.wasm\']
        }
    },
    chainWebpack: config => {
        // 处理ts文件 (新增loader)
        config.module
            .rule(\'ts\')
            .test(/\\.tsx?$/)
            .exclude
                .add(resolve(\'node_modules\'))
                .end()
            .use(\'cache-loader\')
                .loader(\'cache-loader\')
                .options({
                    cacheDirectory: resolve(\'node_modules/.cache/ts-loader\')
                })
                .end()
            .use(\'babel-loader\')
                .loader(\'babel-loader\')
                .end()
            .use(\'ts-loader\')
                .loader(\'ts-loader\')
                .options({
                    transpileOnly: true, // 关闭类型检查,即只进行转译(类型检查交给webpack插件(fork-ts-checker-webpack-plugin)在另一个进程中进行,这就是所谓的多进程方案,如果设置transpileOnly为false, 则编译和类型检查全部由ts-loader来做, 这就是单进程方案.显然多进程方案速度更快)
                    appendTsSuffixTo: [\'\\\\.vue$\'],
                    happyPackMode: false
                })
                .end()
        
        // eslint 自动修复 (修改已经存在的loader)
        config.module
            .rule(\'eslint\')
            .test(/\\.(vue|(j|t)sx?)$/)
            .pre() // eslint是pre处理的
            .use(\'eslint-loader\')
                .loader(\'eslint-loader\')
                .tap(options => { // 修改已经存在loader的配置
                    options.fix = true
                    return options
                })
                .end()
        
        // 使用webpack 插件进行typescript 的类型检查 fork-ts-checker-webpack-plugin
        config
            .plugin(\'fork-ts-checker\')
            .use(ForkTsCheckerWebpackPlugin, [{
                vue: true,
                tslint: false,
                formatter: \'codeframe\',
                checkSyntacticErrors: false,
                // 因为fork-ts-checker-webpack-plugin是在单独的进程跑的,所以它的错误或警告信息是异步回传给到webpack进程的, 这时编译报错信息只在终端显示,不会在预览的浏览器界面显示报错信息。
                // 将async设置为false后,就要求webpack等待fork-ts-checker-webpack-plugin进程返回信息, 这样会在页面显示编译报错信息。不过这样做也可能会拖慢整个webpack的转译等待时间。
                // async: false 
            }])
        
    }
}

项目根目录下新建 tsconfig.json 文件

配置编译 ts 文件规则.我们默认使用了vue-cli 生成项目时选择 typescript 版本时的 tsconfig.json

  • 自定义新增了 \"noImplicitAny\": false,
  • strictPropertyInitialization 这个配置是要求定义类的属性时必须初始化赋值,在\"strict\": true 时自动设置为 true,这非常不合理,因为我们在 vue 中属性的值经常在 created/mounted 赋值, 因此设置为 \"strictPropertyInitialization\": false.
  • strictNullChecks 这个配置是严格的 null 检查模式. 在\"strictNullChecks\": true模式下(\"strict\": true 时自动设置为 true), nullundefined 值不包含在任何类型里, 但是我们在 vuedata 里面初始化变量时,经常会初始化为 null, 因此我们将此配置设置为 false
// strictNullChecks:true时下面一行会报错
let str: string = null
  • 上述连个配置均为 strict: true配置导致的, 如果你想简单可以将其注释掉, 因为 strict 默认为 false, 上述两个配置默认也是 false
strict: true //启用所有严格类型检查选项。
// 启用 --strict相当于启用 --noImplicitAny, --noImplicitThis, --alwaysStrict, --strictNullChecks和 --strictFunctionTypes和--strictPropertyInitialization。

最终 tsconfig.json

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

其他版本的 tsconfig.json, 重点在 pathstypes

{
  \"compilerOptions\": {
    \"target\": \"esnext\", // 编译目标语法, 可以写es5, 但是我们项目的ts-loader前经过了babel-loader
    \"module\": \"esnext\", // 
    \"strict\": true,
        \"strictPropertyInitialization\": false, // strict为true时,默认为true
        \"strictNullChecks\": false, // 设置null为其他类型的子类型, 效果:变量或者属性可以初始化为null
    \"jsx\": \"preserve\",
        \"noImplicitAny\": false, // false表示运行隐式的any类型,也就是允许不设置任何类型, 这个设置运行js文件直接改成ts文件
    \"importHelpers\": true,
    \"moduleResolution\": \"node\", // 和nodejs一样的node_modules机制
    \"experimentalDecorators\": true,
    \"esModuleInterop\": true,
    \"allowSyntheticDefaultImports\": true,
    \"sourceMap\": true,
    \"baseUrl\": \".\",
    \"paths\": { // 配合baseUrl, ts文件中import 模块路径的解析规则
      \"@/*\": [
        \"src/*\",
        \"src/types/*\"
      ],
      \"*\":[
        \"node_modules/*\",
        \"src/types/*\"
      ]
    },
    \"lib\": [
      \"esnext\",
      \"dom\",
      \"dom.iterable\",
      \"scripthost\"
    ]
  },
  \"include\": [
    \"src/**/*.ts\",
    \"src/**/*.tsx\",
    \"src/**/*.vue\",
    \"tests/**/*.ts\",
    \"tests/**/*.tsx\"
  ],
  \"exclude\": [
    \"node_modules\"
  ]
}

以上两个版本主要是 pathstypes 不同, 你需要了解他们的作用,并在工作中设置合适的值.
第二种的其他版本运行把所有的 .d.ts 文件放到 src/types 文件夹内.

src 文件下新建一个 ts 文件

src 下新建 shims-vue.d.ts 文件, 否则会报错如下:

ERROR
      TS18003: No inputs were found in config file \'tsconfig.json\'. Specified \'include\' paths were \'[\"src/**/*.ts\",\"src/**/*.tsx\",\"src/**/*.vue\",\"tests/**/*.ts\",\"tests/**/*.tsx\"]\' and \'exclude\' paths were \'[\"node_modules\"]\'.

vueCli3typescript 版本有 shims-vue.d.tsshims-tsx.d.ts 这两个文件,我们不妨把他们放到 src 下.

// shim-vue.d.ts
declare module \'*.vue\' {
  import Vue from \'vue\'
  export default Vue
}
// shims-tsx.d.ts
import Vue, { VNode } from \'vue\'

declare global {
  namespace JSX {
    // tslint:disable no-empty-interface
    interface Element extends VNode {}
    // tslint:disable no-empty-interface
    interface ElementClass extends Vue {}
    interface IntrinsicElements {
      [elem: string]: any
    }
  }
}

以上文件的原理,详见 typescriptmodule-augmentation(模块补充: 可以通过路径在文件中增补类型定义)

增加 eslint 规则

解决使用 ts 内置类型时保报错 ,例如 Partial.
安装(第一步已经安装)

yarn add @typescript-eslint/parser --dev

配置 .eslintrc.js 文件的 parser 项为 @typescript-eslint/parser

  • vue-cli(js版) 生成的 .eslintrc.js 中简单修改
module.exports = {
  root: true,
  env: {
    node: true,
        browser: true,
        es6: true
  },
  \'extends\': [
    \'plugin:vue/essential\',
    \'eslint:recommended\',
    \'@vue/prettier\'
  ],
  parserOptions: {
    // parser: \'babel-eslint\',
        parser: \'@typescript-eslint/parser\', // 解析ts文件, 例如识别ts文件的内置类型
        ecmaFeatures: {
          legacyDecorators: true
        }
  },
  rules: {
    \'no-console\': process.env.NODE_ENV === \'production\' ? \'error\' : \'off\',
    \'no-debugger\': process.env.NODE_ENV === \'production\' ? \'error\' : \'off\'
  },
  overrides: [
    {
      files: [
        \'**/__tests__/*.{j,t}s?(x)\',
        \'**/tests/unit/**/*.spec.{j,t}s?(x)\'
      ],
      env: {
        jest: true
      }
    }
  ]
}

在上述 .eslintrc.js 的配置中, 默认是使用双引号和分号结尾的, 当我在 rules中修改时,会和 prettier 的配置冲突, 因此在根目录下新建 .prettierrc.js 文件,书写

module.exports = { 
    \"printWidth\": 80, // 每行代码长度(默认80)
    \"tabWidth\": 2, // 每个tab相当于多少个空格(默认2)
    \"useTabs\": false, // 是否使用tab进行缩进(默认false)
    \"singleQuote\": true, // 使用单引号(默认false)
    \"semi\": false, // 声明结尾使用分号(默认true)
    \"trailingComma\": \"all\", // 多行使用拖尾逗号(默认none)
    \"bracketSpacing\": true, // 对象字面量的大括号间使用空格(默认true)
    \"jsxBracketSameLine\": false, // 多行JSX中的>放置在最后一行的结尾,而不是另起一行(默认false)
    \"arrowParens\": \"avoid\" // 只有一个参数的箭头函数的参数是否带圆括号(默认avoid)
};
  • 其他版本
module.exports = {
  root: true,
  parserOptions: {
      // +++++++++++
    parser: \'@typescript-eslint/parser\',
    sourceType: \'module\',
    ecmaFeatures: {
      legacyDecorators: true
    }
  },
  env: {
    browser: true,
    node: true,
    es6: true,
  },
  // plugin:包名/配置名称
  extends: [\'plugin:vue/recommended\', \'eslint:recommended\'],

  // add your custom rules here
  //it is base on https://github.com/vuejs/eslint-config-vue
  rules: {
    \"vue/max-attributes-per-line\": [2, {
      \"singleline\": 10,
      \"multiline\": {
        \"max\": 1,
        \"allowFirstLine\": false
      }
    }],
    \"vue/singleline-html-element-content-newline\": \"off\",
    \"vue/multiline-html-element-content-newline\":\"off\",
    \"vue/name-property-casing\": [\"error\", \"PascalCase\"],
    \"vue/no-v-html\": \"off\",
    \'accessor-pairs\': 2,
    \'arrow-spacing\': [2, {
      \'before\': true,
      \'after\': true
    }],
    \'block-spacing\': [2, \'always\'],
    \'brace-style\': [2, \'1tbs\', {
      \'allowSingleLine\': true
    }],
    \'camelcase\': [0, {
      \'properties\': \'always\'
    }],
    \'comma-dangle\': [2, \'never\'],
    \'comma-spacing\': [2, {
      \'before\': false,
      \'after\': true
    }],
    \'comma-style\': [2, \'last\'],
    \'constructor-super\': 2,
    \'curly\': [2, \'multi-line\'],
    \'dot-location\': [2, \'property\'],
    \'eol-last\': 2,
    \'eqeqeq\': [\"error\", \"always\", {\"null\": \"ignore\"}],
    \'generator-star-spacing\': [2, {
      \'before\': true,
      \'after\': true
    }],
    \'handle-callback-err\': [2, \'^(err|error)$\'],
    \'indent\': [2, 2, {
      \'SwitchCase\': 1
    }],
    \'jsx-quotes\': [2, \'prefer-single\'],
    \'key-spacing\': [2, {
      \'beforeColon\': false,
      \'afterColon\': true
    }],
    \'keyword-spacing\': [2, {
      \'before\': true,
      \'after\': true
    }],
    \'new-cap\': [2, {
      \'newIsCap\': true,
      \'capIsNew\': false
    }],
    \'new-parens\': 2,
    \'no-array-constructor\': 2,
    \'no-caller\': 2,
    \'no-console\': \'off\',
    \'no-class-assign\': 2,
    \'no-cond-assign\': 2,
    \'no-const-assign\': 2,
    \'no-control-regex\': 0,
    \'no-delete-var\': 2,
    \'no-dupe-args\': 2,
    \'no-dupe-class-members\': 2,
    \'no-dupe-keys\': 2,
    \'no-duplicate-case\': 2,
    \'no-empty-character-class\': 2,
    \'no-empty-pattern\': 2,
    \'no-eval\': 2,
    \'no-ex-assign\': 2,
    \'no-extend-native\': 2,
    \'no-extra-bind\': 2,
    \'no-extra-boolean-cast\': 2,
    \'no-extra-parens\': [2, \'functions\'],
    \'no-fallthrough\': 2,
    \'no-floating-decimal\': 2,
    \'no-func-assign\': 2,
    \'no-implied-eval\': 2,
    \'no-inner-declarations\': [2, \'functions\'],
    \'no-invalid-regexp\': 2,
    \'no-irregular-whitespace\': 2,
    \'no-iterator\': 2,
    \'no-label-var\': 2,
    \'no-labels\': [2, {
      \'allowLoop\': false,
      \'allowSwitch\': false
    }],
    \'no-lone-blocks\': 2,
    \'no-mixed-spaces-and-tabs\': 2,
    \'no-multi-spaces\': 2,
    \'no-multi-str\': 2,
    \'no-multiple-empty-lines\': [2, {
      \'max\': 1
    }],
    \'no-native-reassign\': 2,
    \'no-negated-in-lhs\': 2,
    \'no-new-object\': 2,
    \'no-new-require\': 2,
    \'no-new-symbol\': 2,
    \'no-new-wrappers\': 2,
    \'no-obj-calls\': 2,
    \'no-octal\': 2,
    \'no-octal-escape\': 2,
    \'no-path-concat\': 2,
    \'no-proto\': 2,
    \'no-redeclare\': 2,
    \'no-regex-spaces\': 2,
    \'no-return-assign\': [2, \'except-parens\'],
    \'no-self-assign\': 2,
    \'no-self-compare\': 2,
    \'no-sequences\': 2,
    \'no-shadow-restricted-names\': 2,
    \'no-spaced-func\': 2,
    \'no-sparse-arrays\': 2,
    \'no-this-before-super\': 2,
    \'no-throw-literal\': 2,
    \'no-trailing-spaces\': 2,
    \'no-undef\': 2,
    \'no-undef-init\': 2,
    \'no-unexpected-multiline\': 2,
    \'no-unmodified-loop-condition\': 2,
    \'no-unneeded-ternary\': [2, {
      \'defaultAssignment\': false
    }],
    \'no-unreachable\': 2,
    \'no-unsafe-finally\': 2,
    \'no-unused-vars\': [2, {
      \'vars\': \'all\',
      \'args\': \'none\'
    }],
    \'no-useless-call\': 2,
    \'no-useless-computed-key\': 2,
    \'no-useless-constructor\': 2,
    \'no-useless-escape\': 0,
    \'no-whitespace-before-property\': 2,
    \'no-with\': 2,
    \'one-var\': [2, {
      \'initialized\': \'never\'
    }],
    \'operator-linebreak\': [2, \'after\', {
      \'overrides\': {
        \'?\': \'before\',
        \':\': \'before\'
      }
    }],
    \'padded-blocks\': [2, \'never\'],
    \'quotes\': [2, \'single\', {
      \'avoidEscape\': true,
      \'allowTemplateLiterals\': true
    }],
    \'semi\': [2, \'never\'],
    \'semi-spacing\': [2, {
      \'before\': false,
      \'after\': true
    }],
    \'space-before-blocks\': [2, \'always\'],
    \'space-before-function-paren\': [2, \'never\'],
    \'space-in-parens\': [2, \'never\'],
    \'space-infix-ops\': 2,
    \'space-unary-ops\': [2, {
      \'words\': true,
      \'nonwords\': false
    }],
    \'spaced-comment\': [2, \'always\', {
      \'markers\': [\'global\', \'globals\', \'eslint\', \'eslint-disable\', \'*package\', \'!\', \',\']
    }],
    \'template-curly-spacing\': [2, \'never\'],
    \'use-isnan\': 2,
    \'valid-typeof\': 2,
    \'wrap-iife\': [2, \'any\'],
    \'yield-star-spacing\': [2, \'both\'],
    \'yoda\': [2, \'never\'],
    \'prefer-const\': 2,
    \'no-debugger\': process.env.NODE_ENV === \'production\' ? 2 : 0,
    \'object-curly-spacing\': [2, \'always\', {
      objectsInObjects: false
    }],
    \'array-bracket-spacing\': [2, \'never\']
  }
}

一个注意

如果你写了如下代码, 会报一个错误:

<template>
<div>{{message}}</div>
</template>
<script lang=\"ts\">
import { Vue, Component } from \"vue-property-decorator\"
@Component
export default class TestTs extends Vue {
  message: string = \"hello ts!\";
  mounted() {
    setTimeout(()=>{
      this.message = \"hello typeScript!\"
    },2000)
  }
}
</script>

错误如下:

error  Parsing error: Using the export keyword between a decorator and a class is not allowed. Please use `export @dec class` instead.

解决方法: 修改eslintrc.js

parserOptions: {
    ecmaFeatures: {
      legacyDecorators: true
    }
  },

vue-property-decorator 的使用

vue-property-decorator

本文来自网络整理,转载请注明原出处:https://segmentfault.com/a/1190000022065579

展开阅读全文

发表评论

登录后才能评论