跳到主要内容

vue3-typescript

2024年04月29日
柏拉文
越努力,越幸运

::: tip 前言 仓库 :::

介绍

模板特色:

  • 基于Webpack搭建
  • 基于Vue 3.0
  • 基于TypeScript
  • 提供两种Axios工业级封装方案
  • 提供了Esling+Prettier结合的工业级代码规范

实践

一、 构建项目

1. yarn init -y 初始化项目

2. 根据以下目录结构,构建目录

:::details 点击查看目录

├─config
| |-utils
| |- env.ts
│ ├─webpack.config.base.js
│ ├─webpack.config.dev.js
│ ├─webpack.config.prod.js
├─node_modules
├─public
| |-index.html
└─src
| ├─api
| ├─assets
| ├─components
| ├─router
| ├─store
| ├─style
| ├─pages
| ├─utils
| |-App.vue
| |-index.ts
| |-shims-api.d.ts
| |-shims-suffix.d.ts
|-.babelrc
|-.editorconfig
|-.env.dev
|-.env.prod
|-.eslintignore
|-.eslintrc
|-.gitigore
|-package.json
|-tsconfig.json

:::

3. 编辑 .gitigore, 将 node_modules 去除文件追踪

:::details .gitigore

node_modules/

:::

二、Vue 配置

::: warning 依赖

  • yarn add axios @types/axios lodash @type/lodash -S

  • yarn add vue@next vue-router@next vuex@next @vue/compiler-sfc -S :::

  • 根目录新建src

  • src下新建index.ts

    :::details 点击查看代码

    import { createApp } from 'vue';
    import App from '@/App.vue';
    import store from '@/store';
    import router from '@/router';
    import 'styles/index.scss';

    const app = createApp(App);
    app.use(router).use(store).mount('#root');

    :::

  • src下新建App.vue

    :::details 点击查看代码

    <template>
    <div id="app">
    <router-view />
    </div>
    </template>

    <script lang="ts">
    import { defineComponent } from 'vue';

    export default defineComponent({
    name: 'App',
    });
    </script>
    <style lang="scss">
    #app {
    text-align: center;
    }
    </style>

    :::

  • src下新建router

  • router下新建index.ts

    :::details 点击查看代码

    import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
    import Home from '../pages/Home.vue';

    const routes: Array<RouteRecordRaw> = [
    {
    path: '/',
    name: 'Home',
    component: Home,
    },
    ];

    const router = createRouter({
    history: createWebHistory(process.env.BASE_URL),
    routes,
    });

    export default router;

    :::

  • src下新建store

  • store下新建index.ts

    :::details 点击查看代码

    import { createStore } from 'vuex';

    export default createStore({
    state: {},
    mutations: {},
    actions: {},
    modules: {},
    });

    :::

  • src下新建pages

  • pages下新建Home.vue

    :::details 点击查看代码

    <template>
    <h1>柏拉文模板</h1>
    <div>
    <img src="../assets/images/avatar.png" alt="" />
    </div>
    <div>
    <h1>功能特色</h1>
    <ul>
    <li>基于 Vue3.0</li>
    <li>基于 TypeScript</li>
    <li>基于 Webpack 5.0</li>
    <li>两套 axios 封装方案</li>
    <li>Eslint 与 Prettier 相结合的工业级代码规范</li>
    </ul>
    </div>
    </template>

    <script lang="ts">
    import { defineComponent, ref } from 'vue';

    export default defineComponent({
    name: 'Home',
    setup() {},
    });
    </script>

    <style lang="scss" scoped>
    img {
    width: 400px;
    height: 400px;
    }
    </style>

    :::

  • src下新建styles

  • styles下新建index.scss

    :::details 点击查看代码

    @import url(./reset.scss);
    html{
    width:100%;
    height:100%;
    }
    body{
    width: 100%;
    height:100%;
    margin:0;
    padding:0;
    }

    :::

  • styles下新建reset.scss

    :::details 点击查看代码

    ul{
    list-style: none;
    }
    li{
    list-style: none;
    }

    :::

三、Webpack 配置

::: warning 细节

  • 静态资源配置

  • Alias 别名配置

  • Css-Loader 和 Sass-Loader 配置

  • 环境变量配置: 通过dotenv来按需加载不同的环境变量。(Vue-Cli)的环境变量也是使用的这个插件。

  • Babel-Loader 配置: 通过webpack配置Babel-LoaderBabel-Loader自身的配置通过新建的babel.config.js配置 :::

::: warning 依赖

  • yarn add dotenv -D

  • yarn add ts-loader -D

  • yarn add vue-loader -D

  • yarn add css-loader style-loader sass sass-loader autoprefixer style-loader postcss-loader -D

  • yarn add webapck webpack-cli html-webpack-plugin clean-webpack-plugin webpack-dev-server webpack-merge friendly-errors-webpack-plugin eslint-webpack-plugin cross-env -D :::

  • 详细 Webpack 配置

  • 根目录新建config

  • config下新建utils

  • utils下新建env.js

    :::details 点击查看代码

    const envMode = process.env.envMode;
    require('dotenv').config({ path: `.env.${envMode}` });
    const prefixRE = /^VUE_APP_/;
    let env = {};
    for (const key in process.env) {
    if (key == 'NODE_ENV' || key == 'BASE_URL' || key == 'REQUEST_URL' || prefixRE.test(key)) {
    env[key] = JSON.stringify(process.env[key]);
    }
    }

    module.exports = env;

    :::

  • 根目录新建.env.dev

    :::details 点击查看代码

    BASE_URL=/
    REQUEST_URL=/api
    NODE_ENV=development

    :::

  • 根目录新建.env.prod

    :::details 点击查看代码

    BASE_URL=/
    NODE_ENV=production

    :::

  • config目录新建webpack.config.base.js

    :::details 点击查看代码

    const Path = require('path');
    const Webpack = require('webpack');
    const ENV = require('./utils/env');
    const ESLintPlugin = require('eslint-webpack-plugin');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const { VueLoaderPlugin } = require('vue-loader/dist/index');
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');

    module.exports = {
    cache: {
    type: 'filesystem',
    },
    entry: Path.resolve(process.cwd(), './src/index.ts'),
    output: {
    path: Path.resolve(process.cwd(), 'build'),
    filename: 'js/index.js',
    },
    resolve: {
    alias: {
    '@': Path.resolve(process.cwd(), './src'),
    "js": Path.resolve(process.cwd(), './src/assets/js'),
    "api": Path.resolve(process.cwd(), './src/api'),
    "css": Path.resolve(process.cwd(), './src/assets/css'),
    "scss": Path.resolve(process.cwd(), './src/assets/scss'),
    "utils": Path.resolve(process.cwd(), './src/utils'),
    "images": Path.resolve(process.cwd(), './src/assets/images'),
    "assets": Path.resolve(process.cwd(), './src/assets'),
    "styles": Path.resolve(process.cwd(), './src/styles'),
    },
    extensions: ['.vue', '.js', '.ts'],
    },
    module: {
    rules: [
    {
    test: /\.vue$/,
    loader: 'vue-loader',
    exclude: /node_modules/,
    },
    {
    test: /\.(css|scss)$/,
    exclude: /node_modules/,
    use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
    },
    {
    test: /\.(js|jsx)$/,
    loader: 'babel-loader',
    exclude: /node_modules/,
    },
    {
    test: /\.(ts|tsx)$/,
    loader: 'ts-loader',
    exclude: /node_modules/,
    options: {
    appendTsSuffixTo: [/\.vue$/],
    },
    },
    {
    test: /\.(png|svg|jpe?g|gif)$/i,
    type: 'asset',
    generator: {
    filename: 'images/[name]-[hash][ext]',
    },
    },
    {
    test: /\.(eot|svg|ttf|woff2?|)$/i,
    type: 'asset/resource',
    generator: {
    filename: 'fonts/[name]-[hash][ext]',
    },
    },
    ],
    },
    plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
    template: Path.resolve(process.cwd(), './public/index.html'),
    filename: 'index.html',
    title: 'Webpack-Vue3-TypeScript',
    }),
    new CleanWebpackPlugin(),
    new Webpack.DefinePlugin({
    'process.env': {
    ...ENV,
    },
    }),
    new FriendlyErrorsWebpackPlugin(),
    new ESLintPlugin({ extensions: ['js', 'ts', 'vue'] }),
    ],
    };

    :::

  • config目录新建webpack.config.dev.js

    :::details 点击查看代码

    const Path = require('path');
    const Base = require('./webpack.base');
    const Merge = require('webpack-merge');

    const Dev = {
    mode: 'development',
    devServer: {
    port: 8090,
    proxy: {
    '/api': {
    target: 'http://localhost:4000',
    pathRewrite: {
    '^/api': '',
    },
    },
    },
    static: {
    publicPath: '/',
    directory: Path.resolve(process.cwd(), './build/'),
    },
    },
    };
    module.exports = Merge.merge(Base, Dev);

    :::

  • config目录新建webpack.config.prod.js

    :::details 点击查看代码

    const Base = require('./webpack.base');
    const Merge = require('webpack-merge');

    const Prod = {
    mode: 'production',
    };
    module.exports = Merge.merge(Base, Prod);

    :::

四、Babel 额外配置

::: warning 依赖

  • yarn add core.js -D

  • yarn add @babel/core -D

  • yarn add babel-loader -D

  • yarn add @babel/plugin-transform-runtime -D

  • yarn add @babel/preset-env -D
    :::

  • 详细了解 Babel 配置

  • 根目录新增.babelrc,配置如下:

    :::details 点击查看代码

    {
    "presets": [
    [
    "@babel/preset-env",
    {
    "modules": false,
    "targets": {
    "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
    },
    "useBuiltIns": "usage",
    "corejs": 3
    }
    ],
    "@vue/babel-preset-jsx"
    ],
    "plugins": ["@babel/plugin-transform-runtime"]
    }

    :::

五、TypeScript 配置

::: warning 依赖

  • yarn add ts-loader -D

  • yarn add typescript -D

  • yarn add @types/其他依赖 :::

  • 详细了解 TypeScript 配置

  • 根目录新增tsconfig.json,配置如下:

    :::details 点击查看代码

    {
    "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": false,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": "./",
    "types": ["webpack-env"],
    "paths": {
    "@/*": ["src/*"],
    "js/*": ["src/assets/js/*"],
    "api/*": ["src/api/*"],
    "css/*": ["src/assets/css/*"],
    "scss/*": ["src/assets/scss/*"],
    "utils/*": ["src/utils/*"],
    "assets/*": ["src/assets/*"],
    "images/*": ["src/assets/images/*"],
    "styles/*": ["src/styles/*"]
    },
    "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
    },
    "include": ["src/**/*.js", "src/**/*.ts", "src/**/*.jsx", "src/**/*.tsx", "src/**/*.vue"],
    "exclude": ["node_modules"]
    }

    :::

  • 根据typescript.json中的include配置,在src目录下新增shims-api.d.ts。配置如下:

    :::details 点击查看代码

    interface UserInfoTypes {
    username: string;
    age: number;
    sex: string;
    }

    :::

  • 根据typescript.json中的include配置,在src目录下新增shims-suffix.d.ts。配置如下:

    :::details 点击查看代码

    /* eslint-disable */
    declare module '*.vue' {
    import type { DefineComponent } from 'vue'
    const component: DefineComponent<{}, {}, any>
    export default component
    }

    declare module '*.scss'
    declare module '*.svg'
    declare module '*.png'
    declare module '*.jpg'
    declare module '*.jpeg'
    declare module '*.gif'
    declare module '*.bmp'
    declare module '*.tiff'

    :::

六、Eslint 与 Prettier 配置

::: warning 依赖

  • yarn add eslint@7.5.0 -D 了解 eslint

  • yarn add prettier@2.3.2 -D 了解 prettier

  • yarn add eslint-plugin-vue@7.16.0 -D 了解 eslint-plugin-vue

  • yarn add eslint-webpack-plugin@3.1.0 -D 了解 eslint-webpack-plugin

  • yarn add eslint-plugin-import@2.24.0 -D 了解 eslint-plugin-import

  • yarn add eslint-config-prettier@8.3.0 -D 了解 eslint-config-prettier

  • yarn add eslint-plugin-prettier@3.4.0 -D 了解 eslint-plugin-prettier

  • yarn add @typescript-eslint/parser@4.29.1 -D 了解 @typescript-eslint/parser

  • yarn add @vue/eslint-config-prettier@6.0.0 -D 了解 @vue/eslint-config-prettier

  • yarn add eslint-import-resolver-alias@1.1.2 -D 了解 eslint-import-resolver-alias

  • yarn add @vue/eslint-config-typescript@7.0.0 -D 了解 @vue/eslint-config-typescript

  • yarn add eslint-config-airbnb-typescript@12.3.1 -D 了解 eslint-config-airbnb-typescript

  • yarn add @typescript-eslint/eslint-plugin@4.29.1 -D 了解 @typescript-eslint/eslint-plugin :::

  • 根目录新增.eslintrc.js,配置如下:

    :::details 点击查看代码

    module.exports = {
    root: true,
    env: {
    browser: true,
    node: true,
    },
    extends: [
    'plugin:vue/vue3-recommended',
    'airbnb-typescript/base',
    '@vue/typescript/recommended',
    '@vue/prettier',
    '@vue/prettier/@typescript-eslint',
    ],
    parserOptions: {
    ecmaVersion: 2020,
    project: './tsconfig.json',
    },
    rules: {
    'no-console': 0,
    'no-plusplus': 0,
    'no-param-reassign': 0,
    'import/extensions': [
    'error',
    {
    ignorePackages: true,
    pattern: {
    js: 'never',
    ts: 'never',
    jsx: 'never',
    tsx: 'never',
    vue: 'always',
    jpg: 'always',
    json: 'always',
    scss: 'always',
    },
    },
    ],
    '@typescript-eslint/no-unused-vars': 0,
    '@typescript-eslint/no-inferrable-types': 0,
    '@typescript-eslint/explicit-module-boundary-types': 0,
    },
    settings: {
    'import/extensions': ['.js', '.jsx', '.ts', '.tsx', '.scss'],
    'import/parsers': {
    '@typescript-eslint/parser': ['.ts', '.tsx'],
    },
    'import/resolver': {
    alias: {
    map: [
    ['@', './src/'],
    ['js', './src/assets/js/'],
    ['api', './src/api/'],
    ['css', './src/assets/css/'],
    ['scss', './src/assets/scss/'],
    ['utils', './src/utils/'],
    ['assets', './src/store/'],
    ['images', './src/assets/images/'],
    ['styles', './src/styles/'],
    ],
    extensions: ['.ts', '.js', '.jsx', '.json', '.scss'],
    },
    node: {
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
    },
    },
    },
    };

    :::

  • 根目录新增.eslintignore,配置如下:

    :::details 点击查看代码

    build/*
    config/*
    .eslintrc.js

    :::

  • 根目录新增.prettierrc.js,配置如下:

    :::details 点击查看代码

    module.exports = {
    "printWidth": 120,
    "tabWidth": 4,
    "singleQuote": true,
    "semi": true,
    "useTabs": false
    }

    :::

七、VsCode 基于项目配置

::: warning 细节

  1. 配置 .settings.json 的作用: 基于项目的 vs code 配置

  2. 配置 vue.code-snippets 的作用: 配置好 vue 代码片段后,后续在 vs code 编写代码时,可以快速使用定义好的模板 :::

  • 根目录新建.vscode目录

  • .vscode目录下新建settings.json,配置如下:

    :::details 点击查看代码

    {

    }

    :::

  • .vscode目录下新建vue.code-snippets,配置如下:

    :::details 点击查看代码

    {
    "Print2 to console": {
    "prefix": "vue2",
    "body": [
    "<template>",
    "\t<div class=\"\">",
    "\t</div>",
    "</template>",
    "",
    "<script>",
    "export default{",
    "\tcomponents:{},",
    "\tdata(){",
    "\t\treturn {",
    "\t\t}",
    "\t},",
    "\twatch:{},",
    "\tmounted(){},",
    "\tmethods:{},",
    "}",
    "</script>",
    "",
    "<style lang=\"scss\" scoped>",
    "</style>"
    ],
    "description": "vue2 模版"
    },
    "Print3 to console": {
    "prefix": "vue3",
    "body": [
    "<template>",
    "\t<div class=\"\">",
    "\t</div>",
    "</template>",
    "",
    "<script lang=\"ts\">",
    "import { defineComponent } from 'vue'",
    "export default defineComponent({",
    "\tname:'',",
    "\tsetup(){",
    "\t}",
    " })",
    "</script>",
    "",
    "<style lang=\"scss\" scoped>",
    "</style>"
    ],
    "description": "vue3 模版"
    }
    }

    :::

  • 根目录新增.editorconfig,配置如下:

    :::details 点击查看代码

    root = true

    [*]
    charset = utf-8
    indent_style = space
    indent_size = 2
    end_of_line = lf
    insert_final_newline = true
    trim_trailing_whitespace = true
    in_empty_tag = true

    :::

八、package.json 命令配置与依赖版本

:::details 点击查看代码

{
"scripts": {
"dev": "cross-env envMode=dev webpack serve --progress --config ./config/webpack.dev.js",
"build": "cross-env envMode=prod webpack --config ./config/webpack.prod.js"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/plugin-transform-runtime": "^7.16.0",
"@babel/preset-env": "^7.16.0",
"@types/webpack-env": "^1.16.3",
"@typescript-eslint/eslint-plugin": "4.29.1",
"@typescript-eslint/parser": "4.29.1",
"@vue/babel-preset-jsx": "^1.2.4",
"@vue/eslint-config-prettier": "6.0.0",
"@vue/eslint-config-typescript": "7.0.0",
"autoprefixer": "^10.4.0",
"babel-loader": "^8.2.3",
"clean-webpack-plugin": "^4.0.0",
"cross-env": "^7.0.3",
"css-loader": "^6.5.1",
"dotenv": "^10.0.0",
"eslint": "7.5.0",
"eslint-config-airbnb-typescript": "12.3.1",
"eslint-config-prettier": "8.3.0",
"eslint-import-resolver-alias": "1.1.2",
"eslint-plugin-import": "2.24.0",
"eslint-plugin-prettier": "3.4.0",
"eslint-plugin-vue": "7.16.0",
"eslint-webpack-plugin": "^3.1.0",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^5.5.0",
"postcss-loader": "^6.2.0",
"prettier": "2.3.2",
"sass": "^1.43.4",
"sass-loader": "^12.3.0",
"style-loader": "^3.3.1",
"ts-loader": "^9.2.6",
"typescript": "^4.3.0",
"vue-loader": "^16.8.3",
"webpack": "^5.63.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.4.0",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@types/axios": "^0.14.0",
"@types/lodash": "^4.14.176",
"@vue/compiler-sfc": "^3.2.21",
"axios": "^0.24.0",
"lodash": "^4.17.21",
"vue": "^3.2.21",
"vue-router": "^4.0.12",
"vuex": "^4.0.2"
}
}

:::

问题

文件引用别名 问题

TypeScript项目中,如果需要使用别名,需要同时修改.eslintrc.js,tsconfig.json,webpack.base.js 三个文件

:::details .eslintrc.js

'import/resolver': {
alias: {
map: [
['@', './src/'],
['js', './src/assets/js/'],
['api', './src/api/'],
['css', './src/assets/css/'],
['scss', './src/assets/scss/'],
['utils', './src/utils/'],
['assets', './src/store/'],
['images', './src/assets/images/'],
['styles', './src/styles/'],
],
extensions: ['.ts', '.js', '.jsx', '.json', '.scss'],
}

:::

:::details tsconfig.json

"paths": {
"@/*": ["src/*"],
"js/*": ["src/assets/js/*"],
"api/*": ["src/api/*"],
"css/*": ["src/assets/css/*"],
"scss/*": ["src/assets/scss/*"],
"utils/*": ["src/utils/*"],
"assets/*": ["src/assets/*"],
"images/*": ["src/assets/images/*"],
"styles/*": ["src/styles/*"]
},

:::

:::details 点击查看代码

resolve: {
alias: {
'@': Path.resolve(process.cwd(), './src'),
"js": Path.resolve(process.cwd(), './src/assets/js'),
"api": Path.resolve(process.cwd(), './src/api'),
"css": Path.resolve(process.cwd(), './src/assets/css'),
"scss": Path.resolve(process.cwd(), './src/assets/scss'),
"utils": Path.resolve(process.cwd(), './src/utils'),
"images": Path.resolve(process.cwd(), './src/assets/images'),
"assets": Path.resolve(process.cwd(), './src/assets'),
"styles": Path.resolve(process.cwd(), './src/styles'),
}
}

:::

webpack-env 问题

Cannot find type definition file for 'webpack-env'. 找不到‘webpack-环境’的类型定义文件

解决:

  • 方案一: 直接去除webpack-env属性即可
  • 方案二: 安装对应的TypeScriptwebpack-env依赖 yarn add @types/webpack-env -D

Parameter ‘XXX‘ 问题

Parameter ‘XXX’ implicitly has an ‘any’ type

解决:

  • 方案一: tsconfig.json添加"noImplicitAny": false
  • 方案二: tsconfig.json“strict”: true,改为false

import/extensions 问题

Missing file extension "ts" for "./lib/env" import/extensions

解决:.eslintrc.js 文件中增加对应后缀名

:::details 点击查看代码

'import/extensions': [
'error',
{
ignorePackages: true,
pattern: {
js: 'never',
ts: 'never',
jsx: 'never',
tsx: 'never',
vue: 'always',
jpg: 'always',
json: 'always',
scss: 'always',
},
},
],

:::

type declarations 问题

Cannot find module './app.vue' or its corresponding type declarations

解决: src目录下新建文件shims-suffix.d , 添加如下代码

:::details 点击查看代码

/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

:::

Cannot find module ‘xxx‘ or its corresponding type declarations

解决: src目录下新建.d.ts后缀文件,文件中声明对应 module , 并且这个文件只能放置在tsconfig.json文件中的include属性配置的文件夹下。只有这样这类文件和.ts文件才可以编译

:::details .d.ts

declare module '*.scss'
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'

:::

:::details ts.config.json

"include": ["src/**/*.js", "src/**/*.ts", "src/**/*.jsx", "src/**/*.tsx", "src/**/*.vue"],

:::