Vue3.js(Webpack 5)模板项目创建(2021.5.19更新)

·  阅读 574
Vue3.js(Webpack 5)模板项目创建(2021.5.19更新)

源码地址:github.com/zapzqc/vue3…

基础环境

  • Vue CLI 版本为 v5.0.0-beta.1,v5.0 以上支持 webpack 5,终于可以直接使用模块联邦了
  • Node.js 版本为 v15.3.0 (官方建议是 10 以上版本,最低为 8.9)
  • yarn 版本为 1.22.10 (推荐使用,用NPM也可以)

通过以下命令行查询对应版本号:

vue --version      // @vue/cli 5.0.0-beta.1

node --v           // v15.3.0

yarn -v            // 1.22.10
复制代码

如发现版本不满足要求,可以分别通过:

  • 运行以下命令行,更新 Vue CLI 至最新版本

    npm i -g @vue/cli@v5.0.0-beta.1
    复制代码
  • 前往 Node.js 下载最新版本的程序,并安装。

  • 运行以下命令行,更新 yarn 至最新版本

    npm i -g yarn
    复制代码

项目创建

Vue 默认会通过以前选择过的包管理工具 yarn 或 NPM 来安装依赖。想全局修改的话,可在命令行中运行:

vue config --set packageManager yarn  // 或 npm  推荐 yarn
复制代码

也可在创建项目时动态指定当前项目的包管理工具:

vue create vue3-starter -m yarn
复制代码

勾选以下几项(单击图片可看大图):

依次选择如下内容: 最后会问是否要保存当前这个配置,按自己的意愿选择和命名。

成功后,运行如下命令行:

cd vue3-starter
yarn serve
复制代码

在浏览器中打开 http://localhost:8080/ 看到页面就算完成了。

项目改造

默认结构

├── public                    // 静态资源 该文件夹下的内容在构建时会直接拷贝到dist文件夹下
│   ├── favicon.ico           // 网站图标
│   └── index.html            // HTML模板页
├── src                       // 主要工作目录
│   ├── assets                // 静态资源 会被webpack打包处理
│   │   └── logo.png
│   ├── components            // 组件(dumb components,获取props,派发事件)
│   │   └── HelloWorld.vue    // 示例组件
│   ├── router                // 路由(统一使用懒加载)
│   │   └── index.ts          // 组装各路由并导出
│   ├── store                 // 状态管理(可选)
│   │   └── index.ts
│   ├── views                 // 页面(smart components,可以访问store,路由,window)
│   │   ├── About.vue         // 关于
│   │   └── Home.vue          // 首页
│   ├── App.vue               // 根组件
│   ├── main.ts               // 入口文件(引入全局的样式和脚本,可安装插件、注册组件或指令等)
│   └── shims-vue.d.ts        // 帮助IDE识别 .vue文件
├── .browserslistrc  // 目标浏览器配置
├── .editorconfig    // 代码风格规范
├── .eslintrc.js     // eslint配置
├── .gitignore       // git提交忽略文件
├── babel.config.js  // babel配置
├── package.json     // 项目依赖、脚本
├── README.md        // 项目命令行说明
└── tsconfig.json    // TypeScript配置文件
复制代码

内容改造

安装依赖

axios

axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

yarn add axios
复制代码

Normalize.css

Normalize.css 它使不同浏览器能更一致地呈现所有元素,并符合现代标准。

yarn add normalize.css
复制代码

Element Plus

Element Plus,是为一套基于 Vue 3.0 的桌面端组件库。

yarn add element-plus
yarn add babel-plugin-component -D    // 为了按需打包
复制代码

husky

husky,是一个 Git 的 Hook 工具

yarn add husky -D

npx husky add .husky/pre-commit "npm run pre-commit"
复制代码

lint-staged

lint-staged,对 Git 暂存阶段的内容,执行各种分析的工具

yarn add lint-staged -D
复制代码

stylelint

stylelint,是对 CSS 进行分析的工具

yarn add -D stylelint stylelint-config-standard
yarn add -D stylelint-config-sass-guidelines stylelint-scss     // 对 SASS 进行检查
yarn add -D stylelint-config-rational-order                     // 自动调整CSS中的属性顺序
复制代码

commitlint

commitlint,是对提交信息进行 分析的工具

yarn add -D @commitlint/cli @commitlint/config-conventional

npx husky add .husky/commit-msg 'npx --no-install commitlint --edit $1'
复制代码

修改文件

按照名称顺序,由上到下,由外到内。

  • 修改 .editorconfig 中最后一行(现在屏幕都比较宽,100个字符确实满足不了需求)
max_line_length = 100   // 改为 max_line_length = 160
复制代码
  • 修改 .eslintrc.js 中的 rules (打包时配置 将 console 和 debug 全部删除,不需要做这个提示)
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',    // 修改为 'no-console': 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',   // 修改为 'no-debugger': 'off',

'import/prefer-default-export': 'off', // Compositon Funciton 不一定需要默认导出
'max-len': ['error', { code: 160 }],  // 添加此项,使得一行最多容纳 160 个字符串
复制代码
  • 修改 package.json,在对应的节点下添加:
"scripts": {
    "prepare": "husky install",
    "postinstall": "husky install",
    "pre-commit": "lint-staged",
  },
  
"lint-staged": {
    "*.{js,vue,ts}": [
      "npx eslint --fix"
    ],
    "*.{css,scss}": [
      "npx stylelint --fix"
    ]
}
复制代码

最终的 package.json 为:

{
  "name": "vue3-starter",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "prepare": "husky install",
    "postinstall": "husky install",
    "pre-commit": "lint-staged",
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "axios": "^0.21.1",
    "core-js": "^3.12.1",
    "element-plus": "^1.0.2-beta.44",
    "normalize.css": "^8.0.1",
    "vue": "^3.0.4",
    "vue-router": "^4.0.1",
    "vuex": "^4.0.0-0"
  },
  "devDependencies": {
    "@commitlint/cli": "^12.1.4",
    "@commitlint/config-conventional": "^12.1.4",
    "@typescript-eslint/eslint-plugin": "^4.24.0",
    "@typescript-eslint/parser": "^4.24.0",
    "@vue/cli-plugin-babel": "~5.0.0-beta.1",
    "@vue/cli-plugin-eslint": "~5.0.0-beta.1",
    "@vue/cli-plugin-router": "~5.0.0-beta.1",
    "@vue/cli-plugin-typescript": "~5.0.0-beta.1",
    "@vue/cli-plugin-vuex": "~5.0.0-beta.1",
    "@vue/cli-service": "~5.0.0-beta.1",
    "@vue/compiler-sfc": "^3.0.11",
    "@vue/eslint-config-airbnb": "^5.3.0",
    "@vue/eslint-config-typescript": "^7.0.0",
    "babel-plugin-component": "^1.1.1",
    "eslint": "^7.26.0",
    "eslint-plugin-import": "^2.23.2",
    "eslint-plugin-vue": "^7.9.0",
    "husky": "^6.0.0",
    "lint-staged": "^11.0.0",
    "sass": "^1.32.13",
    "sass-loader": "^11.1.1",
    "stylelint": "^13.13.1",
    "stylelint-config-rational-order": "^0.1.2",
    "stylelint-config-sass-guidelines": "^8.0.0",
    "stylelint-config-standard": "^22.0.0",
    "stylelint-scss": "^3.19.0",
    "typescript": "~4.2.4",
    "webpack-bundle-analyzer": "^4.4.2"
  },
  "lint-staged": {
    "*.{js,vue,ts}": [
      "npx eslint --fix"
    ],
    "*.{css,scss}": [
      "npx stylelint --fix"
    ]
  }
}
复制代码
  • 修改 babel.config.js
module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset',
  ],
  plugins: [
    [
      'component',
      {
        libraryName: 'element-plus',  // Element Plus 按需打包
        styleLibraryName: 'theme-chalk',
      },
    ],
  ],
};
复制代码
  • 添加 vue.config.js(定义自身的 WebPack 参数)
/**
 * 判断是否是生产环境
 * @returns {boolean} 是否是生产环境
 */
function isProd() {
  return process.env.NODE_ENV === 'production';
}

// 配置请求的基本API,当前开发模式配置的是淘宝的测试地址
process.env.VUE_APP_BASE_API = isProd() ? '' : 'http://rap2api.taobao.org/app/mock/115307/user';

module.exports = {
  publicPath: isProd() ? './' : '/', // 部署到生产环境时,按需修改前项为项目名称
  productionSourceMap: false, // 不需要生产环境的 source map,减少构建时间

  configureWebpack: (config) => {
    if (isProd()) {
      // 去除 console
      Object.assign(
        config.optimization.minimizer[0].options.terserOptions.compress, {
          drop_console: true,
        },
      );
    }
  },
};

复制代码
  • 替换 public 下的 favicon.ico 为自己的网站图标
  • 修改 public 下的 index.html 中的语言(设置为中文后,浏览器不会出现翻译提示)
<html lang="">   //  改为 <html lang="zh">
复制代码
  • 在 src 下添加 hooks(所有钩子函数存放在此),services(请求后台接口的模块存放在此),utils(常用功能)
  • 修改 src 下的 App.vue 为 app.vue (所有文件的命名统一使用 kebab-case 命名法),删除大部分内容只保留
<template>
  <router-view/>
</template>
复制代码
  • 修改 src 下的 main.ts
import { createApp } from 'vue';

import 'normalize.css'; // CSS reset的替代方案
import '@/assets/styles/style.scss'; // 引入全局样式

import App from './app.vue';
import router from './router';
import store from './store';

const app = createApp(App);
app.use(store); // 按需使用状态管理
app.use(router).mount('#app');

复制代码
  • 删除 src/assets 下 logo.png 文件,添加 fonts(字体)、icons(小图标)、images(大图片)、styles(CSS 样式)文件夹
  • 在 src/assets/images 下 添加 common.scss(各项目通用样式) 和 style.css(当前应用全局样式)
// common.css

/** ************************** 通用样式 ****************************** */

  html, body {
    height: 100%;
  }
  
  /** ****************** 修改type=number的样式 ****************** */
  input::-webkit-outer-spin-button,
  input::-webkit-inner-spin-button {
      -webkit-appearance: none;
  }
  
  input[type="number"] {
      -moz-appearance: textfield;
  }
  /** ******************************************************** */
  
  
  /* 修改谷歌浏览器记住密码后input默认样式 */
  input:-webkit-autofill,
  textarea:-webkit-autofill,
  select:-webkit-autofill {
      -webkit-text-fill-color: #ededed !important;
      box-shadow: 0 0 0px 1000px transparent inset !important;
      background-color: transparent;
      background-image: none;
      transition: background-color 50000s ease-in-out 0s;
  }
  /** ******************************************************** */
复制代码
// style.scss

@import './common.scss';
复制代码
  • 删除 components 文件夹下 HelloWorld.vue 文件,添加 hooks.vue(添加一个使用 hooks 的例子)
<template>
<div>
    <div class='title'>{{myTitle}}</div>
    <button @click="handleCLick">防抖测试</button>
    <div class='scroll-box' @scroll="handleScroll(throttleRef)">
      {{throttleRef}}测试
      <div style="height: 200px"></div>
      <div style="height: 200px"></div>
      <div style="height: 200px"></div>
    </div>
    </div>
</template>

<script lang="ts">
import { ref, defineComponent } from 'vue';
import { useDebounce } from '@/hooks/common/use-debounce';
import { useThrottle } from '@/hooks/common/use-throttle';

/**
 * hooks使用示例组件
 */
export default defineComponent({
  name: 'Hooks',
  props: {
    title: String,
  },
  setup(props) {
    const throttleRef = ref('节流');

    const handleCLick = useDebounce((() => { console.log('防抖测试'); }), 500);
    const handleScroll = useThrottle(((message) => { console.log(`${message}测试`); }), 500);

    return {
      myTitle: props.title,
      throttleRef,
      handleCLick,
      handleScroll,
    };
  },
});
</script>

<style lang="scss">

.title{
  text-align: center;
}

button{
  margin-bottom: 8px;
}

.scroll-box{
  height:300px;
  width:500px;
  background-color:rgb(209, 204, 204);
  overflow-y:scroll;
}

</style>
复制代码
  • 在 src/hooks 下添加 common(各项目通用 hook 函数) 文件夹,添加 use-debounce.ts(防抖),use-throttle.ts(节流),use-router.ts(路由)三个常用 hook
// use-debounce.ts

/**
 * 防抖 在事件被触发一定时间后再执行回调,如果在这段事件内又被触发,则重新计时
 * 使用场景:
 * 1、搜索框中,用户在不断输入值时,用防抖来节约请求资源
 * 2、点击按钮时,用户误点击多次,用防抖来让其只触发一次
 * 3、window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
 * @param fn 回调
 * @param duration 时间间隔的阈值(单位:ms) 默认1000ms
 */
export function useDebounce<F extends(...args: unknown[]) => unknown> (fn: F, duration = 1000):
() => void {
  let timeoutId: ReturnType<typeof setTimeout> | undefined;

  const debounce = (...args: Parameters<F>) => {
    if (timeoutId) {
      clearTimeout(timeoutId);
    }
    timeoutId = setTimeout(() => {
      fn(...args);
      timeoutId = undefined;
    }, duration);
  };

  return debounce;
}
复制代码
// use-throttle.ts

/**
 * 节流 规定在一段时间内,只能触发一次函数。如果这段时间内触发多次函数,只有一次生效
 * 使用场景:
 * 1、鼠标不断点击触发,mousedown(单位时间内只触发一次)
 * 2、监听滚动事件,比如是否滑到底部自动加载更多
 * @param fn 回调
 * @param duration 时间间隔的阈值(单位:ms) 默认500ms
 */
export function useThrottle<F extends(...args: unknown[]) => unknown>(fn: F, duration = 1000):
() => void {
  let timeoutId: ReturnType<typeof setTimeout> | undefined;

  const throttle = (...args: Parameters<F>) => {
    if (timeoutId) {
      return;
    }
    timeoutId = setTimeout(() => {
      fn(...args);
      timeoutId = undefined;
    }, duration);
  };

  return throttle;
}
复制代码
// use-router.ts

import {
  reactive, toRefs, watch, getCurrentInstance, Ref,
} from 'vue';

import { Router } from 'vue-router';

/**
 *  获取路由
 *  @returns 当前路由以及Router实例
 */
export function useRouter():{route:Ref, router:Router } {
  const vm = getCurrentInstance();
  const state = reactive({ route: vm?.proxy?.$route });
  watch(() => vm?.proxy?.$route, (newValue) => { state.route = newValue; });
  return { ...toRefs(state), router: vm?.proxy?.$router as Router };
}
复制代码
  • 在 src/router 下添加 home.ts 作为一个示例模块的路由
import { RouteRecordRaw } from 'vue-router';

const homeRoutes: Array<RouteRecordRaw> = [
  {
    path: '/home',
    name: 'home',
    component: () => import('@/views/home.vue'),
  },
];

export default homeRoutes;
复制代码
  • 修改 src/router 下的 index.ts(让它能够自动加载 router文件夹下的其它路由模块,以后只需要在 router 下添加像 home 一样的路由模块即可)
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import Login from '../views/login.vue';

// 首次必然要加载的路由
const constRoutes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'Login',
    component: Login,
  },
];

// 所有路由
let routes:Array<RouteRecordRaw> = [];

// 自动添加router目录下的所有ts路由模块
const files = require.context('./', false, /\.ts$/);
files.keys().forEach((route) => {
  // 如果是根目录的 index.js、 不做任何处理
  if (route.startsWith('./index')) {
    return;
  }
  const routerModule = files(route);
  // 兼容 import export 和 require module.export 两种规范 ES modules commonjs
  routes = [...constRoutes, ...(routerModule.default || routerModule)];
});

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

export default router;
复制代码
  • 在 src/services 下添加 user.ts(和后台接口交互的用户模块示例)
import http from '@/utils/http';
import { AxiosResponse } from 'axios';

// 使用接口定义登录接口返回的数据格式·
export interface ILogin{
  accessToken: string;
  message:string
}

// 添加API地址
const API = {
  login: '/login',
};

/**
 * 登录
 * @param userInfo 用户信息
 * @returns 验证结果
 */
export function login(userInfo:Record<string, unknown>):Promise<AxiosResponse<ILogin>> {
  return http.get<ILogin>(API.login, { data: userInfo });
}

复制代码
  • 在 src/store 下添加 modules 文件夹,并在其中添加 user.ts(作为测试)
import { ILogin, login } from '@/services/user';

// 用常量替代 mutation 事件类型,当前模块所有mutation一目了然
const SET_ACCESSTOKEN = 'SET_ACCESSTOKEN';

// state
const userState = {
  accessToken: '',
};

// getters

// actions
const actions = {
  async login({ commit }:{commit:(mutation:string, arg:string)=>void}, userInfo:Record<string, unknown>):Promise<ILogin> {
    const { data } = await login(userInfo);
    commit(SET_ACCESSTOKEN, data.accessToken);
    return data;
  },
};

// mutations
const mutations = {
  [SET_ACCESSTOKEN](state:{accessToken:string}, accessToken:string) :void {
    state.accessToken = accessToken;
  },
};

export default {
  state: userState, actions, mutations,
};

复制代码
  • 修改 src/store 下 index.ts(让其动态引入 modules 下的文件作为模块)
import { createStore } from 'vuex';

interface IModule {
  [key: string]: { namespaced: boolean }
}

// 自动添加mudules下的所有ts模块
const modules: IModule = {};
const files = require.context('./modules', false, /\.ts$/);
files.keys().forEach((key) => {
  const moduleKey = key.replace(/(\.\/|\.ts)/g, '');
  modules[moduleKey] = files(key).default;
  modules[moduleKey].namespaced = true; // 让 mutations、getters、actions 也按照模块划分
});

// 无需使用模块或者是一些通用的状态写在下方
export default createStore({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules,
});

复制代码

在 src/utils 下添加 http 文件夹,并在其中添加 index.ts 文件(封装 axios)

import axios from 'axios';

const http = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // 如跨域请求时要带上cookie,则设置为true
  timeout: 1000 * 5, // 请求超时时长 5秒
});

http.interceptors.request.use(
  (config) => {
    if (config.method === 'post') {
      // 按需添加内容
    }
    return config;
  },
  (error) => {
    console.log(error);
    return Promise.reject(error);
  },
);

http.interceptors.response.use(
  (response) => {
    // 如果返回的状态不是200 就报错 按需修改
    if (response.status && response.status !== 200) {
      return Promise.reject(new Error('错误'));
    }
    return response;
  },
  (error) => {
    console.log(error);
    return Promise.reject(error);
  },
);

export default http;

复制代码
  • 删除 src/views 下的 About.vue 和 Home.vue,新建 login.vue 和 home.vue
// login.vue

<template>
  <div>
    <el-button @click="handleLogin">登录</el-button>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { ElButton } from 'element-plus';
import { useRouter } from '@/hooks/common/use-router';
import { useStore } from 'vuex';

export default defineComponent({
  name: 'Login',
  components: { ElButton },
  setup() {
    const store = useStore();
    const { router } = useRouter();
    const handleLogin = async () => {
      const data = await store.dispatch('user/login', { userName: 'zqc', password: '18' }); // 派发事件,调用actions
      if (data.accessToken) {
        router.push('home');
      }
    };
    return {
      handleLogin,
    };
  },
});
</script>
复制代码

新增文件

  • .stylelintrc.js
module.exports = {
  extends: ['stylelint-config-standard', 'stylelint-config-sass-guidelines', 'stylelint-config-rational-order'], // 按照规则对CSS属性进行排序
  plugins: ['stylelint-scss'],
  rules: {
    'selector-no-qualifying-type': [
      true,
      {
        ignore: ['attribute'],     // 允许按类型限定属性选择器
      },
    ],
    'max-nesting-depth': 3,        // 允许的最大嵌套深度为 3
    'order/properties-alphabetical-order': null,  // 屏蔽属性按字母顺序检查
    'selector-class-pattern': null,               // 屏蔽类选择器的检查,以确保使用字符 __
    'selector-max-compound-selectors': 5,        // 允许的最大复合选择器为 5
    'font-family-no-missing-generic-family-keyword': null,  // 屏蔽没有申明通用字体
  },
};

复制代码
  • commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
};
复制代码

改造后的结构

├── .husky                    // Git hook
│   ├── _           
│   │   └── husky.sh          // husky 相关命令
│   ├── .gitignore            // 忽略 _ 文件夹
│   ├── commmit-msg           // 针对提交消息检查的钩子
│   └── pre-commit            // Git 提交前的 钩子
├── public                    // 静态资源 该文件夹下的内容在构建时会直接拷贝到dist文件夹下
│   ├── favicon.ico           // 网站图标
│   ├── index.html            // HTML模板页
│   └── ...
├── src                       // 主要工作目录
│   ├── assets                // 静态资源 会被 webpack 打包处理
│   │   ├── fonts             // 字体文件(可选)
│   │   │   └── ...
│   │   ├── icons             // 图标(可选)
│   │   │   └── ...
│   │   ├── images            // 图片(可选)
│   │   │   ├── exception     // exception(通用异常页面)模块使用到的图片
│   │   │   │   └── ...
│   │   │   ├── module-a      // 此处要用模块命名(可选)
│ 	│   │   │   └── ...       // 该模块下使用到的图片
│   │   │   └── ...           // 通用的图片(小项目就不用分文件夹了)
│   │   └── styles            // 样式
│   │       ├── common.scss   // 常用样式(提供通用的)
│   │       ├── style.scss    // 全局样式,组装各样式并导出最终被 main.js 引入
│   │       └── ...
│   ├── components            // 组件(dumb components,获取props,派发事件)
│   │   ├── common            // 不同项目中的通用组件(可选)
│   │   │   └── ...
│   │   ├── module-a          // 此处要用模块命名(可选)
│   │   │   └── ...           // 该模块下的组件
│   │   └── ...               // 当前项目中的通用组件
│   ├── hooks                 // 钩子(本身不应该叫这个名字,确切的说应该叫Compostion Function)
│   │   ├── common            // 不同项目中的通用 hooks
│   │   │   ├── use-debounce.ts  // 防抖
│   │   │   ├── use-router.ts    // 路由
│   │   │   ├── use-throttle.ts  // 节流
│   │   │   └── ...
│   │   └── ...               // 本项目中通用的 hooks
│   ├── layouts               // 布局组件(可选)
│   │   └── ...
│   ├── router                // 路由(除必须要加载的以外,统一使用懒加载)
│   │   ├── index.ts          // 组装各路由并导出
│   │   └── ...
│   ├── services              // 接口请求
│   │   ├── module-a .ts      // 各业务模块所有包含的请求和数据处理,此处要用模块命名
│   │   └── ...
│   ├── store                 // 状态管理(可选)
│   │   ├── modules           // 各模块
│   │   │   └── ...           // 尽量和 views 中的模块对应上
│   │   ├── index.ts          // 组装模块并导出
│   ├── utils                 // 工具类
│   │   ├── http              // aixos 封装
│   │   │   └── index.ts
│   │   └── ...
│   ├── views                 // 页面(smart components,可以访问store,路由,window)
│   │   ├── module-a.vue      // 用模块命名,如该模块下页面较多,可建以模块为名称的文件夹,在其中创建多个页面
│   │   └── ...
│   ├── app.vue        // 根组件
│   ├── main.ts        // 入口文件(引入全局的样式和脚本,可安装插件、注册组件或指令等)
│   └── shims-vue.d.ts // 帮助IDE识别 .vue文件
├── .browserslistrc    // 目标浏览器配置
├── .editorconfig      // 代码风格规范
├── .eslintrc.js       // ESLint 配置
├── .gitignore         // git 提交忽略文件
├── .stylelintrc.js    // stylelint 配置
├── babel.config.js    // Babel 配置
├── commitlint.config.js // commmitlint 配置
├── package.json       // 项目依赖、脚本
├── README.md          // 项目说明
├── tsconfig.json      // TypeScript 配置文件
└── vue.config.js      // 自定义 webpack 配置
复制代码
分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改