vue-cli +typescript+webpack4 項目搭建超詳細步驟

vue+typescript+webpack4 項目搭建步驟

前言: 由於最近纔開始接觸Typescript,而後首先就是搭建環境,而網上教程時間比較久並且不全,因此在這裏記錄下手把手的詳細腳印。😂 🎉🎉🎉javascript

源碼地址請戳 👇👇👇 vue-ts-initcss

一、初始化項目

vue init webpack vue-ts-init
cd vue-ts-init
npm install
複製代碼

腳手架項目webpack版本爲3.6.0html

二、webpack@3.6.0升級至webpack@4.37.0

方法一:使用yarn安裝vue

yarn upgrade webpack@4.6.0
yarn add webpack-dev-server webpack-cli -D
複製代碼

方法二:手動修改package.json中webpack版本,從新安裝,完成後java

運行 npm run build, 報錯node

一、webpack.optimize.CommonsChunkPlugin

Error: webpack.optimize.CommonsChunkPlugin has been removed, please use config.optimization.splitChunks insteadjquery

緣由:CommonsChunkPlugin已被webpack4廢棄,推薦使用SplitChunkPlugin抽離公共模塊webpack

解決:找到 /build/webpack.prod.conf.js ,去掉以下配置git

new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor',
    minChunks (module) {
    // any required modules inside node_modules are extracted to vendor
    return (
        module.resource &&
        /\.js$/.test(module.resource) &&
        module.resource.indexOf(
        path.join(__dirname, '../node_modules')
        )
    )
    }
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
    name: 'manifest',
    minChunks: Infinity
}),
// This instance extracts shared chunks from code splitted chunks and bundles them
// in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
new webpack.optimize.CommonsChunkPlugin({
    name: 'app',
    async: 'vendor-async',
    children: true,
    minChunks: 3
}),
複製代碼

/build/webpack.prod.conf.js,添加es6

const webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true,
      usePostCSS: true
    })
  },
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  // 添加以下配置
  optimization: {
	splitChunks: {
	    cacheGroups: {
    		commons: {
    			chunks: "all",
    			minChunks: 2,
    			maxInitialRequests: 5, // The default limit is too small to showcase the effect
    			minSize: 0 // This is example is too small to create commons chunks
	    	 },
	    	 vendor: {
    			test: /node_modules/,
    			chunks: "all",
    			name: "vendor",
    			priority: 10,
    			enforce: true
    		}
    	}
    }
},
	......
})
複製代碼

附:官方的例子 common-chunk-and-vendor-chunk

optimization: {
    //提取公共模塊,webpack4去除了CommonsChunkPlugin,使用SplitChunksPlugin做爲替代
    //主要用於多頁面
    //例子代碼 https://github.com/webpack/webpack/tree/master/examples/common-chunk-and-vendor-chunk
    //SplitChunksPlugin配置,其中緩存組概念目前不是很清楚
    splitChunks: {
        // 表示顯示塊的範圍,有三個可選值:initial(初始塊)、async(按需加載塊)、all(所有塊),默認爲all;
        chunks: "all",
        // 表示在壓縮前的最小模塊大小,默認爲0;
        minSize: 30000,
        //表示被引用次數,默認爲1
        minChunks: 1,
        //最大的按需(異步)加載次數,默認爲1;
        maxAsyncRequests: 3,
        //最大的初始化加載次數,默認爲1;
        maxInitialRequests: 3,
        // 拆分出來塊的名字(Chunk Names),默認由塊名和hash值自動生成;設置ture則使用默認值
        name: true,
        //緩存組,目前在項目中設置cacheGroup能夠抽取公共模塊,不設置則不會抽取
        cacheGroups: {
            //緩存組信息,名稱能夠本身定義
            commons: {
                //拆分出來塊的名字,默認是緩存組名稱+"~" + [name].js
                name: "test",
                // 同上
                chunks: "all",
                // 同上
                minChunks: 3,
                // 若是cacheGroup中沒有設置minSize,則據此判斷是否使用上層的minSize,true:則使用0,false:使用上層minSize
                enforce: true,
                //test: 緩存組的規則,表示符合條件的的放入當前緩存組,值能夠是function、boolean、string、RegExp,默認爲空;
                test:""
            },
            //設置多個緩存規則
            vendor: {
                test: /node_modules/,
                chunks: "all",
                name: "vendor",
                //表示緩存的優先級
                priority: 10,
                enforce: true
            }
        }
    }
}
複製代碼

二、compilation.mainTemplate.applyPluginsWaterfall

再運行 npm run build, 報錯

building for production.../Users/xyz_persist/front_end/ts/vue-ts-init2/node_modules/html-webpack-plugin/lib/compiler.js:81
        var outputName = compilation.mainTemplate.applyPluginsWaterfall('asset-path', outputOptions.filename, {
                                                  ^

TypeError: compilation.mainTemplate.applyPluginsWaterfall is not a function
複製代碼

緣由:html-webpack-plugin未升級版本致使

解決:升級 html-webpack-plugin 版本

npm i html-webpack-plugin@3.2.0

npm i vue-loader@15.7.1
複製代碼

三、Error: Chunk.entrypoints: Use Chunks.groupsIterable and filter by instanceof Entrypoint instead

再運行 npm run build, 報錯

⠋ building for production...(node:13954) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
⠼ building for production...(node:13954) UnhandledPromiseRejectionWarning: Error: Chunk.entrypoints: Use Chunks.groupsIterable and filter by instanceof Entrypoint instead
複製代碼

緣由:extract-text-webpack-plugin還不能支持webpack4.0.0以上的版本

官網extract-text-webpack-plugin

解決:升級 extract-text-webpack-plugin 版本

npm i extract-text-webpack-plugin@next -D

注:extract-text-webpack-plugin做用:

將全部入口chunk中引用的.css抽離到獨立的css文件中,而再也不內嵌到JS bundle中。

若是樣式文件大小較大,會更快提早加載,由於CSS bundle 會跟 JS bundle並行加載

四、TypeError: Cannot read property 'eslint' of undefined

再運行 npm run build, 報錯

TypeError: Cannot read property 'eslint' of undefined
    at Object.module.exports (/Users/xyz_persist/front_end/ts/vue-ts-init2/node_modules/eslint-loader/index.js:148:18)


TypeError: Cannot read property 'vue' of undefined
    at Object.module.exports (/Users/xyz_persist/front_end/ts/vue-ts-init2/node_modules/vue-loader/lib/loader.js:61:18)
 @ ./src/main.js 4:0-24 13:21-24
 ......
複製代碼

緣由:eslint-loader、vue-loader版本問題

解決:

npm i eslint-loader@2.2.1 -D
npm i vue-loader@15.7.1 -D
複製代碼

五、Make sure to include VueLoaderPlugin in your webpack config

再運行 npm run build, 報錯

ERROR in ./src/App.vue
Module Error (from ./node_modules/vue-loader/lib/index.js):
vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.
 @ ./src/main.js 4:0-24 13:21-24

複製代碼

緣由:Vue-loader在15.*以後的版本都是 vue-loader的使用都是須要伴生 VueLoaderPlugin的

解決:在/build/webpack.base.conf.js中添加

const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
......
    plugins: [
         new VueLoaderPlugin()
    ],
}
複製代碼

六、Error: Path variable [contenthash] not implemented in this context: static/css/[name].[contenthash].css

再運行 npm run build, 報錯

緣由:webpack4.x中提取CSS文件應該使用mini-css-extract-plugin,廢棄extract-text-webpack-plugin

官網mini-css-extract-plugin

解決:在/build/webpack.prod.conf.js

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

...
plugins: [
    ...
    // new ExtractTextPlugin({
    // filename: utils.assetsPath('css/[name].[contenthash].css'),
    // allChunks: true,
    // }),
    new MiniCssExtractPlugin({
      filename: 'css/app.[name].css',
      chunkFilename: 'css/app.[contenthash:12].css'  // use contenthash *
    }),
]
複製代碼

再修改/build/utils.js文件

// const ExtractTextPlugin = require('extract-text-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
...
    if (options.extract) {
      // return ExtractTextPlugin.extract({
      // use: loaders,
      // fallback: 'vue-style-loader'
      // })
      // MiniCssExtractPlugin.loader,
      return [MiniCssExtractPlugin.loader].concat(loaders)
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
複製代碼

至此,npm run build已經可以正常打包,生產環境搞定

七、BaseClient.js?e917:12 Uncaught TypeError: Cannot assign to read only property 'exports' of object '#

運行 npm run dev, 命令行沒有報錯

打開瀏覽器,控制檯報錯

BaseClient.js?e917:12 Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'
    at Module.eval (BaseClient.js?e917:12)
    at eval (BaseClient.js:42)
    at Module../node_modules/webpack-dev-server/client/clients/BaseClient.js (app.js:2061)
    at __webpack_require__ (app.js:727)
    at fn (app.js:101)
    at Module.eval (SockJSClient.js?810f:26)
    at eval (SockJSClient.js:137)
    at Module../node_modules/webpack-dev-server/client/clients/SockJSClient.js (app.js:2073)
    at __webpack_require__ (app.js:727)
    at fn (app.js:101)
複製代碼

緣由:根據字面意思,猜想是解析JS的時候不認識 exports

解決:/build/webpack.base.conf.js 中

{
        test: /\.js$/,
        loader: 'babel-loader',
        // include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')],
        include: [resolve('src'), resolve('test')]
},
複製代碼

至此,npm run dev 編譯沒問題,開發環境搞定!

tips:

webpack4.x版本,其餘插件修改: NoEmitOnErrorsPlugin 廢棄,使用optimization.noEmitOnErrors替代,在生產環境中默認開啓 ModuleConcatenationPlugin 廢棄,使用optimization.concatenateModules替代,在生產環境默認開啓 NamedModulesPlugin 廢棄,使用optimization.namedModules替代,在生產環境默認開啓 uglifyjs-webpack-plugin升級到了v1.0版本,默認開啓緩存和並行功能 CommonsChunkPlugin 被刪除

八、總結

  • 用於進行文件分離的內置插件 webpack.optimize.CommonsChunkPlugin ,在Webpack 4.x中,咱們藉助於config.optimization來實現
  • 用於壓縮JS和CSS的內置插件webpack.optimize.UglifyJsPluginwebpack.optimize.OptimizeCSSPlugin ,在Webpack 4.x中都取消了,可是可以使用UglifyJsPluginOptimizeCSSPlugin插件來代替
  • 用於提取CSS到單獨文件的插件extract-text-webpack-plugin , 在webpack 4.x中則應該使用mini-css-extract-plugin
  • Webpack 4.x版本,Vue項目的vue-loader也必須更新到15.0以上版本

附:github.com/webpack-con…

三、Vue 引入 TypeScript

一、安裝插件

npm i vue-class-component vue-property-decorator --save
npm i ts-loader typescript tslint tslint-loader tslint-config-standard --save-dev
複製代碼

這些庫大致的做用:

  • vue-class-component:強化 Vue 組件,使用 TypeScript/裝飾器 加強 Vue 組件
  • vue-property-decorator:在 vue-class-component 上加強更多的結合 Vue 特性的裝飾器
  • ts-loader:TypeScript 爲 Webpack 提供了 ts-loader,其實就是爲了讓webpack識別 .ts .tsx文件
  • tslint-loadertslint:我想你也會在.ts .tsx文件 約束代碼格式(做用等同於eslint)
  • tslint-config-standardtslint 配置 standard風格的約束

二、配置 webpack

找到/build/webpack.base.conf.js

  • entry.appmain.js 改爲 main.ts,順便把項目文件中的main.js也改爲main.ts, 裏面內容保持不變
entry: {
  app: './src/main.ts'
}
複製代碼
  • 找到resolve.extensions 裏面加上.ts 後綴 (是爲了以後引入.ts的時候不寫後綴)
resolve: {
    extensions: ['.js', '.vue', '.json', '.ts'],
    alias: {
        '@': resolve('src')
    }
}
複製代碼
  • 找到module.rules 添加webpack對.ts的解析
module: {
  rules: [
    ....
		// 新增如下配置
    {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        use: [
          "babel-loader",
          {
            loader: "ts-loader",
            options: { appendTsxSuffixTo: [/\.vue$/] }
          },
          {
            loader: 'tslint-loader'
          }
        ]
     },
		....
  }
}
複製代碼

注:ts-loader 會檢索當前目錄下的 tsconfig.json 文件,根據裏面定義的規則來解析.ts文件(就跟.babelrc的做用同樣),tslint-loader 做用等同於 eslint-loader

三、添加 tsconfig.json

在根路徑下建立tsconfig.json文件,完整配置見tsconfig.json

這是我本地的配置

{
    "include": [
        "src/**/*"
    ],
    "exclude": [
        "node_modules"
    ],
    "compilerOptions": {
        // 解析非相對模塊名的基準目錄
        "baseUrl": ".",
        // 指定特殊模塊的路徑
        "paths": {
            "@/*": ["*", "src/*"]
        },
        "jsx": "preserve",
        "jsxFactory": "h",
        // 容許從沒有設置默認導出的模塊中默認導入
        "allowSyntheticDefaultImports": true,
        // 啓用裝飾器
        "experimentalDecorators": true,
        // 容許編譯javascript文件
        "allowJs": true,
        // 採用的模塊系統
        "module": "esnext",
        // 編譯輸出目標 ES 版本
        "target": "es5",
        // 如何處理模塊
        "moduleResolution": "node",
        // 將每一個文件做爲單獨的模塊
        "isolatedModules": true,
        // 編譯過程當中須要引入的庫文件的列表
        "lib": [
            "dom",
            "es5",
            "es6",
            "es7",
            "es2015.promise"
        ],
        "sourceMap": true,
        "pretty": true
    }
}
複製代碼

貼一份參考的配置

{
  // 編譯選項
  "compilerOptions": {
    // 輸出目錄
    "outDir": "./output",
    // 是否包含能夠用於 debug 的 sourceMap
    "sourceMap": true,
    // 以嚴格模式解析
    "strict": true,
    // 採用的模塊系統
    "module": "esnext",
    // 如何處理模塊
    "moduleResolution": "node",
    // 編譯輸出目標 ES 版本
    "target": "es5",
    // 容許從沒有設置默認導出的模塊中默認導入
    "allowSyntheticDefaultImports": true,
    // 將每一個文件做爲單獨的模塊
    "isolatedModules": false,
    // 啓用裝飾器
    "experimentalDecorators": true,
    // 啓用設計類型元數據(用於反射)
    "emitDecoratorMetadata": true,
    // 在表達式和聲明上有隱含的any類型時報錯
    "noImplicitAny": false,
    // 不是函數的全部返回路徑都有返回值時報錯。
    "noImplicitReturns": true,
    // 從 tslib 導入外部幫助庫: 好比__extends,__rest等
    "importHelpers": true,
    // 編譯過程當中打印文件名
    "listFiles": true,
    // 移除註釋
    "removeComments": true,
    "suppressImplicitAnyIndexErrors": true,
    // 容許編譯javascript文件
    "allowJs": true,
    // 解析非相對模塊名的基準目錄
    "baseUrl": "./",
    // 指定特殊模塊的路徑
    "paths": {
      "jquery": [
        "node_modules/jquery/dist/jquery"
      ]
    },
    // 編譯過程當中須要引入的庫文件的列表
    "lib": [
      "dom",
      "es2015",
      "es2015.promise"
    ]
  }
}
複製代碼

四、添加 tslint.json

在根路徑下建立tslint.json文件,就是 引入 tsstandard 規範

{
    "defaultSeverity": "error",
    "extends": "tslint-config-standard",
    "globals": {
        "require": true
    },
    "rules": {
        "space-before-function-paren": false,
        "whitespace": [false],
        "no-consecutive-blank-lines": false,
        "no-angle-bracket-type-assertion": false,
        "no-empty-character-class": false
    }
}
複製代碼

五、讓 ts 識別 .vue

因爲 TypeScript 默認並不支持 *.vue 後綴的文件,因此在 vue 項目中引入的時候須要建立一個 vue-shim.d.ts 文件,放在項目項目對應使用目錄下,例如 src/vue-shim.d.ts

// 意思是告訴 TypeScript *.vue 後綴的文件能夠交給 vue 模塊來處理
import Vue from "vue";
import lodash from "lodash";
// 重要***
declare module '*.vue' {
    export default Vue
}

declare module 'vue/types/vue' {
    interface Vue {
        $Message: any,
        $Modal: any
    }
}

declare global {
    const _: typeof lodash
}

複製代碼

而在代碼中導入 *.vue 文件的時候,須要寫上 .vue 後綴。緣由仍是由於 TypeScript 默認只識別 *.ts 文件,不識別 *.vue 文件

import HelloWorld from '@/components/HelloWorld.vue'

六、改造 .vue 文件

在這以前先讓咱們瞭解一下所須要的插件(下面的內容須要掌握es7裝飾器, 就是下面使用的@符號)

vue-class-component

vue-class-componentVue 組件進行了一層封裝,讓 Vue 組件語法在結合了 TypeScript 語法以後更加扁平化:

<template>
  <div>
    <input v-model="msg">
    <p>msg: {{ msg }}</p>
    <p>computed msg: {{ computedMsg }}</p>
    <button @click="greet">Greet</button>
  </div>
</template>

<script lang="ts">
  import Vue from 'vue'
  import Component from 'vue-class-component'

  @Component
  export default class App extends Vue {
    // 初始化數據
    msg = 123

    // 聲明週期鉤子
    mounted () {
      this.greet()
    }

    // 計算屬性
    get computedMsg () {
      return 'computed ' + this.msg
    }

    // 方法
    greet () {
      alert('greeting: ' + this.msg)
    }
  }
</script>
複製代碼

上面的代碼跟下面的代碼做用是同樣的

export default {
  data () {
    return {
      msg: 123
    }
  }

  // 聲明週期鉤子
  mounted () {
    this.greet()
  }

  // 計算屬性
  computed: {
    computedMsg () {
      return 'computed ' + this.msg
    }
  }

  // 方法
  methods: {
    greet () {
      alert('greeting: ' + this.msg)
    }
  }
}
複製代碼
vue-property-decorator

vue-property-decorator 是在 vue-class-component 上加強了更多的結合 Vue 特性的裝飾器,新增了這 7 個裝飾器:

  • @Emit
  • @Inject
  • @Model
  • @Prop
  • @Provide
  • @Watch
  • @Component (從 vue-class-component 繼承)

在這裏列舉幾個經常使用的@Prop/@Watch/@Component, 更多信息,詳見官方文檔

import { Component, Emit, Inject, Model, Prop, Provide, Vue, Watch } from 'vue-property-decorator'

@Component
export class MyComponent extends Vue {
  
  @Prop()
  propA: number = 1

  @Prop({ default: 'default value' })
  propB: string

  @Prop([String, Boolean])
  propC: string | boolean

  @Prop({ type: null })
  propD: any

  @Watch('child')
  onChildChanged(val: string, oldVal: string) { }
}
複製代碼

上面的代碼至關於:

export default {
  props: {
    checked: Boolean,
    propA: Number,
    propB: {
      type: String,
      default: 'default value'
    },
    propC: [String, Boolean],
    propD: { type: null }
  }
  methods: {
    onChildChanged(val, oldVal) { }
  },
  watch: {
    'child': {
      handler: 'onChildChanged',
      immediate: false,
      deep: false
    }
  }
}
複製代碼
開始修改App.vue文件
  1. script 標籤上加上 lang="ts", 意思是讓webpack將這段代碼識別爲typescript 而非javascript
  2. 修改vue組件的構造方式,詳見官方
  3. vue-property-decorator語法改造以前代碼

以下:

<script lang="ts">
  import Vue from 'vue'
  import Component from 'vue-class-component'

  @Component({})
  export default class App extends Vue {}

</script>
複製代碼
開始修改HelloWorld.vue

以下:

<template>
  <div class="hello">
    <h1>{{ message }}</h1>
    <h2>Essential Links</h2>
    <button @click="onClick">Click!</button>
  </div>
</template>

<script lang="ts">
  import Vue from 'vue'
  import Component from 'vue-class-component'

  @Component({
    // 全部的組件選項均可以放在這裏
  })
  export default class HelloWorld extends Vue {
    // 初始數據能夠直接聲明爲實例的屬性
    message: string = 'Welcome to Your Vue.js + TypeScript App'

    // 組件方法也能夠直接聲明爲實例的方法
    onClick (): void {
      console.log(this.message)
      console.log(_)
    }
  }
</script>
複製代碼

至此,運行npm run dev npm run build 都能正常跑起來了

四、進階TS配置

一、支持es6 / es7

tsconfig.json中,添加對es6 / es7的支持,更多的配置請見tsconfig - 編譯選項

"lib": [
      "dom",
      "es5",
      "es6",
      "es7",
      "es2015.promise"
    ]
複製代碼

二、讓 vue 識別全局方法/變量

在建立的 src/vue-shim.d.ts文件中,增長以下代碼

// 聲明全局方法
declare module 'vue/types/vue' {
  interface Vue {
    $Message: any,
    $Modal: any
  }
}
複製代碼

這樣,以後再使用this.$message()的話就不會報錯了

三、支持 ProvidePlugin 的全局變量,好比 lodash 的 _

若是咱們在項目中有使用 jquery,lodash 這樣的工具庫的時候,確定不但願在全部用到的地方都import _ from ‘lodash’

那咱們就來配置一下:

首先仍是在webpack.base.conf.js 中添加一個插件、並把這個 vendor拉出來

entry: {
    app: './src/main.ts',
    vendor: [
      "lodash"
    ]
  }

  plugins: [
    new webpack.ProvidePlugin({
      _: 'lodash'
    })
  ]
複製代碼

上面的意思是,當模塊使用這些變量的時候wepback會自動加載

而後,你須要告訴eslint這個 _ 是全局的

.eslintrc.js中添加

globals: {
    _: true
  },
複製代碼

接下來,你還須要告訴ts這個 _ 是全局的

vue-shim.d.ts

declare global {
  const _: typeof lodash
}
複製代碼

這樣,在其餘地方就能夠用_直接調用啦

四、配置 vuex

# 安裝依賴
npm i vuex vuex-class --save
複製代碼
  • vuex:在 vue 中集中管理應用狀態
  • vuex-class :在 vue-class-component 寫法中 綁定 vuex

最後

到這裏就結束了最基礎的配置啦,但願能夠幫到你噢!

參考

vue + typescript 進階篇

TypeScript-Vue-Starter

相關文章
相關標籤/搜索