無縫改造vue項目,支持typescript

改造 vue_cli3+js 版本支持 ts

生成vue項目的vue_cli版本爲 4.0.5 javascript

應用場景:html

  • 目前已經在開發的項目, 後續想要默默 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 文件vue

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 文件規則.java

  • vue-cli 選擇 typescript 版本時的 tsconfig.json(自定義新增了 "noImplicitAny": false, "strictPropertyInitialization": false 這個配置是要求定義類的屬性時必須初始化賦值,在"strict": true 時自動設置爲 true,這很是不合理,由於咱們在 vue 中屬性的值常常在 created/mounted 賦值)
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
        "strictPropertyInitialization": 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"
  ]
}
  • 其餘版本, 重點在 pathstypes
{
  "compilerOptions": {
    "target": "esnext", // 編譯目標語法, 能夠寫es5, 可是咱們項目的ts-loader前通過了babel-loader
    "module": "esnext", // 
    "strict": true,
    "strictPropertyInitialization": false, // strict爲true時,默認爲true
    "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 文件夾內.node

src 文件下新建一個 ts 文件

src 下新建 shims-vue.d.ts 文件, 不然會報錯以下:webpack

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 下.git

// 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(模塊補充: 能夠經過路徑在文件中增補類型定義)es6

增長 eslint 規則

解決使用 ts 內置類型時保報錯 ,例如 Partial.
安裝(第一步已經安裝)github

yarn add @typescript-eslint/parser --dev

配置 .eslintrc.js 文件的 parser 項爲 @typescript-eslint/parserweb

  • 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

相關文章
相關標籤/搜索