2021 / 08 / 19

多入口webpack2.6 + vue + element-ui 升级ts支持

本文字数: 10701阅读时间: 26分钟

依赖更新

新增的依赖如下:

"@types/node": "^12.7.2",  
"@vue/cli-plugin-eslint": "^3.9.2",  
"@vue/eslint-config-typescript": "^4.0.0",
"typescript": "2.9.1",
"ts-loader": "3.5.0",  
"tslint": "^5.18.0",  
"tslint-config-standard": "^8.0.1",  
"tslint-loader": "^3.5.4",
"eslint-plugin-vue": "^5.0.0",
"vue-property-decorator": "^8.2.1",

升级

修改webpack.base.js

首先是针对webpack.base.js, webpack2.x的配置文件还是区分dev和pro的多文件配置的,但是ts是基础支持,不管dev还是pro环境都需要,所以修改的是webpack.base.js.

因为项目用了jsx/tsx,所以ts-loader直接写会报错,需要针对有无tsx区别处理.

webpack2.x的vue项目应该会有个vueLoader.conf, 需要加一行ts的loader

var utils = require('./utils')  
var config = require('../config')  
var isProduction = process.env.NODE_ENV === 'production'  
  
module.exports = {  
  loaders: utils.cssLoaders({  
  sourceMap: isProduction  
      ? config.build.productionSourceMap  
      : config.dev.cssSourceMap,  
  extract: isProduction  
  }),  
  postcss: [  
  require('autoprefixer')({  
  browsers: ['iOS >= 7', 'Android >= 4.1']  
 }) ],  ts: ['ts-loader', 'tslint-loader']  // 新加
}

build/webpack.base.conf.js

const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
const fs = require('fs')
const vuxLoader = require('vux-loader')
const VueWithTSXRegex = /Tsx\.vue$/
const VueWithoutTSXRegex = /(^|[^x]|[^s]x|[^T]sx)\.vue$/

function resolve(dir) {
    return path.join(__dirname, '..', dir)
}
const projectPath = `${config.projectDir}/${config.projectName}`
vueLoaderConfig.loaders['ts'] = 'babel-loader!ts-loader'
vueLoaderConfig.loaders['tsx'] = 'babel-loader!ts-loader'
const webpackConfig = {
    entry: {
        app: `./src/${projectPath}/main.ts`
    },
    output: {
        path: config.build.assetsRoot,
        filename: '[name].js',
        publicPath: process.env.NODE_ENV === 'production' ?
            config.build.assetsPublicPath :
            config.dev.assetsPublicPath
    },
    resolve: {
        extensions: ['.js', '.vue', '.json', '.ts', '.tsx'],
        alias: {
            'vue$': 'vue/dist/vue.esm.js',
            '@': resolve('src')
        }
    },
    module: {
        // Disable handling of unknown requires
        unknownContextRegExp: /$^/,
        unknownContextCritical: false,

        // Disable handling of requires with a single expression
        exprContextRegExp: /$^/,
        exprContextCritical: false,

        // Warn for every expression in require
        wrappedContextCritical: true,
        rules:
            // {
            //   test: /\.(js|vue)$/,
            //   loader: 'eslint-loader',
            //   enforce: "pre",
            //   include: [resolve('src'), resolve('test')],
            //   options: {
            //     formatter: require('eslint-friendly-formatter')
            //   }
            // },
            ((opts) => [
          Object.assign({}, opts, {
                    test: VueWithTSXRegex
                }),
          Object.assign({}, opts, {
                    test: VueWithoutTSXRegex
                }),
      ])({
                loader: 'vue-loader',
                options: vueLoaderConfig
            })
            // ts-loader options
            .concat(
                ((opts) => [
              Object.assign({}, opts, {
                        test: /\.ts$/
                    }),
              Object.assign({}, opts, {
                        test: /\.tsx$/
                    }),
          ])({
                    loader: 'ts-loader',
                    options: {
                        appendTsSuffixTo: [VueWithoutTSXRegex],
                        appendTsxSuffixTo: [VueWithTSXRegex],
                    }
                })
            )
            // other options
            .concat([
                {
                    test: /\.ts$/,
                    exclude: /node_modules/,
                    enforce: 'pre',
                    loader: 'tslint-loader'
          },
                {
                    test: /\.js$/,
                    loader: 'babel-loader',
                    include: [resolve('src'), resolve('test')]
          },
                {
                    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                    loader: 'url-loader',
                    query: {
                        limit: 10000,
                        name: utils.assetsPath('img/[name].[hash:7].[ext]')
                    }
          },
                {
                    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
                    loader: 'url-loader',
                    query: {
                        limit: 10000,
                        name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
                    }
          }
      ])
        // {
        //     test: /\.ts$/,
        //     exclude: /node_modules/,
        //     enforce: 'pre',
        //     loader: 'tslint-loader'
        // },
        // {
        //   test: /\.vue$/,
        //   loader: 'vue-loader',
        //   options: vueLoaderConfig
        // },
        // {
        //     test: /\.tsx?$/,
        //     loader: 'ts-loader',
        //     exclude: /node_modules/,
        //     options: {
        //         appendTsSuffixTo: [/\.vue$/]
        //     }
        // },
        // {
        //     test: /\.js$/,
        //     loader: 'babel-loader',
        //     include: [resolve('src'), resolve('test')]
        // },
        // {
        //     test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        //     loader: 'url-loader',
        //     query: {
        //         limit: 10000,
        //         name: utils.assetsPath('img/[name].[hash:7].[ext]')
        //     }
        // },
        // {
        //       test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        //       loader: 'url-loader',
        //       query: {
        //           limit: 10000,
        //           name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        //       }
        //   }

    }
}

module.exports = vuxLoader.merge(webpackConfig, {
    options: {},
    plugins: [{
        name: 'vux-ui'
  }]
})

修改.eslintrc.js

这里主要是加上了@typescript-eslint/parser和node的环境支持

// http://eslint.org/docs/user-guide/configuring  
  
module.exports = {  
  root: true,  
  // parser: 'babel-eslint',  
  
  parserOptions: {  
  sourceType: 'module',  
  parser: '@typescript-eslint/parser'  
  },  
  
  env: {  
  browser: true,  
  node: true  
  },  
  
  // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style  
  extends: 'plugin:vue/recommended',  
  
  // required to lint *.vue files  
  plugins: [  
  "vue"  
  ],  
  
  // add your custom rules here  
  'rules': [  
  'plugin:vue/essential',  
  'eslint:recommended',  
  '@vue/typescript'  
  ],  
  
  rules: {  
  'arrow-parens': 0,  
  'generator-star-spacing': 0,  
  'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',  
  indent: [  
  'error',  
  4  
  ],  
  'space-before-function-paren': [  
  'error',  
  'never'  
  ],  
  'comma-dangle': [  
  'error',  
  'only-multiline'  
  ],  
  'eol-last': 0,  
  'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off'  
  }  
}

增加shims-tsx.d.ts和vue-shim.d.ts

其实这一步操作跟现在的vue-cli3.0生成好的项目一样,在src的根目录下都会有这两个文件.为了声明Vue以及一些没有@types/xxx且没有自带.d.ts的第三方库。本项目基于element-ui的,配置如下,仅供参考.

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  
  }  
 }}

vue-shim.d.ts

declare module '*.vue' {  
  import Vue from 'vue'  
  export default Vue  
}  
// 第三方库
declare module 'dateformat'

修改入口文件

把main.js改成main.ts, 为了偷懒,所以直接注释掉ts-lint,因为文件太多了,不可能一个一个文件去改成ts。类似这样⬇️(虽然很偷懒,但是很省事)

import RouterHook from './router/router-hook'  
// @ts-ignore  
import Interceptor from '@/utils/Interceptor'  
// @ts-ignore  
import PermissionPlugin from '@/utils/permission-plugin'  
// @ts-ignore

新增tsconfig.json和tslint.json

tsconfig也改了很多次,最后修改成如下可以正常是用,tslint仅供参考

tsconfig.json

{  
  "compilerOptions": {  
  // 与 Vue 的浏览器支持保持一致  
  "target": "es5",  
  // 这可以对 `this` 上的数据属性进行更严格的推断  
  "strict": true,  
  "jsx": "preserve",  
  "jsxFactory": "Vue.prototype.$createElement",  
  // 如果使用 webpack 2+ 或 rollup,可以利用 tree-shake:  "module": "es2015",  
  "types": [  
  "node"  
  ],  
  "typeRoots": [  
  // add path to @types  
  "node_modules/@types"  
  ],  
  "lib": [ "es2015", "es2015.promise", "dom", "dom.iterable", "es6", "es2017.object"],  
  "moduleResolution": "node",  
  "experimentalDecorators": true,  
  "noImplicitAny": true,  
  "removeComments": true  
  }  
}

tslint.json

{  
  "defaultSeverity": "warning",  
  "extends": "tslint-config-standard",  
  "globals": {  
  "require": true  
  },  
  "linterOptions": {  
  "exclude": [  
  "node_modules/**"  
  ]  
 },  "rules": {  
  "no-consecutive-blank-lines": false,  
  "quotemark": [true, "single"],  
  "indent": [true, "spaces", 2],  
  "interface-name": false,  
  "ordered-imports": false,  
  "object-literal-sort-keys": false,  
  "member-access": false,  
  "noImplicitAny": false  
  }  
}

测试

写一个ts的页面试试

demo.ts

<template lang="pug">  
  .home.container  
  p hello-world  
  p  
 el-input(v-model="form.text" ref='input')  
  p {{currentName}}  
  p  
 el-button(@click="topicName") 输出名字  
</template>  
  
<script lang="ts">  
import {Component, Ref, Vue, Watch} from 'vue-property-decorator'  
import {IForm} from "../../interface/IForm"  
@Component({  
})  
export default class Home extends Vue {  
  @Ref('input') readonly inputa!: HTMLButtonElement  
  form: IForm = {  
  text: ''  
  }  
  topicName() {  
  this.$message.success((this.inputa).value)  
 }  get currentName() {  
  return `hello , my name is ${this.form.text}`  
  }  
  @Watch('form', {immediate: true, deep: true})  
  watchName(form: IForm) {  
  console.log(`text -> ${form.text}`)  
 }}  
</script>  
<style lang="less" scoped>  
  .container{  
  display: flex;  
  flex: 1;  
  flex-wrap: wrap;  
  p{  
  width: 100%;  
 } }</style>

总结

到这一步算是完成了老项目对与ts的支持,旧项目因为体积和大量代码的容与,迁移到新项目还是成本太高了,但是新项目的代码想复用在老项目也不得不需要支持ts,所以才有了今天这份博文,总的来说,webpack2.x因为历史原因,在选择typescript和ts-loader的选择上也需要慎重选择版本,一个不小心就会报错。而无法是用新版本的ts也是这个构建的诟病之一了,如果有更好的方案麻烦指出!感谢.

后记

2019-08-22: babel7已经原生支持typescript了,可以把ts-loader干掉了,hhhhhh.

更多阅读