7

Vite现在可谓是炙手可热,可能很多小伙伴还没有使用过Vite,但是我相信大多数小伙伴已经在使用Vite了,因为是太香了有没有。可能在使用过程中很多东西Vite不是配置好的,并不像Vue-cli配置的很周全,那么今天就说一下如何配置开发环境,其中主要涉及到的点有一下几个:

  • TypeScript
  • Vuex
  • Vue-Router
  • E2E

    • Cypress
  • Test unit

    • Jest
    • vtu
  • Eslint + Perttite
  • verify git commit message
  • CI
  • alias

Vite初始化项目

在开始之前首先要先使用Vite创建项目,如果小伙伴已经对Vite已经有了一定的了解,那么可以跳过。根据Vite官网介绍可以使用npmyarn来创建项目。

使用 NPM:

npm init vite@latest

使用 Yarn:

yarn create vite

使用 PNPM:

pnpx create-vite

输入完命令之后然后按照提示操作即可,因为在项目要支持TypeScript所以我这里就选择的是vue-ts。创建好之后Vite会帮助我们把项目给创建好,可以发现Vite所创建好的项目其实与使用Vue-cli所创建的项目目录结构其实是差不多的,这里也就不多赘述了。

集成Vue-Router

Vue-Router是大多数项目中比不可少的工具之一了,Vue-Router可以让构建单页面应用变得更加的容易。包含的功能有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
  • 自定义的滚动条行为

以上截取自Vue-router官网

安装Vue-Router:

使用 NPM:

npm install vue-router@next --save

使用 Yarn:

yarn add vue-router@next --save

安装完成之后在src目录下创建文件夹router/index.ts,创建完成之后需要在Vue-Router中对Vue-Router进行初始化配置。我们暂时把初始化的工作搁置一下,先需要创建pages文件夹,把需要展示的页面创建完成。

创建完成之后,接下来就是完善router/index.ts中文件的初始化工作了:

import { createRouter, createWebHashHistory } from "vue-router";

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: "/home",
      name: "Home",
      alias: "/",
      component: () => import("../pages/Home.vue")
    },
    {
      path: "/about",
      name: "About",
      component: () => import("../pages/About.vue")
    }
  ]
})

export default router;

接下来在main.ts文件中集成Vue-Router

import { createApp } from 'vue';
import App from './App.vue';

import router from "./router";

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

测试一下,这里修改一下App.vue的代码,测试一下我们的路由是否已经可以正常使用了。

<template>
  <router-link to="/home">Home</router-link>
  <router-link to="/about">About</router-link>
  <router-view></router-view>
</template>

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

export default defineComponent({
  name: 'App'
})
</script>

接下来启动服务就可以看到所配置的页面了,说明配置的路由已经生效了。Good Job,真的很不错~~~

集成Vuex

VuexVue所支持的状态管理工具,在在实际应用过程中也有很大的作用,当多个组件之间的数据流转变得非常困难,因此需要集中的对状态进行管理,Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新。

安装Vuex:

使用 NPM:

npm install vuex@next --save

使用 Yarn:

yarn add vuex@next --save

安装完成之后,首先添加store/index.ts来初始化Vuex。需要注意的是,如下示例使用了Vuex命名空间。可能在实际项目中使用命名空间相对来说还是比较普遍的,避免进行状态管理的时候导致变量污染。

import { createStore } from "vuex";

const store = createStore({
  modules: {
    home: {
      namespaced: true,
      state: {
        count: 1
      },
      mutations: {
        add(state){
          state.count++;
        }
      }
    }
  }
})

export default store;

集成到Vue中:

import { createApp } from 'vue';
import App from './App.vue';

import router from "./router";
import store from "./store";

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

现在Vuex就已经被集成到Vue中了为了保证集成的Vuex是有效地,那么需要对此进行测试:

pages/Home.vue

<template>
  <h1>Home</h1>
  <h2>{{count}}</h2>
  <button @click="handleClick">click</button>
</template>

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

export default defineComponent({
  setup () {
    const store = useStore();
    const count = computed(() => store.state.home.count);
    const handleClick = () => {
      store.commit('home/add');
    };
    return {
      handleClick,
      count
    };
  }
})
</script>

当点击按钮的时候,就可以发现count值也随着点击每次递增,那么store是可以正常使用。Good Job,真的很不错~~~

集成单元测试

在开发过程中为了保证程序的健壮性,就需要对程序进行单元测试,所以在项目初始化的时候,同样就需要为单元测试进行配置。配置中使用的工具是jest,如果对于单元测试不太了解的,请自行百度学习,脱离本文重点,不多赘述。

安装相关依赖:

使用 NPM:

npm install jest -D                  # 单元测试工具 ^26.6.3
npm install @types/jest -D           # 单元测试类型文件
npm install babel-jest -D            # 支持es6模块 ^26.6.3
npm install @babel/preset-env -D     # 支持es6模块 ^7.14.7
npm install vue-jest@next -D         # 支持Vue文件 ^5.0.0-alpha.8
npm install ts-jest -D               # 支持Ts      ^26.5.1
npm install @vue/test-utils@next     # Vue官方测试工具 2.0.0-rc.6
npm install @vue/test-utils@next -D  # 2.0.0-rc.6
npm install @babel/preset-typescript # 支持Ts ^7.12.17

使用 Yarn:

yarn add jest --dev                  # 单元测试工具 ^26.6.3
yarn add @types/jest --dev           # 单元测试类型文件
yarn add babel-jest --dev            # 支持es6模块 ^26.6.3
yarn add @babel/preset-env --dev     # 支持es6模块 ^7.14.7
yarn add vue-jest@next --dev         # 支持Vue文件 ^5.0.0-alpha.8
yarn add ts-jest --dev               # 支持Ts      ^26.5.1
yarn add @vue/test-utils@next        # Vue官方测试工具 2.0.0-rc.6
yarn add @babel/preset-typescript    # 支持Ts ^7.12.17

依赖安装完成之后,在src目录下创建tests文件夹,这个文件夹用来存放关于测试相关的文件,因为我们不但有单元测试还有E2E测试,所以要对两者进行区分。

因为在安装配置的时候可能会导致版本不兼容,导致的报错,所以在配置的时候添一定要注意版本号,在上面已经注释了版本号,需要注意一下。安装完所依赖之后,接下来就需要对单元测试进行配置。

在根目录创建jest.config.jsJest进行初始化的基本配置:

module.exports = {
  transform: {  //  babel预设
    "^.+\\.vue$": "vue-jest",       //  支持导入Vue文件
    "^.+\\.jsx?$": "babel-jest",    //  支持import语法
    '^.+\\.tsx?$': 'ts-jest'        //  支持ts
  }
};

修改tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    "types": ["vite/client", "jest"]    //  指定类型为jest
  },
  "include": [
    "src/**/*.ts", 
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue", 
    "tests"     // 指定单元测试路径
  ]
}

因为Node无法运行TypeScript这里需要使用babelTypeScript进行编译,要配置babel的相关配置,在根目录创建babel.config.js


module.exports = {
  transform: {
    "^.+\\.vue$": "vue-jest",
    "^.+\\.jsx?$": "babel-jest",
    '^.+\\.tsx?$': 'ts-jest'
  },
  testMatch: [  //  匹配单元测试规则
    "**/?(*.)+(test).[jt]s?(x)"
  ]
};

上面基本已经对单元测试的配置完成了,接下来就是测试是否能够正常运行单元测试,在根目录下面创建tests/unit等文件夹(文件):

index.test.ts

import { mount } from "@vue/test-utils";
import demo from "./demo";
import Foo from "../../src/components/Foo.vue";

test("1+1=2", () => {
  console.log(demo);
  console.log(mount(Foo));
  expect(1+1).toBe(2);
});

demo.ts

export default {
  a: 1
};

之后就是运行单元测试,在package.json中添加命令:

{
  "scripts": {
    "test:unit": "jest"
  }
}

src目录下添加shims-vue.d.ts

declare module '*.vue' {
  import Vue from 'vue';
  export default Vue;
}

运行一下就可以看到输出得结果,那么就代表我们的单元测试可以正常运行了,Good Job,真的很不错~~~

集成E2E测试

可能很多小伙伴对于E2E测试不是特别的熟悉,E2E即端对端测试,属于黑盒测试,通过编写测试用例,自动化模拟用户操作,确保组件间通信正常,程序流数据传递如预期。

既然对E2E有了一定的了解之后呢,那么就需要安装E2E测试相关的模块的依赖,E2E测试使用是cypress依赖,首次安装的时候可能会慢,要耐心等待:

使用 NPM:

yarn add cypress -D             # e2e测试工具

使用 Yarn:

yarn add cypress --dev          # e2e测试工具

安装依赖完成之后需要对cypress进行初始化,这个时候我们需要在package.json中添加命令:

{
  "scripts": {
    "test:e2e": "cypress open"
  }
}

执行完成之后呢会在根目录下创建一个cypress文件夹,该文件夹里面包含了很多目录相关的文件,暂时先不管,之后在tests文件夹中添加e2e文件夹,把cypress所有内容剪切到tests/e2e中。

完成上述操作之后咧?cypress在运行的时候会默认的去cypress文件中去找相关依赖,但是现在已经把cypress中的文件已经挪走了,那么我们就需要对cypress进行配置,修改cypress的基础路径,并指向到tests/e2e文件即可。

在根目录创建cypress.json文件:

{
  "pluginsFile": "tests/e2e/plugins/index.ts",
  "video": false    //  关掉视频录制默认行为
}

细心的小伙伴可能已经发现了,上面所以引用的文件是以.ts结尾的,但是在对应目录下面是.js文件,这是为什么?因为我们的项目是以TypeScript为基础的项目,所以我们E2E测试也应该使用TypeScript,这个时候我们应该把E2E文件夹下所有.js文件改为.ts(需要注意的是,把integration改成specs,并删除里面所有测试用例)。

更改plugin文件夹下面的index.ts

module.exports = (on, config) => {
  return Object.assign({}, config, {
    fixturesFolder: 'tests/e2e/fixtures',
    integrationFolder: 'tests/e2e/specs',
    screenshotsFolder: 'tests/e2e/screenshots',
    videosFolder: 'tests/e2e/videos',
    supportFile: 'tests/e2e/support/index.ts'
  });
};

针对cypress的单元测试已经完成了,但是刚才已经删除了测试用例,接下来就写一下测试用例,在tests/e2e/specs文件夹下面创建:

first.specs.ts

describe("First ", () => {

  it("before", () => {
    cy.visit("http://localhost:3000/");
    cy.get("button").click();
  });

});

需要注意的是,因为cypress是需要在项目启动的时候进行测试的,所以我们需要把本地的服务运行起来才可以。测试用例变现完成之后,就可以运行了。在弹出页面中会看到所有用例的文件,点击对应文件,就会在浏览器中打开了,浏览器左侧则是对应测试的步骤了。

经过上述的配置E2E的配置已经基本完成了,由于两个测试全处于tests中两个测试会互相影响么?

插曲

既然E2E测试和单元测试都已经完成了,那么分别运行一下单元测试和E2E测试,当运行单元测试后的时候,就会抛出一个错误。

  ● Test suite failed to run

    tests/unit/index.spec.ts:8:15 - error TS2339:
        Property 'toBe' does not exist on type 'Assertion'.

    8   expect(1+1).toBe(2);
                    ~~~~

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        3.234 s
Ran all test suites.
error Command failed with exit code 1.

很明显,我们的单元测试收到E2E测试的影响,运行失败了,可以根据文件提示找到对应的位置,因为两个测试库的expect冲突了,所以导致单元测试下expect返回的对象内容不在是原来单元测试expect了。

既然出现问题了就需要解决问题ing,在上面配置单元测试的时候,为了让单元测试支持TypeScripttsconfig.json中添加了测试的路径,我们如果只指向到单元测试里面这个问题是不是就解决了,但是如果这样的话,cypress无法再支持TypeScript了,其实我们可以在E2E再创建一个tsconfig.json这个文件只应用于E2E

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    "types": ["vite/client", "jest"]
  },
  "include": [
    "src/**/*.ts", 
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue", 
    "tests/unit"     // 指定单元测试路径
  ]
}

tests/e2e/tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"]
  }
}

之后再运行yarn test:unit的时候就会发现不会报错了,那么经过这次更改e2e是否受到影响了呢?运行yarn test:e2e显然并没有发生任何错误,也可以正常运行的。Good Job,真的很不错~~~

当运行yarn test:e2e的时候,总会弹出一个窗口,但是当项目部署走CI的时候,是没有办法进行点击的,这个时候应该怎么办呢?其实cypress是有办法在终端执行的,通过npx cypress run这个命令去在终端运行E2E测试。

修改package.json:

{
  "scripts": {
    "test:unit": "jest",
    "test:e2e": "cypress open",
    "test": "yarn test:unit && npx cypress run"
  }
}

之后运行yarn test就可以先运行单元测试,后运行E2E测试了。

集成Git提交验证

在开发项目的时候可能并不是一个人进行开发的,可能会由多个人进行开发,那么作为标准的自动化来讲,对于Git提交的时候,要有一些固定显著的格式来规范我们的项目开发人员,这个时候就需要使用某些工具进行约束。

安装相关依赖:

使用 NPM:

npm install yorkie -D
npm install chalk -D

使用 Yarn:

yarn add yorkie --dev
yarn add chalk --dev

安装完依赖之后,对yorkie之后需要对其进行相关的配置,在package.json中添加字段:

{
    "gitHooks": {
        "commit-msg": "node scripts/commitMessage.js"
    }
}

在上面的配置中,运行了一个js文件,那么这个js文件中则是对提交内容的校验。

scripts/commitMessage.js

const chalk = require('chalk')
const msgPath = process.env.GIT_PARAMS
const msg = require('fs').readFileSync(msgPath, 'utf-8').trim()

const commitRE = /^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)(\(.+\))?(.{1,10})?: .{1,50}/
const mergeRe = /^(Merge pull request|Merge branch)/

if (!commitRE.test(msg)) {
  if (!mergeRe.test(msg)) {
    console.log(msg)
    console.error(
      `  ${chalk.bgRed.white(' ERROR ')} ${chalk.red(
        `invalid commit message format.`,
      )}\n\n` +
        chalk.red(
          `  Proper commit message format is required for automated changelog generation. Examples:\n\n`,
        ) +
        `    ${chalk.green(`feat(compiler): add 'comments' option`)}\n` +
        `    ${chalk.green(
          `fix(v-model): handle events on blur (close #28)`,
        )}\n\n` +
        chalk.red(
          `  See https://github.com/vuejs/vue-next/blob/master/.github/commit-convention.md for more details.\n`,
        ),
    )
    process.exit(1)
  }
}

集成Eslint

Eslint对于团队开发来说是一个很不错的工具,可以根据Eslint的配置给约束开发者代码的风格,以及书写格式。

安装相关依赖:

使用 NPM:

npm install eslint -D
npm install eslint-plugin-vue -D
npm install @vue/eslint-config-typescript -D
npm install @typescript-eslint/parser -D
npm install @typescript-eslint/eslint-plugin -D
npm install typescript -D
npm install prettier -D
npm install eslint-plugin-prettier -D
npm install @vue/eslint-config-prettier -D

使用 Yarn:

yarn add eslint --dev
yarn add eslint-plugin-vue --dev
yarn add @vue/eslint-config-typescript --dev
yarn add @typescript-eslint/parser --dev
yarn add @typescript-eslint/eslint-plugin --dev
yarn add typescript --dev
yarn add prettier --dev
yarn add eslint-plugin-prettier --dev
yarn add @vue/eslint-config-prettier --dev

配置安装完成之后呢,还需要对eslint进行配置,在根目录下创建.eslintrc:

.eslintrc

{
  "root": true,
  "env": {
    "browser": true,
    "node": true,
    "es2021": true
  },
  "extends": [
    "plugin:vue/vue3-recommended",
    "eslint:recommended",
    "@vue/typescript/recommended"
  ],
  "parserOptions": {
    "ecmaVersion": 2021
  }
}

配置项已经添加好了,如何去运行已经配置好的eslint呢?接下来就需要在package.json中添加命令:

{
    "lint": "eslint --ext src/**/*.{ts,vue} --no-error-on-unmatched-pattern"
}

接下来运行一下yarn lint就可以了,可以通过eslint完成格式的校验了,现在的问题是什么,在执行yarn lint的时候把所有的文件全部都校验了一次,这个可不是我们所希望的,如果有很多文件的话,那么速度将会很慢,那么有没有办法,只在git提交的时候对修改的文件进行eslint校验呢?

安装相关依赖:

使用 NPM:

npm install lint-staged -D

使用 Yarn:

yarn add lint-staged --dev

修改package.json

{
  "gitHooks": {
    "commit-msg": "node scripts/commitMessage.js",
    "pre-commit": "lint-staged"
  },
  "lint-staged": {
    "*.{ts,vue}": "eslint --fix"
  },
  "scripts": {
    "test:unit": "jest",
    "test:e2e": "cypress open",
    "test": "yarn test:unit && npx cypress run",
    "lint": "npx prettier -w -u . && eslint --ext .ts,.vue src/** --no-error-on-unmatched-pattern",
    "bea": "npx prettier -w -u ."   //  美化代码
  },
}

配置alias

在使用cli的时候总是使用@去引入某些文件,由于Vite没有提供类似的配置,所以我们需要手动的对其进行一些相关的配置,才能继续使用@符号去快捷的引入文件。

修改vite.config.ts:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { join } from "path";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: [
      {
        find: '@',
        replacement: '/src',
      },
      { find: 'views', replacement: '/src/views' },
      { find: 'components', replacement: '/src/components' },
    ]
  }
});

修改tsconfig.json:

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    "types": ["vite/client", "jest"],
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    } 
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/unit"
  ]
}

为了保证在单元测试中也可以使用@引入src下面的文件需要对jest.config.js配置进行修改:

修改jest.config.js

module.exports = {
  transform: {
    '^.+\\.vue$': 'vue-jest',
    '^.+\\.jsx?$': 'babel-jest',
    '^.+\\.tsx?$': 'ts-jest',
  },
  testMatch: ['**/?(*.)+(test).[jt]s?(x)'],
  moduleNameMapper: {
    "@/(.*)$": "<rootDir>/src/$1" 
  }
};

结尾

使用Vite对项目初始化还是存在很多坑的,比如在配置单元测试的时候的版本兼容,还有配置别名的时候需要注意多个文件之间的协调。

我们做了这么多的统一配置无非是为了能够让项目在开发过程中更加的规范,能够达成统一的效果,也能让我们的程序更加的健壮。革命尚未成功,同志仍需努力。

可能文章中一些见解存在一些问题,欢迎大家在评论区指出,大家一起学习,一起进步。如果文章对你有帮助的话,请点击赞吧~


Aaron
4k 声望6.1k 粉丝

Easy life, happy elimination of bugs.