vite+vue3+ts+eslint+prettier+stylelint+husky+jest搭建项目

文章目录

    • 1. vite1.0
    • 2. vite2.0
    • 3. 引入 typescript
      • 配置 jsx 支持
    • 4. 加入 vue-router、pinia,并实现 pinia 持久化
    • 5. 按需引入 vant、移动端适配 rem
    • 5. (推荐)另一种方式按需引入 vant, 自动引入 vue、vue-router、hooks
      • 5.1 按需引入组件
      • 5.2 自动引入 vue/vue-router/hooks 等
    • 6. 配置 eslint、prettier
    • 7. 配置 stylelint
    • 8. 引入 jest 测试
    • 9. husky 和 lint-staged 构建代码工作流
    • 10. vscode 保存自动格式化
    • 11. 生成自定义 changelog

更多内容可参考我的博客


1. vite1.0

pnpm i -g create-vite-app
create-vite-app vite-demo

2. vite2.0

pnpm init @vitejs/app

3. 引入 typescript

pnpm i typescript -D

在 项目根目录下创建 typescript 的配置文件 tsconfig.json

  {
    "compilerOptions": {
      "target": "esnext",
      "module": "esnext",
      "moduleResolution": "node",
      "strict": true,
      "forceConsistentCasingInFileNames": true,
      "allowSyntheticDefaultImports": true,
      "strictFunctionTypes": false,
      "jsx": "react",
      "baseUrl": ".",
      "allowJs": true, // 允许编译器编译JS,JSX文件
      "checkJs": false, // 允许在JS文件中报错,通常与allowJS一起使用
      "sourceMap": true,
      "esModuleInterop": true,
      "noUnusedLocals": true,
      "noUnusedParameters": true,
      "experimentalDecorators": true,
      "skipLibCheck": true,
      "types": ["vite/client", "jest", "pinia-plugin-persist"],
      "paths": {
        "@/*": ["src/*"]
      },
      "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
    },
    "include": [
      "src/**/*.ts",
      "src/**/*.d.ts",
      "src/**/*.tsx",
      "src/**/*.vue",
      "tests/**/*.ts",
      "tests/**/*.tsx"
    ],
    "exclude": ["node_modules", "dist", "**/SwiperScroll.vue"]
  }

main.js 修改成 main.ts
在根目录,打开Index.html

<script type="module" src="/src/main.js"></script>
// 修改为:
<script type="module" src="/src/main.ts"></script>

配置 jsx 支持

pnpm i -D @vitejs/plugin-vue-jsx
//vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";

export default defineConfig({
  plugins: [vue(), vueJsx()],
});

4. 加入 vue-router、pinia,并实现 pinia 持久化

pnpm i vue-router pinia pinia-plugin-persist -S

在根目录下创建  store/index.ts

/*
 * @Description: index
 * @Autor: huoyou
 */
import type { App } from "vue";
import { createPinia } from "pinia";
import piniaPersist from "pinia-plugin-persist";

export function setupStore(app: App<Element>) {
  const pinia = createPinia();
  pinia.use(piniaPersist);
  app.use(pinia);
}

在根目录下创建  router/index.ts

/*
 * @Description: router
 * @Autor: huoyou
 */
import type { App } from "vue";
import { useUserStore } from "@/store/useUser";
import { useKeepAliveStore } from "@/store/useKeepAlive";
import {
  createRouter,
  createWebHashHistory,
  RouteRecordRaw,
  RouteLocationNormalized,
} from "vue-router";

interface IRoute extends RouteLocationNormalized {
  name: string;
}
// 自动注入modules下的路由
const modulesFiles = import.meta.globEager("./modules/**/*.(ts|js)");
let routerList: Array<RouteRecordRaw> = [];
for (const path in modulesFiles) {
  routerList = [
    ...routerList,
    ...(modulesFiles[path].default || modulesFiles[path]),
  ];
}

const router = createRouter({
  history: createWebHashHistory(), // hash模式: createWebHashHistory,   history模式: createWebHistory
  routes: routerList,
  // @ts-ignore
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      // 后退才有savedPosition
      return savedPosition;
    } else {
      if (to.meta.isKeepAlive) {
        to.meta.scrollTop = document.body.scrollTop;
      }
      return { left: 0, top: to.meta.scrollTop || 0 };
    }
  },
});
// @ts-ignore
router.beforeEach((to: IRoute, from, next) => {
  const userStore = useUserStore();
  const keepAliveStore = useKeepAliveStore();
  to.meta.isKeepAlive && keepAliveStore.setKeepAlive(to.name);
  if (to.matched.some((res) => res.meta.requireAuth)) {
    // 判断是否需要登录权限
    if (userStore.token) {
      next();
    } else {
      next({
        path: `/login`, // 未登录则跳转至login页面
        query: { redirect: to.fullPath }, // 登陆成功后回到当前页面,这里传值给login页面,to.fullPath为当前点击的页面
      });
    }
  } else {
    next();
  }
});
router.afterEach((to, from) => {
  // 添加转场效果
  const toDepth = to.path.split("/").length;
  const fromDepth = from.path.split("/").length;
  to.meta.transitionName = toDepth < fromDepth ? "slide-right" : "slide-left";
});

export function setupRouter(app: App<Element>) {
  app.use(router);
}

export default router;

main.ts  修改

import { createApp } from "vue";
import App from "./App.vue";
import router, { setupRouter } from "@/router";
import { setupStore } from "@/store";
import requirePlugins from "./plugins";
import "lib-flexible/flexible"; // 用于设置rem基准值
import "vite-plugin-svg-icons/register"; // 注入svg

const app = createApp(App);
// router
setupRouter(app);
// store
setupStore(app);
// ui & global components & directives & other plugins
requirePlugins(app);

router.isReady().then(() => {
  app.mount("#app");
});

components/HelloWord.vue  修改

<template>
  <h1>{{ msg }}</h1>
  <button @click="inCrement"> count is: </button>
  <p>{{ count }}</p>
</template>

<script>
  import { defineComponent, computed } from 'vue'
  import { useStore } from 'vuex'
  import { key } from '../store'

  export default defineComponent({
    name: 'HelloWorld',
    props: {
      msg: {
        type: String,
        default: ''
      }
    },
    setup() {
      const store = useStore(key)

      const count = computed(() => store.state.count)

      return {
        count,
        inCrement: () => store.commit('increment')
      }
    }
  })
</script>

5. 按需引入 vant、移动端适配 rem

  • 引入 vant
pnpm i vant@next -S
pnpm i vite-plugin-style -D

vite.config.js 中加入

plugins: [
  ...// 新增
  styleImport({
    libs: [
      {
        libraryName: "vant",
        esModule: true,
        resolveStyle: (name) => `vant/es/${name}/style`,
      },
    ],
  }),
];
  • 移动端适配
pnpm i amfe-flexible -S
pnpm i postcss postcss-pxtorem [email protected] -D

根目录下 postcss.config.js

module.exports = {
  plugins: {
    autoprefixer: {},
    // 将单位转换为rem
    "postcss-pxtorem": {
      rootValue: 37.5, // 值:设计图宽度/20  (目标是将屏幕转化为20rem)
      propList: ["*"],
      // 该项仅在使用 Circle 组件时需要
      // van-circle__layer 原因参见 https://github.com/youzan/vant/issues/1948
      selectorBlackList: ["van-circle__layer", ".norem"], // 过滤掉.norem-开头的class,不进行rem转换
    },
  },
};

main.js 中加入

import "amfe-flexible"; // 用于设置rem基准值

新增.browserslistrc

# @Description: 浏览器兼容
# @Autor: huoyou
# @Date: 2021-07-23 11:08:28
# @LastEditTime: 2021-07-23 11:21:49

> 1%
last 2 versions
not dead
iOS >= 7
Android >= 4.1
IE >= 10

5. (推荐)另一种方式按需引入 vant, 自动引入 vue、vue-router、hooks

5.1 按需引入组件

参考链接

pnpm i unplugin-vue-components -D

vite.config.js 配置

import Components from 'unplugin-vue-components/vite';
import {
  // AntDesignVueResolver,
  // ElementPlusResolver,
  VantResolver
} from 'unplugin-vue-components/resolvers';



plugins: [
      vue(),
      // @ts-ignore
  		// 新增
      Components({
        resolvers: [VantResolver()],
        extensions: ['vue'],
        dts: 'src/types/components.d.ts',
        directoryAsNamespace: true
      }),
      viteSvgIcons({
        // Specify the icon folder to be cached
        iconDirs: [path.resolve(process.cwd(), './src/assets/icons')],
        // Specify symbolId format
        symbolId: 'icon-[dir]-[name]'
      })
    ],

5.2 自动引入 vue/vue-router/hooks 等

pnpm i unplugin-auto-import -D

vite.config.js 配置

// vite.config.js
import AutoImport from 'unplugin-auto-import/vite'

module.exports =
  plugins: [
    AutoImport({
      imports: ['vue', 'vue-router', 'vuex'],
      dts: 'src/types/auto-imports.d.ts'
    })
  ]
}

eslint 插件:

pnpm i vue-global-api -D

.eslintrc.js

globals: {
   Message: true,
   env: true,
   // 新增
   useRoute: true,
   useRouter: true,
   useStore: true
},
extends: [
  ...
  'vue-global-api',
]

使用前:

<script setup>
  import {(ref, computed, watch)} from 'vue' const counter = ref(0) const
  doubled = computed(() => counter.value * 2) watch(doubled, (v) =>{" "}
  {console.log("New value: " + v)})
</script>

使用后:

<script setup>
  const counter = ref(0) const doubled = computed(() => counter.value * 2)
  watch(doubled, (v) => {console.log("New value: " + v)})
</script>

6. 配置 eslint、prettier

  • vue
pnpm i eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
  • eslint: ESLint 的核心代码

  • prettier:prettier 插件的核心代码

  • eslint-config-prettier:解决 ESLint 中的样式规范和 prettier 中样式规范的冲突,以 prettier 的样式规范为准,使 ESLint 中的样式规范自动失效

  • eslint-plugin-prettier:将 prettier 作为 ESLint 规范来使用

  • eslint-plugin-vue:包含常用的 vue 规范

  • @typescript-eslint/parser:ESLint 的解析器,用于解析 typescript,从而检查和规范 Typescript 代码

  • @typescript-eslint/eslint-plugin:包含了各类定义好的检测 Typescript 代码的规范

  • react

pnpm i eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-react @typescript-eslint/parser @typescript-eslint/eslint-plugin -D

注意: 如果不生效时, 考虑是否版本问题, [email protected] [email protected]
vue 中,在根目录下建立 eslint 配置文件: .eslintrc.js

/**
 * @module .eslintrc
 * @author: huoyou
 * @description: eslint配置
 *
 * 所需插件
 * prettier // 规则见 https://prettier.io/docs/en/options.html
 * eslint // 规则见 https://cn.eslint.org/docs/rules/
 * eslint-plugin-vue 规则见 https://github.com/vuejs/eslint-plugin-vue
 * eslint-plugin-prettier // 将prettier作为ESLint规范来使用
 * eslint-config-prettier
 * @typescript-eslint/eslint-plugin
 * @typescript-eslint/parser // ESLint的解析器,用于解析typescript,从而检查和规范Typescript代码
 *
 * "off" 或 0 - 关闭规则
 * "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
 * "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
 */

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
  },
  globals: {
    Message: true,
    env: true,
    useRoute: true,
    useRouter: true,
    useStore: true,
  },
  /* 指定如何解析语法。可以为空,但若不为空,只能配该值,原因见下文。*/
  parser: "vue-eslint-parser",
  /* 优先级低于parse的语法解析配置 */
  parserOptions: {
    parser: "@typescript-eslint/parser", // Specifies the ESLint parser
    ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
    sourceType: "module", // Allows for the use of imports
    ecmaFeatures: {
      // tsx: true, // Allows for the parsing of JSX
      jsx: true,
    },
  },
  extends: [
    "vue-global-api",
    "eslint:recommended",
    "plugin:vue/vue3-recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier",
    "plugin:prettier/recommended",
    "plugin:jest/recommended",
  ],
  plugins: ["vue"],
  rules: {
    "no-console": process.env.NODE_ENV === "production" ? 1 : 0,
    "no-debugger": process.env.NODE_ENV === "production" ? 1 : 0,
    eqeqeq: 2, // 要求使用 === 和 !==
    "vue/eqeqeq": 2, // 要求使用 === 和 !==
    "no-undef": 2, // 禁用未声明的变量
    "vue/require-v-for-key": 1, // 当v-for写在自定义组件上时,它需要同时使用v-bind:key。在其他元素上,v-bind:key也最好写。
    "no-unused-vars": 0, // 禁止出现未使用过的变量
    "vars-on-top": 0, // 要求所有的 var 声明出现在它们所在的作用域顶部
    "prefer-destructuring": 0, // 优先使用数组和对象解构
    "no-useless-concat": 1, // 禁止不必要的字符串字面量或模板字面量的连接
    "no-useless-escape": 0, // 禁止不必要的转义字符
    "consistent-return": 0, // 要求 return 语句要么总是指定返回的值,要么不指定
    camelcase: 0, // 强制使用骆驼拼写法命名约定
    "no-redeclare": 1, // 禁止多次声明同一变量
    "array-callback-return": 1, // 强制数组方法的回调函数中有 return 语句,Array有几种过滤,映射和折叠的方法。如果我们忘记return在这些回调中写入语句,那可能是一个错误。
    "default-case": 1, // 要求 switch 语句中有 default 分支
    "no-fallthrough": 1, // 禁止 case 语句落空
    "no-lonely-if": 1, // 禁止 if 作为唯一的语句出现在 else 语句中.如果一个if陈述是该else块中唯一的陈述,那么使用一个else if表格通常会更清晰。
    "no-irregular-whitespace": 1, // 禁止在字符串和注释之外不规则的空白
    "prefer-const": 0, // 要求使用 const 声明那些声明后不再被修改的变量.如果一个变量从不重新分配,使用const声明更好。const 声明告诉读者,“这个变量永远不会被重新分配,”减少认知负荷并提高可维护性。
    "no-use-before-define": 1, // 禁止在变量定义之前使用它们
    "vue/attributes-order": 2, // vue api使用顺序
    "vue/order-in-components": [
      2,
      {
        order: [
          "el",
          "name",
          "key",
          "parent",
          "functional",
          ["delimiters", "comments"],
          ["components", "directives", "filters"],
          "extends",
          "mixins",
          ["provide", "inject"],
          "validate",
          "scrollToTop",
          "transition",
          "loading",
          "inheritAttrs",
          "model",
          ["props", "propsData"],
          "emits",
          "setup",
          "asyncData",
          "data",
          "computed",
          "watch",
          "created",
          "mounted",
          "methods",
          ["template", "render"],
          "renderError",
        ],
      },
    ],
    "vue/no-multiple-template-root": 0,
    "@typescript-eslint/explicit-module-boundary-types": 0,
    "@typescript-eslint/no-var-requires": 0,
    "@typescript-eslint/no-unused-vars": 0,
    "@typescript-eslint/ban-ts-comment": 0,
    "@typescript-eslint/no-explicit-any": 0,
    "@typescript-eslint/no-empty-function": 0,
  },
  overrides: [
    {
      files: ["**/__tests__/*.{j,t}s?(x)"],
      env: {
        mocha: true,
      },
    },
  ],
};

react 中,在根目录下建立 eslint 配置文件: .eslintrc.js

/**
 * @module .eslintrc
 * @author: huoyou
 * @description: eslint配置
 *
 * 所需插件
 * prettier // 规则见 https://prettier.io/docs/en/options.html
 * eslint // 规则见 https://cn.eslint.org/docs/rules/
 * eslint-plugin-react 规则见 https://github.com/yannickcr/eslint-plugin-react
 * eslint-plugin-prettier // 将prettier作为ESLint规范来使用
 * eslint-config-prettier
 * @typescript-eslint/eslint-plugin
 * @typescript-eslint/parser // ESLint的解析器,用于解析typescript,从而检查和规范Typescript代码
 *
 * "off" 或 0 - 关闭规则
 * "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
 * "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
 */

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
  },
  /* 优先级低于parse的语法解析配置 */
  parserOptions: {
    parser: "@typescript-eslint/parser", // Specifies the ESLint parser
    ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
    sourceType: "module", // Allows for the use of imports
    ecmaFeatures: {
      // tsx: true, // Allows for the parsing of JSX
      jsx: true,
    },
  },
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier",
    "plugin:prettier/recommended",
  ],
  plugins: ["react"],
  rules: {
    "no-console": process.env.NODE_ENV === "production" ? 1 : 0,
    "no-debugger": process.env.NODE_ENV === "production" ? 1 : 0,
    eqeqeq: 2, // 要求使用 === 和 !==
    "vue/eqeqeq": 2, // 要求使用 === 和 !==
    "no-undef": 2, // 禁用未声明的变量
    "vue/require-v-for-key": 1, // 当v-for写在自定义组件上时,它需要同时使用v-bind:key。在其他元素上,v-bind:key也最好写。
    "no-unused-vars": 1, // 禁止出现未使用过的变量
    "vars-on-top": 1, // 要求所有的 var 声明出现在它们所在的作用域顶部
    "prefer-destructuring": 0, // 优先使用数组和对象解构
    "no-useless-concat": 1, // 禁止不必要的字符串字面量或模板字面量的连接
    "no-useless-escape": 0, // 禁止不必要的转义字符
    "consistent-return": 1, // 要求 return 语句要么总是指定返回的值,要么不指定
    camelcase: 0, // 强制使用骆驼拼写法命名约定
    "no-redeclare": 1, // 禁止多次声明同一变量
    "array-callback-return": 1, // 强制数组方法的回调函数中有 return 语句,Array有几种过滤,映射和折叠的方法。如果我们忘记return在这些回调中写入语句,那可能是一个错误。
    "default-case": 1, // 要求 switch 语句中有 default 分支
    "no-fallthrough": 1, // 禁止 case 语句落空
    "no-lonely-if": 1, // 禁止 if 作为唯一的语句出现在 else 语句中.如果一个if陈述是该else块中唯一的陈述,那么使用一个else if表格通常会更清晰。
    "no-irregular-whitespace": 1, // 禁止在字符串和注释之外不规则的空白
    "prefer-const": 0, // 要求使用 const 声明那些声明后不再被修改的变量.如果一个变量从不重新分配,使用const声明更好。const 声明告诉读者,“这个变量永远不会被重新分配,”减少认知负荷并提高可维护性。
    "no-use-before-define": 1, // 禁止在变量定义之前使用它们
    "@typescript-eslint/explicit-module-boundary-types": 0,
    "react/react-in-jsx-scope": 0,
    "@typescript-eslint/no-var-requires": 0,
  },
  overrides: [
    {
      files: ["**/__tests__/*.{j,t}s?(x)"],
      env: {
        mocha: true,
      },
    },
  ],
};

在根目录下建立 prettier 配置文件: .prettierrc.js

/*
 * @module .prettierrc
 * @author: huoyou
 * @description: eslint配置
 */
module.exports = {
  printWidth: 100, // 单行输出(不折行)的(最大)长度
  tabWidth: 2, // 每个缩进级别的空格数
  tabs: false, // 使用制表符 (tab) 缩进行而不是空格 (space)。
  semi: true, // 是否在语句末尾打印分号
  singleQuote: true, // 是否使用单引号
  quoteProps: "as-needed", // 仅在需要时在对象属性周围添加引号
  jsxSingleQuote: false, // jsx 不使用单引号,而使用双引号
  trailingComma: "none", // 去除对象最末尾元素跟随的逗号
  bracketSpacing: true, // 是否在对象属性添加空格
  jsxBracketSameLine: true, // 将 > 多行 JSX 元素放在最后一行的末尾,而不是单独放在下一行(不适用于自闭元素),默认false,这里选择>不另起一行
  arrowParens: "always", // 箭头函数,只有一个参数的时候,也需要括号
  proseWrap: "always", // 当超出print width(上面有这个参数)时就折行
  htmlWhitespaceSensitivity: "ignore", // 指定 HTML 文件的全局空白区域敏感度, "ignore" - 空格被认为是不敏感的
  vueIndentScriptAndStyle: false, // 在VUE文件中不要缩进脚本和样式标记
  stylelintIntegration: true,
  endOfLine: "auto",
};

7. 配置 stylelint

pnpm i stylelint stylelint-scss stylelint-config-standard-scss stylelint-config-prettier -D

根目录下新增 **.stylelintrc.js**文件

/**
 * @module .stylelintrc
 * @author: huoyou
 * @description: css校验配置
 */
module.exports = {
  extends: ["stylelint-config-standard-scss", "stylelint-config-prettier"],
  rules: {
    "declaration-colon-space-after": "always-single-line",
    "declaration-colon-space-before": "never",
    "declaration-block-trailing-semicolon": null,
    "declaration-block-semicolon-space-before": "never",
    "media-feature-name-no-unknown": null,
    "selector-pseudo-class-no-unknown": [
      true,
      {
        ignorePseudoClasses: ["deep"],
      },
    ],
    "rule-empty-line-before": [
      "always",
      {
        ignore: ["after-comment", "first-nested"],
      },
    ],
    // style calc中使用v-bind
    "function-calc-no-unspaced-operator": null,
  },
};

8. 引入 jest 测试

pnpm i @vue/test-utils@next jest vue-jest@next ts-jest @types/jest eslint-plugin-jest -D

创建 jest.config.js

module.exports = {
  moduleFileExtensions: ["vue", "js", "ts"],
  preset: "ts-jest",
  testEnvironment: "jsdom",
  transform: {
    "^.+\\.vue$": "vue-jest", // vue 文件用 vue-jest 转换
    "^.+\\.ts$": "ts-jest", // ts 文件用 ts-jest 转换
  },
  // 匹配 __tests__ 目录下的 .js/.ts 文件 或其他目录下的 xx.test.js/ts xx.spec.js/ts
  testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(ts)$",
};

在上面的 jest.config.js 文件中,我们配置只匹配 __tests__ 目录下的任意 .ts 文件或其他目录下的 xx.test.ts/xx.spec.ts 文件进行单元测试。

这里,我们在项目根目录下创建 tests 目录来存储单元测试文件

├── src/
└── tests/                           // 单元测试目录
    ├── Test.spec.ts                 // Test 组件测试

  • Test.vue
<template>
  <div class="test-container page-container">
    <div class="page-title">Unit Test Page</div>
    <p>count is: {{ count }}</p>
    <button @click="increment">increment</button>
  </div>
</template>

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

  export default defineComponent({
    name: 'Vuex',
    setup() {
      const count = ref<number>(0)
      const increment = () => {
        count.value += 1
      }
      return { count, increment }
    }
  })
</script>

  • Test.spec.ts
import { mount } from "@vue/test-utils";
import Test from "../src/views/Test.vue";

test("Test.vue", async () => {
  const wrapper = mount(Test);
  expect(wrapper.html()).toContain("Unit Test Page");
  expect(wrapper.html()).toContain("count is: 0");
  await wrapper.find("button").trigger("click");
  expect(wrapper.html()).toContain("count is: 1");
});

TypeScript 的编译器也会提示 jest 的方法和类型找不到,我们还需把 @types/jest 添加根目录下的  ts.config.json(TypeScript 配置文件)中:

{
  "compilerOptions": {
    ...
    "types": ["vite/client", "jest"]
  },
}

添加 eslint-plugin-jest 到 ESLint 配置文件  .eslintrc.js  中

module.exports = {
  ...
  extends: [
    ...
    'plugin:jest/recommended'
  ],
  ...
}

在  package.json  命令(scripts)中加入 “test”: “jest” 。 如下:

"scripts": {
    "dev": "vite",
    "build": "vite build",
    "test": "jest"
 },

运行  yarn test  查看测试结果

使用 husky 命令在  .husky  目录下自动创建  pre-push hook 文件,并在此执行单元测试命令  npm run test

npx husky add .husky/pre-push "npm run test $1"

{

9. husky 和 lint-staged 构建代码工作流

pnpm i husky lint-staged @commitlint/cli @commitlint/config-conventional -D
  • @commitlint/cli: commitlint 的命令行工具
  • @commitlint/config-conventional: commitlint 的规则集
  • husky: 阻止不符合提交规则的 git 记录
npx husky-init

修改 ./husky/pre-commit 钩子

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged

修改 ./husky/commit-msg

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx --no-install commitlint --config .commitlintrc.js --edit $1

package.json 配置:

"scripts": {
   "fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue,.html src",
   "lint": "eslint --ext .js,.jsx,.ts,.tsx,.vue,.html src",
   "stylelint": "stylelint --fix .css,.less,.scss src",
},
"lint-staged": {
  "src/**/*.{js,jsx,ts,.tsx,vue,html,md}": "eslint --config .eslintrc.js",
  "src/**/*.{css,less,scss}": "stylelint --config .stylelintrc.js",
  "*.{ts,tsx,js,json,html,yml,css,less,md}": "prettier --write"
}

根目录创建** .commitlintrc.js**

module.exports = {
  extends: ["@commitlint/config-conventional"],
  rules: {
    "type-enum": [
      2,
      "always",
      [
        "feat", // 新功能(feature)
        "fix", // 修补bug
        "docs", // 文档(documentation)
        "style", // 格式(不影响代码运行的变动)
        "refactor", // 重构(即不是新增功能,也不是修改bug的代码变动)
        "test", // 增加测试
        "revert", // 回滚
        "config", // 构建过程或辅助工具的变动
        "chore", // 其他改动
      ],
    ],
    "type-empty": [2, "never"], // 提交不符合规范时,也可以提交,但是会有警告
    "subject-empty": [2, "never"], // 提交不符合规范时,也可以提交,但是会有警告
    "subject-full-stop": [0, "never"],
    "subject-case": [0, "never"],
  },
};

10. vscode 保存自动格式化

安装插件 ESlint Prettier-Code formatter Stylelint Vetur Vue VSCode Snippets
注意: vue3 安装 volar、Vue 3 Snippets 插件, 同时 disabled Vetur Vue VSCode Snippets 这两个插件
settings.json配置:

{
  // 基础设置
  "editor.detectIndentation": false, // 重置 editor.tabSize属性
  "editor.tabSize": 2, // 缩进2单元格
  "files.autoSave": "afterDelay", // 打开自动保存
  "editor.fontSize": 14, // 默认字体大小
  "editor.lineHeight": 20, // 行高
  "editor.mouseWheelZoom": false, // 通过使用鼠标滚轮同时按住 Ctrl 可缩放编辑器的字体
  "editor.wordWrap": "on", // 行太长自动换行
  "editor.formatOnPaste": false, // 编辑粘贴自动格式化
  "html.format.wrapAttributes": "auto", // 格式化html的标签属性
  "terminal.integrated.rendererType": "dom",
  "search.followSymlinks": false, // 关闭rg.exe进程
  "workbench.startupEditor": "welcomePage", // 图标
  "workbench.iconTheme": "vscode-icons",
  "workbench.colorTheme": "escook dark soft",
  "files.saveConflictResolution": "overwriteFileOnDisk",
  "explorer.confirmDelete": false,
  // 格式化校验
  "javascript.validate.enable": false,
  "editor.defaultFormatter": "esbenp.prettier-vscode", // 设置默认格式化工具为prettier
  "editor.formatOnSave": false, // 保存自动格式化
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
    "source.fixAll.stylelint": true
  },
  "vetur.format.enable": false,
  "vetur.validation.template": false,
  "vetur.validation.interpolation": false,
  "vetur.validation.style": false,
  "vetur.languageFeatures.codeActions": false,
  "vetur.validation.script": false,
  // "vetur.format.defaultFormatter.html": "js-beautify-html",
  // "vetur.format.defaultFormatterOptions": {
  //   "js-beautify-html": {
  //     "wrap_attributes": "force-aligned" //属性强制折行对齐
  //   }
  // },
  "eslint.format.enable": true, //是否开启vscode的eslint
  "eslint.options": {
    //指定vscode的eslint所处理的文件的后缀
    "extensions": [".js", ".jsx", ".vue", ".react", ".ts", ".tsx"]
  },
  // eslint 校验准则
  "eslint.validate": [
    "vue",
    "html",
    "jsx",
    "tsx",
    "javascript",
    "typescript",
    "javascriptreact",
    "typescriptreact"
  ],
  "stylelint.autoFix": true,
  "stylelint.enable": true,
  "css.validate": false,
  "less.validate": false,
  "scss.validate": false,
  "css.lint.unknownAtRules": "ignore",
  "scss.lint.unknownAtRules": "ignore"
}

11. 生成自定义 changelog

pnpm i conventional-changelog-cli conventional-changelog-custom-config -D

package.json 下加入

"scripts": {
  "log": "conventional-changelog -p custom-config -i CHANGELOG.md -s -r 0  -n ./changelog-option.js"
},
"changelog": {
  "bugsUrl": "https://github.com/huoyou/vue_cli3/issues/",
  "emojis": true,
  "authorName": true,
  "authorEmail": true
}

根目录下添加 changelog-option.js

const compareFunc = require("compare-func");
module.exports = {
  writerOpts: {
    transform: (commit, context) => {
      let discard = true;
      const issues = [];

      commit.notes.forEach((note) => {
        note.title = "BREAKING CHANGES";
        discard = false;
      });
      if (commit.type === "feat") {
        commit.type = "✨ Features | 新功能";
      } else if (commit.type === "fix") {
        commit.type = " Bug Fixes | Bug 修复";
      } else if (commit.type === "perf") {
        commit.type = "⚡ Performance Improvements | 性能优化";
      } else if (commit.type === "revert" || commit.revert) {
        commit.type = "⏪ Reverts | 回退";
        // } else if (discard) {
        //   return;
      } else if (commit.type === "docs") {
        commit.type = " Documentation | 文档";
      } else if (commit.type === "style") {
        commit.type = " Styles | 风格";
      } else if (commit.type === "refactor") {
        commit.type = "♻ Code Refactoring | 代码重构";
      } else if (commit.type === "test") {
        commit.type = "✅ Tests | 测试";
      } else if (commit.type === "build") {
        commit.type = "‍ Build System | 构建";
      } else if (commit.type === "ci") {
        commit.type = " Continuous Integration | CI 配置";
      } else if (commit.type === "chore") {
        commit.type = " Chores | 其他更新";
      } else {
        commit.type = " others | 未命名";
      }

      if (commit.scope === "*") {
        commit.scope = "";
      }
      if (typeof commit.hash === "string") {
        commit.hash = commit.hash.substring(0, 7);
      }
      if (typeof commit.subject === "string") {
        let url = context.repository
          ? `${context.host}/${context.owner}/${context.repository}`
          : context.repoUrl;
        if (url) {
          url = `${url}/issues/`;
          // Issue URLs.
          commit.subject = commit.subject.replace(/#([0-9]+)/g, (_, issue) => {
            issues.push(issue);
            return `[#${issue}](${url}${issue})`;
          });
        }
        if (context.host) {
          // User URLs.
          commit.subject = commit.subject.replace(
            /\B@([a-z0-9](?:-?[a-z0-9/]){0,38})/g,
            (_, username) => {
              if (username.includes("/")) {
                return `@${username}`;
              }

              return `[@${username}](${context.host}/${username})`;
            }
          );
        }
      }

      // remove references that already appear in the subject
      commit.references = commit.references.filter((reference) => {
        if (issues.indexOf(reference.issue) === -1) {
          return true;
        }

        return false;
      });
      return commit;
    },
    groupBy: "type",
    commitGroupsSort: "title",
    commitsSort: ["scope", "subject"],
    noteGroupsSort: "title",
    notesSort: compareFunc,
  },
};

你可能感兴趣的:(typescript,javascript,vue.js)