上车!Vue2.x + Webpack4.x + Typescript

1,070 阅读1分钟

技术在于折腾。之前一直用React+ts,类型推导提高了开发效率,真香。而且现在Vue3.x也支持ts了,但是Vue3.x天生对IE的兼容性差,让我们望而退步。如果Vue2.x也支持ts,就提高我们开发效率。但是Vue2-cli自带的Webpack3.x在编译Typescript各种报错,而且Webpack4.x中对于打包速度,效率等有了很大提升,故决定升级Webpack,用最新的Webpack4.x支持Typescript。废话不多说了,上配置:

新建Vue项目

# 全局安装 vue-cli
$ npm install -g vue-cli

# 创建一个基于 webpack 模板的新项目
$ vue init webpack vue-ts-project
$ cd vue-ts-project

# 安装依赖
$ npm install

# 然后就可以运行看到界面了
$ npm run dev

先将Webpack升级到4.x

升级package.json中npm包

webpack

"webpack": "^3.6.0" => "^4.29.6"
"webpack-bundle-analyzer": "^2.9.0" => "^3.1.0"
"webpack-cli": "^3.3.0" (ADD)
"webpack-dev-server": "^2.9.1" => "^3.1.11"
"webpack-merge": "^4.1.0" => "^4.2.1"

loader

"css-loader": "^0.28.0" => "^2.1.1"
"file-loader": "^1.1.4" => "^3.0.1"
"inject-loader": "^3.0.0" => "^4.0.1"
"postcss-loader": "^2.0.8" => "^3.0.0"
"url-loader": "^0.5.8" => "^1.1.2"
"vue-loader": "^13.3.0" => "^15.7.0"
"vue-style-loader": "^3.0.1" => "^4.1.2"
"vue-template-compiler": "^2.5.2" => "^2.6.9"

plugin

"copy-webpack-plugin": "^4.0.1" => "^5.0.1"
"friendly-errors-webpack-plugin": "^1.6.1" => "^1.7.0"
"html-webpack-plugin": "^2.30.1" => "^3.2.0"
"optimize-css-assets-webpack-plugin": "^3.2.0" => "^5.0.1"
"extract-text-webpack-plugin": "^3.0.0"  (DEL)
"mini-css-extract-plugin": "^0.5.0" (ADD)

eslint

"eslint": "^4.15.0" => "^4.19.1"
"eslint-config-standard": "^10.2.1" => "^11.0.0"
"eslint-friendly-formatter": "^3.0.0" => "^4.0.1"
"eslint-loader": "^1.7.1" => "^2.0.0"
"eslint-plugin-import": "^2.7.0" => "^2.12.0"
"eslint-plugin-node": "^5.2.0" => "^6.0.1"
"eslint-plugin-promise": "^3.4.0" => "^3.7.0"
"eslint-plugin-standard": "^3.0.1" => "^3.1.0"
"eslint-plugin-vue": "^4.0.0" => "^4.5.0"

配置修改

build/webpack.base.conf.js

+ const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
+  plugins: [
+    new VueLoaderPlugin()
+  ],
   resolve: {
     extensions: ['.js', '.ts', '.vue', '.json'],
     alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
   }
}

build/webpack.prod.conf.js

-  // const ExtractTextPlugin = require('extract-text-webpack-plugin')
-  // const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
+  const MiniCssExtractPlugin = require('mini-css-extract-plugin')
+  const { VueLoaderPlugin } = require('vue-loader')

   const webpackConfig = merge(baseWebpackConfig, {
     
+    optimization: {
+      minimize: true,
+      splitChunks: {
+        cacheGroups: {
+          vendors: {
+            test: /[\\/]node_modules[\\/]/,
+            chunks: 'initial',
+            name: 'vendors',
+          },
+          'async-vendors': {
+            test: /[\\/]node_modules[\\/]/,
+            minChunks: 2,
+            chunks: 'async',
+            name: 'async-vendors'
+          }
+        }
+       },
+       runtimeChunk: { name: 'runtime' }
+     },
      plugins: [
+       new MiniCssExtractPlugin({
+        filename: utils.assetsPath('css/[name].[contenthash].css'),
+        allChunks: true,
+       }),

-      // new ExtractTextPlugin({
-      //  filename: utils.assetsPath('css/[name].[contenthash].css'),
-      //  allChunks: true,
-      // }),
-      // new UglifyJsPlugin({
-      //   uglifyOptions: {
-      //     compress: {
-      //       warnings: false
-      //     }
-      //   },
-      //   sourceMap: config.build.productionSourceMap,
-      //   parallel: true
-      // }),
-      // new webpack.optimize.CommonsChunkPlugin({
-      //   name: 'vendor',
-      //   minChunks (module) {
-      //     return (
-      //       module.resource && /\.js$/.test(module.resource) && 
-      //       module.resource.indexOf(
-      //         path.join(__dirname, '../node_modules')
-      //       ) === 0
-      //  )
-      // }
-      // }),
-      // new webpack.optimize.CommonsChunkPlugin({
-      //   name: 'manifest',
-      //   minChunks: Infinity
-      // }),
-      // new webpack.optimize.CommonsChunkPlugin({
-      //   name: 'app',
-      //   async: 'vendor-async',
-      //   children: true,
-      //   minChunks: 3
-      // }),
    ]
   }

build/utils.js

- // const ExtractTextPlugin = require('extract-text-webpack-plugin')
+ const MiniCssExtractPlugin = require('mini-css-extract-plugin')

  function generateLoaders (loader, loaderOptions) {
    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]

    if (loader) {
      loaders.push({
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }

-   // Extract CSS when that option is specified
-   // (which is the case during production build)
-   // if (options.extract) {
-   //   return ExtractTextPlugin.extract({
-   //     use: loaders,
-   //     fallback: 'vue-style-loader'
-   //   })
-   // } else {
-   //   return ['vue-style-loader'].concat(loaders)
-   // }
+   return [
+     options.extract ? MiniCssExtractPlugin.loader : 'vue-style-loader',
+   ].concat(loaders)
  }

添加Typescript支持

安装npm包

$ npm install typescript ts-loader --save-dev
$ npm install vue-class-component vue-property-decorator --save-dev

在src下新建vue-shim.d.ts文件

declare module "*.vue" {
  import Vue from "vue"
  export default Vue
}

在项目根目录下新建tsconfig.json文件

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "allowJs": true,
    "module": "esnext",
    "target": "es5",
    "moduleResolution": "node",
    "isolatedModules": true,
    "lib": [
      "dom",
      "es5",
      "es6",
      "es7",
      "es2015.promise",
      "scripthost"
    ],
    "sourceMap": true,
    "pretty": true,
    "strictFunctionTypes": false,
    "importHelpers": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

将main.js扩展名更改为main.ts

+ import App from './App.vue'

修改webpack.base.conf.js配置

  entry: {
-    app: './src/main.js'
+    app: './src/main.ts'
  },
  resolve: {
+   extensions: ['.js', '.vue', '.json', '.ts'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
  },
  module: {
    rules: [
    // ...
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
        options: {
          appendTsSuffixTo: [/\.vue$/],
        },
        include: [resolve('src')]
      }
     // ...
    ]
  },

改造vue组件

<template>
  <div>{{msg}}</div>
</template>

<script lang="ts">
import Vue from 'vue'
import { Component } from 'vue-property-decorator'
import HelloWorld from '@/components/HelloWorld.vue'

@Component({
  components: {
    HelloWorld
  }
})
export default class App extends Vue {
  msg = 'hello world'
}
</script>

happy hacking

完整代码
参考文档