美文网首页TypeScript学习笔记
改造 vue_cli3+ 的 js 版本支持 ts

改造 vue_cli3+ 的 js 版本支持 ts

作者: shuaiutopia | 来源:发表于2020-04-14 09:31 被阅读0次

    生成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

    相关文章

      网友评论

        本文标题:改造 vue_cli3+ 的 js 版本支持 ts

        本文链接:https://www.haomeiwen.com/subject/muzamhtx.html