近期有些小伙伴向我咨询了如何更好的用typescript
去写Vue
,我作为一个后端工程师表示很难通过口头说明白,于是准备了下面这个小案例。但我毕竟只是个后端工程师,有些不够“前端”的写法还请前端大佬多多理解。
以下例子的代码已托管在 Github:https://github.com/LuckyStarry/typescript-vue-sample
npm init
首先我们使用上述命令将工作目录初始化,或手动在工作目录中创建package.json
文件,类似于下述形式。
{
"name": "typescript-vue-sample",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/LuckyStarry/typescript-vue-sample.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/LuckyStarry/typescript-vue-sample/issues"
},
"homepage": "https://github.com/LuckyStarry/typescript-vue-sample#readme"
}
npm i vue vue-property-decorator typescript -S
npm i webpack webpack-merge webpack-dev-server webpack-encoding-plugin webpack-cleanup-plugin webpack-cli vue-loader vue-template-compiler html-webpack-plugin mini-css-extract-plugin uglifyjs-webpack-plugin optimize-css-assets-webpack-plugin babel-loader tslint-loader url-loader ts-loader css-loader less-loader less tslint tslint-config-standard @babel/core babel-preset-env -D
├─.vscode
│ └─settings.json
├──build
│ ├─webpack.config.js
│ ├─webpack.debug.config.js
│ └─webpack.release.config.js
├──output
│ ├─debug
│ │ └─index.html
│ └─release
├──src
│ ├─views
│ │ ├─app
│ │ │ ├─app.less
│ │ │ ├─app.ts
│ │ │ ├─app.vue
│ │ │ └─index.ts
│ │ └─index.ejs
│ ├─main.ts
│ └─vendors.ts
├─.gitignore
├─.jsbeautifyrc
├─.prettierrc
├──package.json
├──tsconfig.json
└──tslint.json
tsconfig.json
{
"include": ["src/**/*"],
"exclude": ["node_modules"],
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"allowJs": false,
"jsx": "preserve",
"module": "esnext",
"target": "es5",
"moduleResolution": "node",
"isolatedModules": false,
"lib": ["dom", "es5", "es2015"],
"sourceMap": true,
"pretty": true,
"baseUrl": "src"
}
}
tslint.json
{
"extends": "tslint-config-standard",
"globals": {
"require": true
},
"rules": {
"class-name": false,
"space-before-function-paren": false,
"member-ordering": false,
"no-return-await": false,
"ter-func-call-spacing": false
}
}
webpack.config.js
const path = require("path");
const EncodingPlugin = require("webpack-encoding-plugin");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
module.exports = {
entry: {
main: "./src/main",
vendors: "./src/vendors"
},
output: {
// eslint-disable-next-line no-undef
path: path.join(__dirname, "../output/dist")
},
module: {
rules: [
{
enforce: "pre",
test: /\.ts(x?)$/,
exclude: /node_modules/,
loader: "tslint-loader"
},
{
test: /\.ts(x?)$/,
exclude: /node_modules/,
use: ["babel-loader", "ts-loader"]
},
{
test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/,
loader: "url-loader?limit=8192"
}
]
},
plugins: [new EncodingPlugin({ encoding: "utf-8" }), new VueLoaderPlugin()],
resolve: {
extensions: [".ts", ".js", ".vue"],
alias: { views: path.resolve(__dirname, "../src/views") }
}
};
webpack.debug.config.js
const path = require("path");
const webpack = require("webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const WebpackMerge = require("webpack-merge");
const webpackConfig = require("./webpack.config");
module.exports = WebpackMerge(webpackConfig, {
devtool: "#source-map",
mode: "development",
output: {
// eslint-disable-next-line no-undef
path: path.join(__dirname, "../output/debug/dist"),
publicPath: "/dist/",
filename: "[name].js",
chunkFilename: "[name].chunk.js"
},
module: {
rules: [
{ test: /\.vue$/, loader: "vue-loader" },
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] },
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"]
}
]
},
plugins: [
new MiniCssExtractPlugin({ filename: "[name].css" }),
new webpack.HotModuleReplacementPlugin()
],
externals: { vue: "Vue" },
devServer: {
contentBase: "./output/debug/",
compress: true,
port: 30020,
host: "0.0.0.0",
disableHostCheck: true,
hot: true,
inline: true,
historyApiFallback: true
}
});
webpack.release.config.js
const path = require("path");
const package = require("../package.json");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const WebpackCleanupPlugin = require("webpack-cleanup-plugin");
const WebpackMerge = require("webpack-merge");
const webpackConfig = require("./webpack.config");
module.exports = WebpackMerge(webpackConfig, {
mode: "production",
output: {
// eslint-disable-next-line no-undef
path: path.join(__dirname, "../output/release/dist"),
publicPath: `/dist`,
filename: "[name].[hash].js",
chunkFilename: "[name].[hash].chunk.js"
},
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader",
options: { loaders: { ts: ["babel-loader", "ts-loader"] } }
},
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] },
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
default: false,
common: false,
styles: {
name: "main",
test: /\.(css|less)$/,
chunks: "all",
minChunks: 1,
enforce: true
}
}
},
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true
}),
new OptimizeCSSAssetsPlugin({})
]
},
plugins: [
new WebpackCleanupPlugin(),
new MiniCssExtractPlugin({ filename: "[name].[hash].css" }),
new HtmlWebpackPlugin({
// eslint-disable-next-line no-undef
filename: path.join(__dirname, "../output/release/index.html"),
template: "./src/views/index.ejs",
inject: false
})
],
externals: { vue: "Vue" }
});
package.json
中的脚本节点{
"scripts": {
"dev": "webpack-dev-server --mode development --config build/webpack.debug.config.js",
"build": "webpack --progress --config build/webpack.release.config.js"
}
}
Babel 规则文件.babelrc
{
"presets": [["env"]]
}
除了将代码文件后缀由 .js
改为 .ts
之外,使用 TypeScript
代码稍微还有些要注意的地方。
首先就是为了能够让编译器正常的识别 .vue
文件,我们需要在 src
目录下新增一个 vue-shim.d.ts
文件,其内容为
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}
.vue
文件除了可以写 HTML 模板外,往往我们还会把 <style>
及 <script>
一起写入这个文件,虽然 Vue 模板编译器可以正常的运行代码,但是代码格式化功能往往无法正常运行。
所以大家可以尝试将一个 .vue
文件分解为 三个文件 —— 代码 .ts
、样式 .less
、模板 .vue
。就像例子中的 App
/* app.less */
h1 {
color: red;
}
// app.ts
import { Vue } from "vue-property-decorator";
export default class App extends Vue {}
<!-- app.vue -->
<style lang="less">
@import url("./app.less");
</style>
<template>
<h1>Hello World</h1>
</template>
<script type="ts" src="./app.ts" />