生成vue項(xiàng)目的vue_cli版本為 4.0.5
應(yīng)用場景:
- 目前已經(jīng)在開發(fā)的項(xiàng)目, 后續(xù)想要摸摸
ts - 剛開始學(xué)習(xí)
ts, 不敢完全入坑
安裝
yarn add typescript ts-loader --dev // 編譯用
yarn add vue-property-decorator // 寫vue組件時(shí)用
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 修改 webpack 的 loader
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, // 關(guān)閉類型檢查,即只進(jìn)行轉(zhuǎn)譯(類型檢查交給webpack插件(fork-ts-checker-webpack-plugin)在另一個(gè)進(jìn)程中進(jìn)行,這就是所謂的多進(jìn)程方案,如果設(shè)置transpileOnly為false, 則編譯和類型檢查全部由ts-loader來做, 這就是單進(jìn)程方案.顯然多進(jìn)程方案速度更快)
appendTsSuffixTo: ['\\.vue$'],
happyPackMode: false
})
.end()
// eslint 自動修復(fù) (修改已經(jīng)存在的loader)
config.module
.rule('eslint')
.test(/\.(vue|(j|t)sx?)$/)
.pre() // eslint是pre處理的
.use('eslint-loader')
.loader('eslint-loader')
.tap(options => { // 修改已經(jīng)存在loader的配置
options.fix = true
return options
})
.end()
// 使用webpack 插件進(jìn)行typescript 的類型檢查 fork-ts-checker-webpack-plugin
config
.plugin('fork-ts-checker')
.use(ForkTsCheckerWebpackPlugin, [{
vue: true,
tslint: false,
formatter: 'codeframe',
checkSyntacticErrors: false,
// 因?yàn)閒ork-ts-checker-webpack-plugin是在單獨(dú)的進(jìn)程跑的,所以它的錯(cuò)誤或警告信息是異步回傳給到webpack進(jìn)程的, 這時(shí)編譯報(bào)錯(cuò)信息只在終端顯示,不會在預(yù)覽的瀏覽器界面顯示報(bào)錯(cuò)信息。
// 將async設(shè)置為false后,就要求webpack等待fork-ts-checker-webpack-plugin進(jìn)程返回信息, 這樣會在頁面顯示編譯報(bào)錯(cuò)信息。不過這樣做也可能會拖慢整個(gè)webpack的轉(zhuǎn)譯等待時(shí)間。
// async: false
}])
}
}
項(xiàng)目根目錄下新建 tsconfig.json 文件
配置編譯 ts 文件規(guī)則.我們默認(rèn)使用了vue-cli 生成項(xiàng)目時(shí)選擇 typescript 版本時(shí)的 tsconfig.json
- 自定義新增了
"noImplicitAny": false, -
strictPropertyInitialization這個(gè)配置是要求定義類的屬性時(shí)必須初始化賦值,在"strict": true時(shí)自動設(shè)置為true,這非常不合理,因?yàn)槲覀冊?vue中屬性的值經(jīng)常在created/mounted賦值, 因此設(shè)置為"strictPropertyInitialization": false. -
strictNullChecks這個(gè)配置是嚴(yán)格的null檢查模式. 在"strictNullChecks": true模式下("strict": true時(shí)自動設(shè)置為true),null和undefined值不包含在任何類型里, 但是我們在vue的data里面初始化變量時(shí),經(jīng)常會初始化為null, 因此我們將此配置設(shè)置為false
// strictNullChecks:true時(shí)下面一行會報(bào)錯(cuò)
let str: string = null
- 上述連個(gè)配置均為
strict: true配置導(dǎo)致的, 如果你想簡單可以將其注釋掉, 因?yàn)?strict默認(rèn)為false, 上述兩個(gè)配置默認(rèn)也是false
strict: true //啟用所有嚴(yán)格類型檢查選項(xiàng)。
// 啟用 --strict相當(dāng)于啟用 --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, 重點(diǎn)在 paths 和 types
{
"compilerOptions": {
"target": "esnext", // 編譯目標(biāo)語法, 可以寫es5, 但是我們項(xiàng)目的ts-loader前經(jīng)過了babel-loader
"module": "esnext", //
"strict": true,
"strictPropertyInitialization": false, // strict為true時(shí),默認(rèn)為true
"strictNullChecks": false, // 設(shè)置null為其他類型的子類型, 效果:變量或者屬性可以初始化為null
"jsx": "preserve",
"noImplicitAny": false, // false表示運(yùn)行隱式的any類型,也就是允許不設(shè)置任何類型, 這個(gè)設(shè)置運(yùn)行js文件直接改成ts文件
"importHelpers": true,
"moduleResolution": "node", // 和nodejs一樣的node_modules機(jī)制
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"paths": { // 配合baseUrl, ts文件中import 模塊路徑的解析規(guī)則
"@/*": [
"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"
]
}
以上兩個(gè)版本主要是 paths 和 types 不同, 你需要了解他們的作用,并在工作中設(shè)置合適的值.
第二種的其他版本運(yùn)行把所有的 .d.ts 文件放到 src/types 文件夾內(nèi).
在 src 文件下新建一個(gè) ts 文件
在 src 下新建 shims-vue.d.ts 文件, 否則會報(bào)錯(cuò)如下:
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"]'.
vueCli3 的 typescript 版本有 shims-vue.d.ts和 shims-tsx.d.ts 這兩個(gè)文件,我們不妨把他們放到 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
}
}
}
以上文件的原理,詳見 typescript的 module-augmentation(模塊補(bǔ)充: 可以通過路徑在文件中增補(bǔ)類型定義)
增加 eslint 規(guī)則
解決使用 ts 內(nèi)置類型時(shí)保報(bào)錯(cuò) ,例如 Partial.
安裝(第一步已經(jīng)安裝)
yarn add @typescript-eslint/parser --dev
配置 .eslintrc.js 文件的 parser 項(xiàng)為 @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文件的內(nèi)置類型
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 的配置中, 默認(rèn)是使用雙引號和分號結(jié)尾的, 當(dāng)我在 rules中修改時(shí),會和 prettier 的配置沖突, 因此在根目錄下新建 .prettierrc.js 文件,書寫
module.exports = {
"printWidth": 80, // 每行代碼長度(默認(rèn)80)
"tabWidth": 2, // 每個(gè)tab相當(dāng)于多少個(gè)空格(默認(rèn)2)
"useTabs": false, // 是否使用tab進(jìn)行縮進(jìn)(默認(rèn)false)
"singleQuote": true, // 使用單引號(默認(rèn)false)
"semi": false, // 聲明結(jié)尾使用分號(默認(rèn)true)
"trailingComma": "all", // 多行使用拖尾逗號(默認(rèn)none)
"bracketSpacing": true, // 對象字面量的大括號間使用空格(默認(rèn)true)
"jsxBracketSameLine": false, // 多行JSX中的>放置在最后一行的結(jié)尾,而不是另起一行(默認(rèn)false)
"arrowParens": "avoid" // 只有一個(gè)參數(shù)的箭頭函數(shù)的參數(shù)是否帶圓括號(默認(rèn)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']
}
}
一個(gè)注意
如果你寫了如下代碼, 會報(bào)一個(gè)錯(cuò)誤:
<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>
錯(cuò)誤如下:
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
}
},