Javascript is very flexible to use because of its weak typing.

This also buried a lot of hidden dangers for large-scale projects and collaborative development of many people. If it is your own private business, it doesn’t matter, mainly external interfaces and public methods, it is a headache to connect. Mainly in the following aspects:

  1. Parameter types are not verified and are passed in any way, sometimes causing unknown problems due to type conversion.
  2. Interface documentation is not standard, every time you have to read the code to know what to pass, how to pass
  3. The interface is written to the specification, but there is a lot of code in the common library that handles type validation

This is very bad for engineering standardization. We decided to introduce typescript for strong validation at the code level.

An overview of

The original VUE project access to TS mainly includes the following steps:

  1. Install typescript-related NPM packages
  2. Modify the Webpack and TS configuration files
  3. Project public library and VUE file modification

Ok, here we go

1. Install typescript related NPM packages

There’s a very important point to note here:

Upgrade typescript depending on your local environment

This is a problem that many first-time users will encounter.

Because only saw the official website of the tutorial, step by step installation found all kinds of errors. The main problem is that the Webpack version doesn’t match, or some other NPM package version doesn’t match

Take my local example:

My native typescript environment is Webpack3, so if I install the latest version of typescript, the console will report that the WebPack version is too low.

So you can either upgrade your Webpack to Webapck4 or use the typescript version that matches it.

I chose the latter, because upgrading your project directly to WebAPck4 takes longer. The scaffolding we use is uniform within the company. It integrates many common underlying services. Upgrading webpack4 is more of a hassle, not to mention the fact that the project is on a tight schedule, you know.

Here are the packages I installed and their versions:

  • “Typescript “: “^3.1.4” (this is required, TS library)
  • “Ts-loader “: “^3.5.0” (Identify TS laoder)
  • “Tslint “: “^5.11.0” (tsLint validation library)
  • “Tslint-loader “: “^3.5.4” (tslint-loader)
  • “Tslint-config-standard “: “^8.0.1” (for the tslint default validation rule)
  • “Vue property-decorator”: “^7.2.0” (for ts syntax in.vue files)

2. Modify the configuration file

  • Modify webpack configuration file (add TS related configuration)
base: {
  entry: {
    ...
    app: resolve('src/main.ts') // Change main.js to main.ts}... resolve: { ... extensions: ['vue'.'.js'.'.ts']} module: {rules: [..., {// add ts recognition to filestest: /\.ts$/,
      exclude: /node_modules/,
      enforce: 'pre',
      loader: 'tslint-loader'
    }, {
      test: /\.tsx? $/, loader:'ts-loader',
      exclude: /node_modules/,
      options: {
        appendTsSuffixTo: [/\.vue$/],
      }
    }
  ]
}
Copy the code

Note: after main.js is changed to main.ts, some modifications are required.

main.ts

import Vue from 'vue'
import { legic } from '@/lib/legic.ts'. // Add custom methods to vue objectsdeclare module 'vue/types/vue' {
  interface Vue {
    $legicPrototype: Function}}$legic= legic ... ; (window as any).vm = new Vue({ el:'#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})

Copy the code

There are a couple of caveats here

  1. [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] You need to declare this through declare
  2. Using the window object in main.ts needs to be written as window as any
  • Create tslint.json in the root directory (similar to eslint, where a validation standard is set)
{
  "extends": "tslint-config-standard"."globals": {
    "require": true}}Copy the code
  • Create tsconfig.json (typescript configuration file) in the root directory
{
  "compilerOptions": {// Compile target platform"target": "es5"// Output directory"outDir": "./dist/"// Add the syntax needed to parse, otherwise TS will detect an error."lib": ["es2015"."es2016"."dom"], // Module parsing"moduleResolution": "node"// Specify which module system code to generate"module": "esnext"// There is an error with an implied any type on expressions and declarations"noImplicitAny": false// When compiling ts files into js files, generate the corresponding map file"sourceMap": true// Allows javascript files to be compiled"allowJs": true// Specify the base directory"baseUrl": ". /"// Enable the decorator"experimentalDecorators": true// Remove the comment"removeComments": true."pretty": true// is relative to"baseUrl"parsing"paths": {
      "vue": ["node_modules/vue/types"]."@ / *": ["src/*"]}},"include": [
    "src/**/*"]."exclude": [
    "node_modules"]}Copy the code
  • Create sfc.d.ts in SRC (declare global variables, class, module, function, namespace)

The main thing we’re doing here is letting TS recognize.vue files, window objects, and some modules

For details about how to use declare, see here

/** * Files that tell TypeScript *. Vue files can be handed to the vue module for processing *. Again, TypeScript only recognizes.ts files by default, not.vue filesdeclare module "*.vue" {
  import Vue from 'vue'
  exportDefault Vue} /** * tells TypeScript that the window is a global object and can be used directly, so that window.xx = 123 does not report an error */declareVar window: any /** * Import VueLazyLaod from'vue-lazyload'The writing of * /declare module 'vue-lazyload'
declare module '@zz/perf/vue'
declare module 'raven-js'
declare module 'raven-js/plugins/vue'

Copy the code

Change SRC /main.js to main.ts

Project reform

This part is the most troublesome, mainly a few large pieces

  • Base library transformation

    If your base library references a large number of NPM packages, then congratulations, this part of your transformation costs will be much lower.

    If you write a significant portion of your lib by hand, congratulations…

    We have our own lib library with a large number of self-maintained JS files. So if you’re going to do a TS retrofit, you have to change everything.

    Example: getParam in lib/url.js (not advanced, but easy to read and compatible)

exportDefault class URL{/** * @memberof URL * @summary Gets the specified parameter * @ in the current page connectiontype {function} * @param {string} param1 - If param2 is undefined, param1 is the key of the specified parameter from the current page URL. If param2 is not empty, Param1 is the specified URL * @param {string} param2 - This parameter is optional. If param2 exists, the key * @ of the parameter is obtained from the specified param1 connectionreturn {string|null}
   */
  static getParam (param1, param2) {
    let url = ' '
    letparam = null; // If there is only one parameter, the default is to get the parameter from the current page linkif (typeof param2 === 'undefined') {
      url = window && window.location.href || ' 'Param = param1 // Get parameters from the specified URL}else{url = param1 param = param2} // ExcludehashImpact url = url.split(The '#') [0]if (url.indexOf('? ') > -1) {
      url = url.split('? ')[1]
    }
    const reg = new RegExp('(^ | &)' + param + '= (/ ^ & *) [& # $] *'.'i')
    const rstArr = url.match(reg)
    if(rstArr ! == null) {return decodeURIComponent(rstArr[2])
    }
    return null
  }
  ...
}
Copy the code

The modified file is lib/url.ts

exportDefault class URL {/** * @memberof URL * @summary Gets the specified parameters in the URL * @type {function} * @param {string} param1 - If param2 is undefined, param1 is the key of the specified parameter from the current page URL. If param2 is not empty, Param1 is the specified URL * @param {string} param2 - This parameter is optional. If param2 exists, the key * @ of the parameter is obtained from the specified param1 connectionreturn{string|null} */ static getParam (param1: string, param2? : string): string {let url: string = ' '
    letParam = null // If there is only one parameter, the default parameter is obtained from the current page linkif (typeof param2 === 'undefined') {
      url = window && window.location.href || ' 'Param = param1 // Get parameters from the specified URL}else {
      url = param1
      param = param2
    }
    url = url.split(The '#') [0]if (url.indexOf('? ') > -1) {
      url = url.split('? ')[1]
    }
    const reg = new RegExp('(^ | &)' + param + '= (/ ^ & *) [& # $] *'.'i')
    const rstArr = url.match(reg)
    if(rstArr ! == null) {return decodeURIComponent(rstArr[2])
    }
    return null
  }
  ...
}
Copy the code

If you want to change the way typescript recommends multiple calls to a method, you can use method overloading.

I didn’t use it because I didn’t want to change the way the page was used.

Note: For a large project, we do not recommend a TS transformation for all files.

We prefer to adopt a progressive approach, one by one, without affecting the original page. Specific schemes will be introduced later

  • Vue file modification

src/components/helper/newUser/index.vue

<template>... </template> <script> import { LEGO_ATTR, initLegoData, legic } from'@/lib/legic'
import { getMyProfile } from '@/api/helper'
import { toast } from '@/lib/ZZSDK'
import myComponent from './myComponent.vue'
let flag = false// Whether to send the video to the buried pointexportDefault {components: {// custom component myComponent},data () {
    return{// User portrait portrait:' ', // nickName:' ', // Whether to click play isPlay:false}},mounted () {
    this.initData()
    initLegoData({
      type: 'newUserGuide'
    });
    legic(LEGO_ATTR.newUserGuide.SHOW);
  },
  methods: {
    initData () {
      getMyProfile().then(data => {
        console.log('data', data)
        const { respData } = data
        this.portrait = respData.portrait || ' '
        this.nickName = respData.nickname || ' '
      }).catch(err => {
        toast({ msg: err })
      })
    },
    goPageClick (type) {
      switch (type) {
        case 'SUN':
          legic(LEGO_ATTR.newUserGuide.SUNVILLAGECLICK)
          break
        case 'FOOTBALL':
          legic(LEGO_ATTR.newUserGuide.FOOTBALLCLICK)
          break
        case 'SIGN':
          legic(LEGO_ATTR.newUserGuide.SIGNCLICK)
          break
        default:
          return}},videoClick () {
      if (flag) {
        return
      } else {
        flag = true
        legic(LEGO_ATTR.newUserGuide.SIGNCLICK)
        this.isPlay = true
        this.$refs.video.play()
      }
    }
  }
}
</script>
<style lang="scss"scoped>... </style>Copy the code

After transforming

<template>... </template> <script lang="ts">
import { LEGO_ATTR, initLegoData, legic } from '@/lib/legic'
import { getMyProfile } from '@/api/helper.ts'
import { toast } from '@/lib/ZZSDK'
import { Component, Vue } from 'vue-property-decorator'
import test from './test.vue'

let flag: boolean = false// If you want to send a video, click on @component ({components: {test}})exportDefault class NewUser extends Vue {// Portrait =' '// Username nickName =' '// Whether to hit Play isPlay =false

  mounted (): void {
    this.initData()
    initLegoData({
      type: 'newUserGuide'
    });
    legic(LEGO_ATTR.newUserGuide.SHOW)
  }

  initDataGetMyProfile (). Then ((data: any) => {console.log()'data', data)
      const { respData } = data
      this.portrait = respData.portrait || ' '
      this.nickName = respData.nickname || ' '
    }).catch((err: string) => {
      toast({ msg: err })
    })
  }

  goPageClick (type: string) {
    switch (type) {
      case 'SUN':
        legic(LEGO_ATTR.newUserGuide.SUNVILLAGECLICK)
        break
      case 'FOOTBALL':
        legic(LEGO_ATTR.newUserGuide.FOOTBALLCLICK)
        break
      case 'SIGN':
        legic(LEGO_ATTR.newUserGuide.SIGNCLICK)
        break
      default:
        return}}videoClick () {
    if (flag) {
      return
    } else {
      flag = true
      legic(LEGO_ATTR.newUserGuide.SIGNCLICK)
      this.isPlay = true
      this.$refs.video['play']()
    }
  }
}
</script>
<style lang="scss"scoped>... </style>Copy the code

Mycomponent. vue is omitted before the modification, and only the modified components are shown here

<template>
  <div class="main">{{title}}{{name}}</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'

@Component
export default class MyComponent extends Vue {

  @Prop({ type: String, default: ' ' })
  name: string

  title: string = 'hello'
}
</script>
<style lang="scss" scoped>
  .main{
    display: none;
  }
</style>
Copy the code

Here are some things to note:

  • Ts does not recognize. Vue files by default, so you need to declare this in sfC. d.ts files and add the. Vue suffix when importing vue components
  • Introduce the vue-property-decorator plug-in. Use modifiers to register components so that data, prop, and function are called flat (which is officially recommended)
  • Ts import import file, if not suffix, the default is js file. If the JS file does not exist, the TS file is identified
  • For router use, normal push operations are ok, but history is not recognized. Such as this.Router [‘history’].current.query.xxx (We currently use it this way, there’s a better way you can correct it.)

Now let’s talk about the transformation scheme mentioned above:

In fact, here mainly involves. Vue file and lib library transformation, vue file has nothing to say, one by one can be changed. Lib files, here I suggest:

  • Keep the original js file at the beginning and do not delete it. In this way, files that have not yet been modified can continue to be used
  • Create a new ts file, such as util.js in lib and util.ts
  • The newly modified vue files are all imported into the lib library xx.ts (with the.ts suffix), such as import Util from ‘@/lib/ Util.

Ok, the above is the whole process of our transformation. What can I point out? Everybody learn from each other