跳到主要内容

vue3-typescript-existed

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

基于 Vite+Vue3.0+TypeScript 搭建项目

构建项目

yarn create vite 项目名称

编辑器配置

settings.json 文件配置


{
//自动换行
"editor.wordWrap": "on",
//开启 保存自动格式化
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.options": {
"extensions": [".js", ".vue", ".jsx", ".ts", ".tsx"]
},
"eslint.validate": [
"javascript",
"javascriptreact",
"html",
"vue",
"vue-html",
"typescript",
"typescriptreact"
]
}

editorconfig 文件配置


[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 4
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 200

代码规范配置

Eslint 规范配置


  • 安装eslint依赖

    yarn add @typescript-eslint/eslint-plugin@4.29.1 @typescript-eslint/parser@4.29.1 @vue/eslint-config-airbnb@6.0.0 @vue/eslint-config-prettier@6.0.0 @vue/eslint-config-typescript@7.0.0 eslint@7.32.0 eslint-config-prettier@8.3.0 eslint-import-resolver-alias@1.1.2 eslint-plugin-import@2.24.0 eslint-plugin-prettier@3.4.2 @eslint-plugin-vue@7.16.0 -D
  • 配置.eslintrc.js文件

    module.exports = {
    root: true,
    env: {
    node: true,
    },
    extends: [
    'plugin:vue/vue3-recommended',
    'airbnb-base',
    '@vue/typescript/recommended',
    '@vue/prettier',
    '@vue/prettier/@typescript-eslint',
    ],
    parserOptions: {
    ecmaVersion: 2020,
    project: './tsconfig.json',
    },
    rules: {
    'prettier/prettier': 0,
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    // indent: ['error', 4],
    // 'import/extensions': ['off'],
    'import/extensions': [
    'error',
    {
    ignorePackages: true,
    pattern: {
    js: 'never',
    jsx: 'never',
    json: 'always',
    ts: 'never',
    tsx: 'never',
    scss: 'never',
    vue: 'always',
    },
    },
    ],
    'import/no-extraneous-dependencies': ['error', {devDependencies: true}],
    'import/no-unresolved': [
    2,
    {
    ignore: ['^@/'], // @ 是设置的路径别名
    },
    ],
    'vue/html-self-closing': [
    'error',
    {
    html: {
    void: 'always',
    normal: 'never',
    component: 'always',
    },
    svg: 'always',
    math: 'always',
    },
    ],
    'vue/attribute-hyphenation': ['error', 'never'],
    // '@typescript-eslint/no-floating-promises': [
    // 'error',
    // {
    // ignoreVoid: true,
    // ignoreIIFE: false
    // }
    // ],
    'no-void': ['error', {allowAsStatement: true}],
    'no-undef': 'off',
    'no-restricted-syntax': [
    'error',
    'WithStatement',
    "BinaryExpression[operator='in']",
    ],
    'no-restricted-globals': ['error', 'event', 'fdescribe'],
    },
    settings: {
    'import/extensions': ['.js', '.jsx', '.ts', '.tsx', '.scss'],
    'import/parsers': {
    '@typescript-eslint/parser': ['.ts', '.tsx'],
    },
    'import/resolver': {
    alias: {
    map: [['/@', './src']],
    extensions: ['.ts', '.js', '.jsx', '.json', '.scss'],
    },
    node: {
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
    },
    },
    },
    };
  • 配置.eslintignore文件

    vite.config.ts
    tsconfig.json

    vite.config.ts
    tsconfig.json
    *.sh
    node_modules
    *.md
    *.woff
    *.ttf
    .vscode
    .idea
    dist
    /public
    .husky
    .local
    /bin
    Dockerfile

Stylelint 规范配置


  • 安装stylelint依赖

    yarn add stylelint@14.0.1 stylelint-config-prettier@9.0.3 stylelint-config-recess-order@2.6.0 stylelint-config-recommended-vue@1.0.0 stylelint-config-standard-scss@2.0.1 stylelint-scss@4.0.0 -D 
  • 配置.stylelintrc.js文件

    module.exports = {
    root: true,
    plugins: ['stylelint-scss'],
    extends: [
    'stylelint-config-standard',
    'stylelint-config-recess-order',
    'stylelint-config-prettier',
    'stylelint-config-recommended-vue',
    ],
    rules: {
    'declaration-colon-space-before': 'never',
    'declaration-colon-space-after': 'always-single-line',
    'at-rule-no-unknown': [
    true,
    {
    ignoreAtRules: ['include', 'mixin'],
    },
    ],
    'rule-empty-line-before': ['never'],
    },
    };

Prettier 格式化配置


  • 安装prettier依赖

    yarn add prettier -D
  • 配置.prettierrc.js文件

    module.exports = {
    printWidth: 120,
    tabWidth: 4,
    singleQuote: true,
    semi: true,
    useTabs: false,
    };
  • 配置.prettierignore文件

    /dist/*
    .local
    .output.js
    /node_modules/**

    **/*.svg
    **/*.sh

    /public/*

TypeScript 配置

安装相关类型依赖


  • 安装node类型依赖

    问题
    • 如有报错:Cannot find type definition file for ‘node’ ,需安装@types/node依赖
    yarn add @types/node -D

描述文件配置

  • Vue 类型文件配置

    declare module '*.vue' {
    import type { DefineComponent } from 'vue'

    const component: DefineComponent<{}, {}, any>
    export default component
    }
    declare module 'slash2';
    declare module '*.css';
    declare module '*.less';
    declare module '*.scss';
    declare module '*.sass';
    declare module '*.svg';
    declare module '*.png';
    declare module '*.jpg';
    declare module '*.jpeg';
    declare module '*.gif';
    declare module '*.md';
    declare module '*.json';
    declare module '*.woff';
    declare module '*.ttf';
    declare interface CustomEleProps {
    style: {
    [key: string]: string
    } | string
    className: string
    }

    declare interface W {
    style: {
    [key: string]: string
    } | string
    className: string
    }

    interface Window {
    hljs: {
    highlightBlock: () => void
    }
    }
  • 自定义类型文件配置

    type UpmIds =
    | ''
    | '596'
    | 'gs-prado'
    | 'pradox'
    | 'dongjian'
    | 'hmmis'
    | 'fenghuo'
    | 'carbo'
    | 'didiCarbo'
    | 'htwMarketing'
    | 'jiuxiao'
    | 'anxing'
    | 'didiIotPlatform';
    interface MenuItemTypes {
    id: string; // 随机生成,同一个工作台不重复
    key: string;
    title: string;
    level: number;
    name: string; // 菜单名称
    uniqKey: string; // 唯一key
    pid: string; // 父级菜单id
    icon: string; // 展示icon
    remark: string; // 菜单描述
    hasAuth: boolean; // 是否有菜单权限
    bizType: string[]; // 是否有菜单权限
    authConfig: {
    auth: boolean; // 是否权限校验
    upmId: UpmIds; // 子系统id
    featureId: number; // 权限点
    };
    menuConfig: {
    type: 'iframe' | 'mapp' | 'qiankun' | 'none'; // 接入类型:iframe,mfe
    url: string; // 页面URL
    openTab: boolean; // 打开新的窗口
    mappPath: string; // 微前端路径
    mappName: string; // 微前端应用id
    };
    creator?: string; // 创建人
    updator?: string; // 修改人
    createdAt?: number; // 创建时间
    updatedAt?: number;
    index: (string | number)[];
    children: MenuItemTypes[];
    }

    interface BizTypeList {
    label: string;
    value: string;
    }

    type MenuListTypes = MenuItemTypes[];
    type MenuMapTypes = {
    [k: string]: MenuItemTypes;
    };
    type MenuActiveKeys = (string | number)[];
    type FeatureBizTypeMapTypes = {
    [k: string]: MenuListTypes;
    };

    interface WorkbenchRes {
    menuList: MenuListTypes;
    bizTypeList: BizTypeList[];
    }
    interface UserInfoTypes {
    avatar: string;
    dept: string;
    email: string;
    id: number;
    job: string;
    permissionCount: null;
    phone: string;
    roleCount: null;
    status: number;
    username: string;
    usernameZh: string;
    }
    type CollectMenuMapTypes = {
    [k: string]: MenuMapTypes;
    };

    interface GlobalState {
    activeAppName: string;
    operation: string;
    onMountApps?: string[];
    }
    interface StateTypes {
    menuMap: MenuMapTypes | null;
    bizType?: string;
    allMenuList: MenuListTypes;
    topActiveKey: string;
    menuList: MenuListTypes;
    bizTypeList: BizTypeList[];
    menuBizTypeMap: FeatureBizTypeMapTypes;
    leftActiveKeys: MenuActiveKeys;
    leftOpenKeys: MenuActiveKeys;
    noBizTypeMenu: number[];
    userInfo: UserInfoTypes;
    collectMenuMap: CollectMenuMapTypes;
    env: string;
    containerType: 'RightMenu' | 'MicroApp';
    activeMenu: MenuItemTypes | null;
    globalState: GlobalState;
    panes: MenuListTypes;
    activeTabKey: string;
    setGlobalState: (state: GlobalState) => void;
    }

    interface RolesTypes {
    id: number;
    name: string;
    nameZh: string;
    description: string;
    labelName: null;
    appId: number;
    status: number;
    lang: null;
    labelId: null;
    type: null;
    labels: null;
    langNameList: null;
    businessId: number;
    businessName: null;
    userCountBound: null;
    featureCountBound: null;
    permissionCountBound: null;
    commonName: null;
    riskLevel: null;
    minRiskLevel: null;
    availableRiskLevel: [];
    isApplicable: number;
    }

    interface ResponseTypes<T = unknown> {
    code: number;
    data: T;
    message: string;
    }

tsconfig.json 文件配置


{
"compilerOptions": {
"plugins": [
{
"name": "typescript-lit-html-plugin"
}
],
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"noImplicitAny": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"types": ["vite/client", "node"],
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx",
".eslintrc.js",
"*.ts"
],
"exclude": ["node_modules"]
}

vite.config.ts 配置

基础配置


  • 安装所需依赖

    yarn add @vitejs/plugin-vue @vitejs/plugin-vue-jsx -D
  • 配置vite.config.ts

    说明
    1. 如报Internal server error: Failed to parse source for import analysis because the content contain 错误,需要配置plugins:[vue(),vueJsx()]
    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import vueJsx from '@vitejs/plugin-vue-jsx';

    export default ({ mode }) => {
    return defineConfig({
    plugins: [vue(), vueJsx()],
    resolve: {},
    optimizeDeps: {},
    build: {},
    server: {},
    });
    };

代理配置


使用server.proxy为开发服务器配置自定义代理规则,以下是两种配置写法:

  • 选项写法

    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import vueJsx from '@vitejs/plugin-vue-jsx';

    export default ({ mode }) => {
    return defineConfig({
    plugins: [
    vue(),
    vueJsx(),
    ],
    css: {},
    resolve: {},
    optimizeDeps: {},
    build: {},
    server: {
    proxy: {
    '/add': {
    target: 'http://localhost:4000',
    changeOrigin: true,
    },
    },
    },
    });
    };
    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import vueJsx from '@vitejs/plugin-vue-jsx';

    export default ({ mode }) => {
    return defineConfig({
    plugins: [
    vue(),
    vueJsx(),
    ],
    css: {},
    resolve: {},
    optimizeDeps: {},
    build: {},
    server: {
    proxy: {
    '/api': {
    target: 'http://localhost:4000',
    changeOrigin: true,
    rewrite: (path) => {
    return path.replace(/\/api/, '');
    },
    },
    },
    },
    });
    };
  • 正则表达式写法

    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import vueJsx from '@vitejs/plugin-vue-jsx';

    export default ({ mode }) => {
    return defineConfig({
    plugins: [
    vue(),
    vueJsx(),
    ],
    css: {},
    resolve: {},
    optimizeDeps: {},
    build: {},
    server: {
    proxy: {
    '^/api2/(api3|api4).*': {
    target: 'http://localhost:4000',
    changeOrigin: true,
    },
    },
    },
    });
    };
    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import vueJsx from '@vitejs/plugin-vue-jsx';

    export default ({ mode }) => {
    return defineConfig({
    plugins: [
    vue(),
    vueJsx(),
    ],
    css: {},
    resolve: {},
    optimizeDeps: {},
    build: {},
    server: {
    proxy: {
    '/api': {
    target: 'http://localhost:4000',
    changeOrigin: true,
    rewrite: (path) => {
    return path.replace(/\/api/, '');
    },
    },
    '^/api2/(api3|api4).*': {
    target: 'http://localhost:4000',
    changeOrigin: true,
    rewrite: (path) => {
    return path.replace(/\/api2\/(api3|api4)/, '');
    },
    },
    },
    },
    });
    };

导入扩展名配置


使用resolve.extensions来配置导入文件省略的扩展名列表

import { defineConfig } from 'vite';
import Path from 'path';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';

export default ({ mode }) => {
return defineConfig({
plugins: [
vue(),
vueJsx(),
],
css: {},
resolve: {
alias: { '@': Path.resolve(__dirname, './src') },
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.scss'],
},
optimizeDeps: {},
build: {},
server: {}
});
};

导入文件路径别名配置


使用resolve.alias来配置文件路径别名

import { defineConfig } from 'vite';
import Path from 'path';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';

export default ({ mode }) => {
return defineConfig({
plugins: [
vue(),
vueJsx(),
],
css: {},
resolve: {
alias: { '@': Path.resolve(__dirname, './src') },
},
optimizeDeps: {},
build: {},
server: {}
});
};

UI 组件按需引入配置


  • AntdResign UI库组件自动按需引入配置

    说明
    1. UI库组件自动按需引入配置: 配置好这个后,后续开发使用UI库组件无需手动引入,且为按需加载
    • 安装所需依赖

      yarn add ant-design-vue@2.2.8 -S

      yarn add vite-plugin-style-import unplugin-vue-components less -D
    • 具体配置

      注意
      1. 样式按需引入
        • 通过vite-plugin-style-import来实现按需引入样式
        • 通过css选项来支持使用JavaScript变量
      2. 组件按需引入
        • 通过unplugin-vue-components 来实现
      import { defineConfig } from 'vite';
      import vue from '@vitejs/plugin-vue';
      import vueJsx from '@vitejs/plugin-vue-jsx';
      import StyleImport from 'vite-plugin-style-import';
      import ViteComponents from 'unplugin-vue-components/vite';
      import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';

      export default ({ mode }) => {
      return defineConfig({
      plugins: [
      vue(),
      vueJsx(),
      StyleImport({
      libs: [
      {
      libraryName: 'ant-design-vue',
      esModule: true,
      resolveStyle: (name) => `ant-design-vue/es/${name}/style/index`,
      },
      ],
      }),
      ViteComponents({
      dirs: ['src'],
      dts: true,
      resolvers: [AntDesignVueResolver({ importStyle: 'less', resolveIcons: true })],
      }),
      ],
      css: {
      postcss: '',
      preprocessorOptions: {
      less: {
      modifyVars: {
      'primary-color': '#1db5ad',
      },
      javascriptEnabled: true,
      },
      },
      },
      resolve: {},
      optimizeDeps: {},
      build: {},
      server: {},
      });
      };
  • ElementUI 组件库自动按需引入

    说明
    1. UI库组件自动按需引入配置: 配置好这个后,后续开发使用UI库组件无需手动引入,且为按需加载
    • 安装所需依赖

      yarn add element-plus -S 

      yarn add unplugin-vue-components vite-plugin-style-import -D
    • 具体配置

      import { defineConfig } from 'vite';
      import vue from '@vitejs/plugin-vue';
      import vueJsx from '@vitejs/plugin-vue-jsx';
      import StyleImport from 'vite-plugin-style-import';
      import ViteComponents from 'unplugin-vue-components/vite';
      import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';

      export default ({ mode }) => {
      return defineConfig({
      plugins: [
      vue(),
      vueJsx(),
      StyleImport({
      libs: [
      {
      libraryName: 'element-plus',
      esModule: true,
      resolveStyle: (name) => {
      return `element-plus/lib/theme-chalk/${name}.css`;
      },
      },
      ],
      }),
      ViteComponents({
      dirs: ['src'],
      dts: true,
      resolvers: [ElementPlusResolver()],
      }),
      ],
      css: {},
      resolve: {},
      optimizeDeps: {},
      build: {},
      server: {},
      });
      };

自定义组件自动按需引入配置


  • 安装所需依赖

    yarn add unplugin-auto-import -D
  • 具体配置

    注意
    1. 对于ant-design-vue弹窗而言,自动引入的话,样式缺失。所以我们需要在入口文件手动引入样式文件
      • message 样式: import 'ant-design-vue/es/message/style/index';
      • modal 样式: import 'ant-design-vue/es/modal/style/index';
    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import vueJsx from '@vitejs/plugin-vue-jsx';
    import AutoImport from 'unplugin-auto-import/vite';

    export default ({ mode }) => {
    return defineConfig({
    plugins: [
    vue(),
    vueJsx(),
    AutoImport({
    include: [/\.[tj]sx?$/, /\.vue\??/],
    imports: [
    'vue',
    'vue-router',
    'vuex',
    {
    'ant-design-vue': ['message'],
    },
    ],
    dts: true,
    }),
    ],
    resolve: {},
    optimizeDeps: {},
    build: {},
    server: {},
    });
    };
    import { createApp } from 'vue';
    import App from './App.vue';
    import store from './store/index';
    import router from './router/index';
    import 'ant-design-vue/es/message/style/index';
    import 'ant-design-vue/es/modal/style/index';

    const Instance = createApp(App);
    Instance.use(router);
    Instance.use(store);
    Instance.mount('#app');
    <template>
    <div>
    <h3>首页</h3>
    <!-- 无需引入 a-button 组件,直接使用即可 -->
    <a-button @click="handleShowMessage">提示</a-button>
    </div>
    </template>

    <script lang="ts">
    export default defineComponent({
    name: 'App',
    components: {},
    setup() {
    // 直接使用 useRouter
    const route = useRouter();
    console.log(route);

    // 直接使用 useStore
    const store = useStore();
    console.log(store);

    const methods = {
    handleShowMessage: () => {
    // 直接使用 message 即可(需要在 main.ts 中引入相关样式)
    message.success('操作成功!');
    },
    };
    return {
    ...methods,
    };
    },
    });
    </script>

    <style></style>