搭建一个前端工程化项目

232 阅读9分钟

技术栈

基础搭建

构建项目雏形

在项目的命令行中运行以下命令:

//确保安装了最新版本的 node
node -v  //v20.5.1
npm -v   //9.8.1
//npm 7+, extra double-dash is needed:
npm create vite@latest vite-vue3-starter -- --template vue

这一指令将会安装并执行 create-vite,它是一个基本模板快速启动项目工具。

image.png 在项目被创建后,通过以下步骤安装依赖并启动开发服务器:

//进入项目
cd vite-vue3-starter
// 安装依赖
npm install
// 启动项目
npm run dev

image.png

Vite 基础配置

Vite 配置文件 vite.config.js 位于项目根目录下,项目启动时会自动读取。 修改配置:

  • 公共基础路径
  • 自定义路径别名
  • 服务器选项
  • 构建选项
  • (关于 Vite 更多配置项及用法,请查看 Vite 官网 vitejs.dev/config/
import { defineConfig } from 'vite'
import { resolve } from 'path';
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  base: './',
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, './src') ,
    },
  },
  server: {
    // 是否开启 https
    https: false,
    // 端口号
    port: 3000,
    // 监听所有地址
    host: '0.0.0.0',
    // 服务启动时是否自动打开浏览器
    open: true,
    // 允许跨域
    cors: true,
    // 自定义代理规则
    proxy: {},
  },
  build: {
    // 设置最终构建的浏览器兼容目标
    target: 'es2015',
    // 构建后是否生成 source map 文件
    sourcemap: false,
    //  chunk 大小警告的限制(以 kbs 为单位)
    chunkSizeWarningLimit: 2000,
    // 启用/禁用 gzip 压缩大小报告
    reportCompressedSize: false,
  },
})

规范目录结构

├── dist/
└── src/
    ├── api/                       // 接口请求目录
    ├── assets/                    // 静态资源目录
    ├── common/                    // 通用类库目录
    ├── components/                // 公共组件目录
    ├── router/                    // 路由配置目录
    ├── store/                     // 状态管理目录
    ├── style/                     // 通用样式目录
    ├── utils/                     // 工具函数目录
    ├── views/                     // 页面组件目录
    ├── App.vue
    ├── main.js
├── tests/                         // 单元测试目录
├── index.html
├── jsconfig.json                  // JavaScript 配置文件
├── vite.config.js                 // Vite 配置文件
└── package.json

集成 Vue Router 路由工具

安装依赖

npm i vue-router@4

创建路由配置文件

在 src/router 目录下新建 index.js 文件与 modules 文件夹

└── src/ 
    ├── router/ 
        ├── modules/ // 路由模块 
    ├── index.js // 路由配置文件

关于路由表,建议根据功能的不同来拆分到 modules 文件夹中,好处是:

  • 方便后期维护
  • 减少 Git 合并代码冲突可能性

我们在modules文件夹下创建一个demo文件夹,用来存放基本的路由信息。同样也要在src下创建views目录,并且在该目录下创建HomeView.vue、PiniaView.vue。

    export default [
  {
    path: '/',
    name: 'home',
    component: () => import('@/views/HomeView.vue'),
  },
  {
    path: '/pinia',
    name: 'pinia',
    component: () => import('@/views/PiniaView.vue'),
  },
];

//路由配置文件
import { createRouter, createWebHistory } from "vue-router";;

import baseRouters from './modules/base';

const routes = [...baseRouters];

const router = createRouter({
  history: createWebHistory(import.meta.evn.BASE_URL),
  routes,
  scrollBehavior() {
    return {
      el: '#app',
      top: 0,
      behavior:'smooth',
    };
  },
});

export default router;

解释两个关键字

1、 createWebHistory

createWebHistory 是 Vue Router 提供的一种基于浏览器 history API 的路由模式, 它使用了 HTML5 中的 history.pushStatehistory.replaceState 方法来实现路由跳转。这种模式可以使得 URL 更加直观,而且不会在 URL 中添加任何特殊字符。

2、 scrollBehavior

scrollBehavior 是一种 “记录浏览器滚动条默认位置” 的解决方案。

考虑一种情景: 用户在一个商品列表中查看详情页以后,想要返回列表页刚刚浏览的位置,这种需求该如何实现?

在用户切换页面后,列表页组件已经被销毁,所以重新返回到列表页后页面会置顶,需要重新下拉查看列表,这样就做了很多没有必要的操作,也是不符合用户的预期。

大概有3种解决方案:

  • 第一种:使用 vue-router 的方法scrollBehavior(推荐)
    • 注意: 这个功能只在支持 history.pushState 的浏览器中可用
    const scrollBehavior = function scrollBehavior (to, from, savedPosition) {
      if (savedPosition) {
        return savedPosition;
      }else {
          return { x: 0, y: 0 }
       }
    };
    const router = new Router({
      routes,
      scrollBehavior,
    });
    
  • 第二种:使用路由守卫(组件内守卫)
    • 原理:在beforRouterLeave的路由钩子记录当前页面滚动位置
    //在页面离开时记录滚动位置
    beforeRouteLeave (to, from, next) {
        this.scrollTop = document.documentElement.scrollTop || document.body.scrollTop
        next()
      },
    
    //进入该页面时,用之前保存的滚动位置赋值
    beforeRouteEnter (to, from, next) {
        next(vm => {
          document.body.scrollTop = vm.scrollTop
        })
      },
    
  • 第三种:使用 缓存
    // App.vue
    <template>
      <div id="app">
        <!-- <router-view/> -->
        <keep-alive>
            <router-view v-if="$route.meta.keepAlive"></router-view>
        </keep-alive>
        <router-view v-if="!$route.meta.keepAlive" />
      </div>
    </template>
    
    // router.js
     routes: [
        {
          path: '/',
          name: 'List',
          component: () => import('./views/index/list.vue'),
          meta: {
            keepAlive: true // 需要缓存
          }
        },
        {
          path: '/content/:contentId',
          name: 'content',
          component: () => import('./views/index/content.vue'),
          meta: {
            keepAlive: false // 不需要缓存
          }
        },
    ]
    

创建路由配置文件

在 main.js 文件中挂载路由配置

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')

集成 Pinia 全局状态管理工具

安装依赖

npm i pinia

创建仓库配置文件

在 src/store 目录下新建 index.js 文件与 modules 文件夹

开发中需要将不同功能所对应的状态,拆分到不同的 modules,好处如同路由模块一样。

└── src/
    ├── store/
    	├── modules/  // 仓库模块
        ├── index.js  // 仓库配置文件
// src/store/modules/counter.js
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 1,
  }),
  actions: {
    accumulate() {
      this.count++;
    },
  },
});
// src/store/index.js
import { createPinia } from 'pinia';

const store = createPinia();

export default store;

export * from './modules/counter';

挂载 Pinia 配置

在 main.js 文件中挂载 Vuex 配

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
import store from './store';

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

Pinia 和 Vuex 区别

Pinia和Vuex都是Vue.js状态管理库,但它们在一些方面有所不同。

  1. Pinia是一个轻量级的状态管理库,它专注于提供一个简单的API来管理应用程序的状态。相比之下,Vuex是一个更完整的状态管理库,它提供了更多的功能,比如模块化、插件和严格模式等;
  2. Pinia是基于Vue 3 的 Composition API 构建的,这使得它更加灵活和可组合。而 Vuex 则是基于Vue 2 的 Options API构建的,因此在某些方面可能会受到限制。
  3. Pinia采用了类似于React Hooks的方式来管理状态,这使得它更加直观和易于使用。Vuex则采用了一种基于 mutations 和 actions 的方式来管理状态,这可能需要更多的代码来实现相同的功能。

Pinia 和 Vuex 都有其优点和缺点

集成 TDesign Vue Next 组件库

安装依赖

npm i tdesign-vue-next

基础使用

全局使用

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
import store from './store';
import TDesign from 'tdesign-vue-next';
import 'tdesign-vue-next/es/style/index.css'; // 引入组件库全局样式资源

createApp(App).use(router)
              .use(store)
              .use(TDesign)
              .mount('#app')

通过插件按需引用使用

使用 unplugin-vue-components 和 unplugin-auto-import 来实现自动导入

npm install unplugin-vue-components unplugin-auto-import -D
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
import store from './store';
import 'tdesign-vue-next/es/style/index.css'; // 引入组件库全局样式资源

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

在 Vite 对应的配置文件 vite.config.js 添加上述插件:

import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { TDesignResolver } from 'unplugin-vue-components/resolvers';

export default {
  plugins: [
    AutoImport({
      resolvers: [TDesignResolver({
        library: 'vue-next'
      })],
    }),
    Components({
      resolvers: [TDesignResolver({
        library: 'vue-next'
      })],
    }),
  ],
};
}

集成 Axios HTTP 工具

安装依赖

npm i axios

请求配置

在 utils 目录下创建 request.js 文件,配置好适合自己业务的请求拦截和响应拦截

└── src/
    ├── api  // 接口
    ├── utils/
        ├── request.js  // axios 请求库二次封装
// src/utils/request.js
import axios from 'axios';

// 创建请求实例
const instance = axios.create({
  baseURL: '/api',
  // 指定请求超时的毫秒数
  timeout: 1000,
  // 表示跨域请求时是否需要使用凭证
  withCredentials: false,
});

// 前置拦截器(发起请求之前的拦截)
instance.interceptors.request.use(
  (config) => {
    /**
     * 在这里一般会携带前台的参数发送给后台,比如下面这段代码:
     * const token = getToken()
     * if (token) {
     *  config.headers.token = token
     * }
    */
    return config;
  },
  (error) => {
    return Promise.reject(error);
  },
);

// 后置拦截器(获取到响应时的拦截)
instance.interceptors.response.use(
  (response) => {
    /**
     * 根据你的项目实际情况来对 response 和 error 做处理
     * 这里对 response 和 error 不做任何处理,直接返回
     */
    return response;
  },
  (error) => {
    const { response } = error;
    if (response && response.data) {
      return Promise.reject(error);
    }
    const { message } = error;
    console.error(message);
    return Promise.reject(error);
  },
);

// 导出常用函数

/**
 * @param {string} url
 * @param {object} data
 * @param {object} params
 */
export const post = (url, data = {}, params = {}) => {
  return instance({
    method: 'post',
    url,
    data,
    params,
  });
};

/**
 * @param {string} url
 * @param {object} params
 */
export const get = (url, params = {}) => {
  return instance({
    method: 'get',
    url,
    params,
  });
};

/**
 * @param {string} url
 * @param {object} data
 * @param {object} params
 */
export const put = (url, data = {}, params = {}) => {
  return instance({
    method: 'put',
    url,
    params,
    data,
  });
};

/**
 * @param {string} url
 * @param {object} params
 */
export const _delete = (url, params = {}) => {
  return instance({
    method: 'delete',
    url,
    params,
  });
};

export default instance;

在 api 文件夹中以业务模型对接口进行拆分,比如将所有跟用户相关接口封装在 User 类中,此类称作用户模型。

在 User 类中比如有登录、注册、获取用户信息等方法,如果有业务逻辑变动,只需要修改相关方法即可。

import { post } from '@/utils/request';

export default class User {
  /**
   * 登录
   * @param {String} username 用户名
   * @param {String} password 密码
   * @returns
   */
  static async login(username, password) {
    return post('/login', {
      username,
      password,
    });
  }
}

模拟演示

<script>
import User from '@/api/user';

export default {
  data() {
    return {
      username: '',
      password: '',
    };
  },
  methods: {
    async login() {
      const res = await User.login(this.username, this.password);
      console.log(res);
    },
  },
};
</script>

集成 CSS 预处理器 Less

本项目使用 CSS 预处理器 Less,直接安装为开发依赖即可。

Vite 内部已帮我们集成了相关的 loader,不需要额外配置。

npm i less -D

如何使用

在 <style></style> 样式标签中引用 lang="less" 即可。

<style lang="less"></style>

全局样式

在 src/style 目录下创建 variables.less 全局样式文件:

└── src/
    ├── style/
        ├── variables.less  // 全局样式文件

在 vite.config.js 配置文件中新增CSS 预处理器相关配置即可实现 less 全局样式:

import { resolve } from 'path';

export default defineConfig({
    css: {
      preprocessorOptions: {
        less: {
          modifyVars: {
            hack: `true; @import (reference) "${resolve('src/style/variables.less')}";`,
          },
          math: 'strict',
          javascriptEnabled: true,
        },
      },
    },
});

样式穿透

在 Vue3 中,改变了以往样式穿透的语法,如果继续使用 ::v-deep/deep/>>> 等语法的话,会出现一个警告,下面是新的语法:

/* 深度选择器 */
:deep(selector) {
  /* ... */
}

/* 插槽选择器 */
:slotted(selector) {
  /* ... */
}

/* 全局选择器 */
:global(selector) {
  /* ... */
}

至此,一个基于 JavaScript + Vite3 + Vue3 + Vue Router + Pinia + Axios + Less 的前端项目开发环境搭建完毕。

代码规范

如何使用 EditorConfig + ESLint + Prettier + Stylelint 组合来实现代码规范化。

  • 解决团队之间代码不规范导致的可读性差和可维护性差的问题。

  • 解决团队成员不同编辑器导致的编码规范不统一问题。

  • 提前发现代码风格问题,给出对应规范提示,及时修复。

  • 减少代码审查过程中反反复复的修改过程,节约时间。

  • 自动格式化,统一编码风格,从此和脏乱差的代码说再见。

集成 EditorConfig 配置

EditorConfig 主要用于统一不同 IDE 编辑器的编码风格。 在项目根目录下添加 .editorconfig 文件:

# 表示是最顶层的 EditorConfig 配置文件
root = true

# 表示所有文件适用
[*]
# 缩进风格(tab | space)
indent_style = space
# 控制换行类型(lf | cr | crlf)
end_of_line = lf
# 设置文件字符集为 utf-8
charset = utf-8
# 去除行首的任意空白字符
trim_trailing_whitespace = true
# 始终在文件末尾插入一个新行
insert_final_newline = true

# 表示仅 md 文件适用以下规则
[*.md]
max_line_length = off
trim_trailing_whitespace = false

# 表示仅 ts、js、vue、css 文件适用以下规则
[*.{ts,js,vue,css}]
indent_size = 2

很多 IDE 中会默认支持此配置,但是也有些不支持,如:VSCode、Atom、Sublime Text 等。 具体列表可以参考官网,如果在 VSCode 中使用需要安装 EditorConfig for VS Code 插件。

image.png

集成 ESLint 配置

ESLint 是针对 EScript 的一款代码检测工具,它可以检测项目中编写不规范的代码,如果写出不符合规范的代码会被警告。

由此我们就可以借助于 ESLint 强大的功能来统一团队的编码规范。

安装依赖

npm i eslint eslint-define-config eslint-config-airbnb-base eslint-plugin-import eslint-plugin-vue vue-eslint-parser -D

安装插件

Visual Studio Code 编辑器使用 ESLint 配置需要下载插件 ESLint 。

image.png

创建 ESLint 配置文件

在项目根目录创建 .eslintrc.js 文件,并填入以下内容:

const { defineConfig } = require('eslint-define-config');

module.exports = defineConfig({
  root: true,
  env: {
    browser: true,
    node: true,
    jest: true,
    es6: true,
  },
  plugins: ['vue'],
  parser: 'vue-eslint-parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
    allowImportExportEverywhere: true,
    ecmaFeatures: {
      jsx: true,
    },
  },
  extends: [
    'eslint-config-airbnb-base',
    'eslint:recommended',
    'plugin:vue/vue3-essential',
    'plugin:vue/vue3-recommended',
    'plugin:prettier/recommended',
  ],
  rules: {
    // 禁止使用多余的包
    'import/no-extraneous-dependencies': 0,
    // 确保在导入路径内一致使用文件扩展名
    'import/extensions': 0,
    // 确保导入指向可以解析的文件/模块
    'import/no-unresolved': 0,
    // 首选默认导出导入/首选默认导出
    'import/prefer-default-export': 0,
    // 要求使用 let 或 const 而不是 var
    'no-var': 'error',
    // 禁止使用 new 以避免产生副作用
    'no-new': 1,
    // 禁止变量声明与外层作用域的变量同名
    'no-shadow': 0,
    // 禁用 console
    'no-console': 0,
    // 禁止标识符中有悬空下划线
    'no-underscore-dangle': 0,
    // 禁止在可能与比较操作符相混淆的地方使用箭头函数
    'no-confusing-arrow': 0,
    // 禁用一元操作符 ++ 和 --
    'no-plusplus': 0,
    // 禁止对 function 的参数进行重新赋值
    'no-param-reassign': 0,
    // 禁用特定的语法
    'no-restricted-syntax': 0,
    // 禁止在变量定义之前使用它们
    'no-use-before-define': 0,
    // 禁止直接调用 Object.prototypes 的内置属性
    'no-prototype-builtins': 0,
    // 禁止可以在有更简单的可替代的表达式时使用三元操作符
    'no-unneeded-ternary': 'error',
    // 禁止重复模块导入
    'no-duplicate-imports': 'error',
    // 禁止在对象中使用不必要的计算属性
    'no-useless-computed-key': 'error',
    // 禁止不必要的转义字符
    'no-useless-escape': 0,
    // 禁用 continue 语句
    'no-continue': 0,
    // 强制使用一致的缩进
    indent: ['error', 2, { SwitchCase: 1 }],
    // 强制使用骆驼拼写法命名约定
    camelcase: 0,
    // 强制类方法使用 this
    'class-methods-use-this': 0,
    // 要求构造函数首字母大写
    'new-cap': 0,
    // 强制一致地使用 function 声明或表达式
    'func-style': 0,
    // 强制一行的最大长度
    'max-len': 0,
    // 要求 return 语句要么总是指定返回的值,要么不指定
    'consistent-return': 0,
    // 强制switch要有default分支
    'default-case': 2,
    // 强制剩余和扩展运算符及其表达式之间有空格
    'rest-spread-spacing': 'error',
    // 要求使用 const 声明那些声明后不再被修改的变量
    'prefer-const': 'error',
    // 强制箭头函数的箭头前后使用一致的空格
    'arrow-spacing': 'error',
    // 只强制对象解构,不强制数组解构
    'prefer-destructuring': ['error', { object: true, array: false }],
  },
});

关于更多配置项信息,请前往 ESLint 官网查看 ESLint-Configuring

创建 ESLint 过滤规则

在项目根目录添加一个 .eslintignore 文件,内容如下:

dist
node_modules
!.prettierrc.js
components.d.ts
auto-imports.d.ts

集成 Prettier 配置

Prettier 是一款强大的代码格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。

安装依赖

npm i prettier -D

安装插件

Visual Studio Code 编辑器使用 Prettier 配置需要下载插件 Prettier - Code formatter

image.png

创建 Prettier 配置文件

Prettier 支持多种格式的配置文件,比如 .json.yml.yaml.js等。

在项目根目录创建 .prettierrc.js 文件,并填入以下内容:

module.exports = {
  // 一行最多 120 字符
  printWidth: 120,
  // 使用 2 个空格缩进
  tabWidth: 2,
  // 不使用缩进符,而使用空格
  useTabs: false,
  // 行尾需要有分号
  semi: true,
  // 使用单引号
  singleQuote: true,
  // 对象的 key 仅在必要时用引号
  quoteProps: 'as-needed',
  // jsx 不使用单引号,而使用双引号
  jsxSingleQuote: false,
  // 末尾需要有逗号
  trailingComma: 'all',
  // 大括号内的首尾需要空格
  bracketSpacing: true,
  // jsx 标签的反尖括号需要换行
  jsxBracketSameLine: false,
  // 箭头函数,只有一个参数的时候,也需要括号
  arrowParens: 'always',
  // 每个文件格式化的范围是文件的全部内容
  rangeStart: 0,
  rangeEnd: Infinity,
  // 不需要写文件开头的 @prettier
  requirePragma: false,
  // 不需要自动在文件开头插入 @prettier
  insertPragma: false,
  // 使用默认的折行标准
  proseWrap: 'preserve',
  // 根据显示样式决定 html 要不要折行
  htmlWhitespaceSensitivity: 'css',
  // vue 文件中的 script 和 style 内不用缩进
  vueIndentScriptAndStyle: false,
  // 换行符使用 lf
  endOfLine: 'lf',
  // 格式化嵌入的内容
  embeddedLanguageFormatting: 'auto',
  // html, vue, jsx 中每个属性占一行
  singleAttributePerLine: false,
};

关于更多配置项信息,请前往 Prettier 官网查看 Prettier-Options

创建 Prettier 过滤规则

在项目根目录添加一个 .prettierignore 文件,内容如下:

## OS
.DS_Store
.idea
.editorconfig
pnpm-lock.yaml
.npmrc

# Ignored suffix
*.log
*.md
*.svg
*.png
*.ico
*ignore

## Local
.husky

## Built-files
.cache
dist

解决 Prettier 和 ESLint 冲突

本项目中的 ESLint 配置使用了 Airbnb JavaScript 风格指南校验,其规则之一是代码结束后面要加分号,而在 Prettier 配置文件中加了代码结束后面不加分号配置项,从而冲突了。

解决两者冲突问题,需要用到 eslint-plugin-prettier 和 eslint-config-prettier

  • eslint-plugin-prettier 将 Prettier 的规则设置到 ESLint 的规则中

  • eslint-config-prettier 关闭 ESLint 中与 Prettier 中会发生冲突的规则

    最后形成优先级:Prettier 配置规则 > ESLint 配置规则

npm i eslint-plugin-prettier eslint-config-prettier -D

修改 ESLint 配置文件

修改 .eslintrc.js 文件,在 extends 中添加 plugin:prettier/recommended 规则(此规则一定要加在最后)

module.exports = {
  extends: [
    'airbnb-base',
    'eslint:recommended',
    'plugin:vue/vue3-essential',
    'plugin:vue/vue3-recommended',
    'plugin:prettier/recommended'
  ],
}

自动格式化代码

Visual Studio Code 在 settings.json 设置文件中,增加以下代码:

{
  "editor.codeActionsOnSave": {
    "source.fixAll": true,
    "source.fixAll.eslint": true
  }
}

集成 Stylelint 配置

Stylelint 是一个强大、先进的 CSS 代码检查器(linter),可以帮助你规避 CSS 代码中的错误并保持一致的编码风格。

安装依赖

npm i stylelint stylelint-less stylelint-config-prettier stylelint-config-standard stylelint-config-recess-order stylelint-order -D

安装插件

image.png

创建 Stylelint 配置文件

module.exports = {
  root: true,
  defaultSeverity: 'error',
  plugins: ['stylelint-order', 'stylelint-less'],
  extends: [
    'stylelint-config-standard', // the standard shareable config for Stylelint
    'stylelint-config-html/html', // the shareable html config for Stylelint.
    'stylelint-config-html/vue', // the shareable vue config for Stylelint.
    'stylelint-config-recess-order', // use the clean order for properties
    'stylelint-config-prettier', // turn off any rules that conflict with Prettier
  ],
  rules: {
    // 禁止在覆盖高特异性选择器之后出现低特异性选择器
    'no-descending-specificity': null,
    // 禁止空源码
    'no-empty-source': null,
    // 禁止字体族中缺少泛型族关键字
    'font-family-no-missing-generic-family-keyword': null,
    // 禁止未知的@规则
    'at-rule-no-unknown': [
      true,
      {
        ignoreAtRules: [
          'tailwind',
          'apply',
          'variants',
          'responsive',
          'screen',
          'function',
          'if',
          'each',
          'include',
          'mixin',
        ],
      },
    ],
    // 不允许未知函数
    'function-no-unknown': null,
    // 不允许未知单位
    'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],
    // 不允许选择器使用供应商前缀
    'selector-no-vendor-prefix': null,
    // 指定关键帧名称的模式
    'keyframes-name-pattern': null,
    // 指定类选择器的模式
    'selector-class-pattern': null,
    // 不允许值使用供应商前缀
    'value-no-vendor-prefix': null,
    // 要求或禁止在规则之前的空行
    'rule-empty-line-before': ['always', { ignore: ['after-comment', 'first-nested'] }],
    // 指定字符串使用单引号
    'string-quotes': 'single',
    // 指定@规则名的大小写
    'at-rule-name-case': 'lower',
    // 指定缩进
    indentation: [2, { severity: 'warning' }],
  },
  ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
};

创建 Stylelint 过滤规则

# .stylelintignore
# 旧的不需打包的样式库
*.min.css

# 其他类型文件
*.js
*.jpg
*.woff

# 测试和打包目录
/test/
/dist/*
/public/*
public/*
/node_modules/

启用 Vue 文件支持

Stylelint v14 版本默认不支持 vue 文件中的 style 代码自动检测,详情查看官方迁移指南

安装依赖

npm i stylelint-config-html postcss-html postcss-less -D

修改 Stylelint 配置文件

修改 .stylelintrc.js 文件,添加如下配置:

module.exports = {
  overrides: [
    {
      files: ['*.vue', '**/*.vue', '*.html', '**/*.html'],
      customSyntax: 'postcss-html',
      rules: {
        // 禁止未知的伪类选择器
        'selector-pseudo-class-no-unknown': [true, { ignorePseudoClasses: ['deep', 'global'] }],
        // 禁止未知的伪元素选择器
        'selector-pseudo-element-no-unknown': [true, { ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'] }],
      },
    },
    {
      files: ['*.less', '**/*.less'],
      customSyntax: 'postcss-less',
      rules: {
        'less/color-no-invalid-hex': true,
        'less/no-duplicate-variables': true,
      },
    },
  ],
};

修改 Visual Studio Code 工作区配置

Visual Studio Code 在 settings.json 设置文件中,增加以下代码:

{
  "stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass", "html"]
}

集成 husky 和 lint-staged

在项目中已集成 ESLint 和 Prettier,在编码时,这些工具可以对代码进行实时校验,在一定程度上能有效规范所写代码,但有些人可能觉得这些限制很麻烦,从而选择视“提示”而不见,依旧按自己编程风格来写代码,或者干脆禁用掉这些工具,开发完成就直接把代码提交到了仓库,日积月累,ESLint 也就形同虚设。

所以,还需要做一些限制,让没通过 ESLint 检测和修复的代码禁止提交,从而保证仓库代码都是符合规范的。

为了解决这个问题,需要用到 Git Hook,在本地执行 git commit 的时候,就对所提交的代码进行 ESLint 检测和修复(即执行 eslint --fix),如果这些代码没通过 ESLint 规则校验,则禁止提交。

实现这一功能,需要借助 husky + lint-staged

配置 husky

注意:本项目使用 husky 6.x 版本,6.x 版本配置方式跟之前版本有较大差异,当发现配置方法不一致时,一切以 husky 官网为准。 使用 husky-init 命令快速在项目初始化 husky 配置:

# 初始化仓库
git init

# 初始化
npx husky-init

# 安装依赖
npm install

husky 包含很多 hook(钩子),常用有:pre-commitcommit-msg

使用 pre-commit 来触发 ESLint 命令,修改 .husky/pre-commit 文件触发命令:

eslint --fix ./src --ext .vue,.js,.ts

pre-commit hook 文件作用是:当执行 git commit -m "xxx" 时,会先对 src 目录下所有的 .vue.js.ts 文件执行 eslint --fix 命令,如果 ESLint 通过,成功 commit,否则终止 commit

但是又存在一个问题:有时候明明只改动了一两个文件,却要对所有的文件执行 `eslint --fix`。

假如这是一个历史项目,在中途配置了 ESLint 规则,那么在提交代码时,也会对其他未修改的“历史”文件都进行检查,可能会造成大量文件出现 ESLint 错误,显然这不是我们想要的结果。

所以只需要用 ESLint 修复此次写的代码,而不去影响其他的代码,此时需要借助 lint-staged 工具。

配置 lint-staged

lint-staged 一般结合 husky 来使用,它可以让 husky 的 hook 触发的命令只作用于 git 暂存区的文件,而不会影响到其他文件。

安装依赖

npm i lint-staged -D

新增配置

在 package.json 里增加 lint-staged 配置项:

{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "prettier --write",
      "eslint --fix"
    ],
    "*.vue": [
      "prettier --write",
      "eslint --fix",
      "stylelint --fix"
    ],
    "*.{html,vue,vss,sass,less}": [
      "prettier --write",
      "stylelint --fix"
    ],
    "package.json": [
      "prettier --write"
    ],
    "*.md": [
      "prettier --write"
    ]
  },
}

修改触发命令

npx lint-staged

image.png

经过以上配置之后,就可以在每次提交之前对所有代码进行格式化,保证线上代码的规范性。

提交规范

多人协作项目中,在提交代码环节,也存在一种情况:不能保证每个人对提交信息的准确描述,因此会出现提交信息紊乱、风格不一致的情况。

如果 git commit 的描述信息精准,在后期维护和 Bug 处理时会变得有据可查,项目开发周期内还可以根据规范的提交信息快速生成开发日志,从而方便我们追踪项目和把控进度。

image.png

集成 commitlint 验证规范提交

在“代码规范”章节中提到,尽管制定了规范,但在多人协作的项目中,总有些人依旧我行我素。

因此提交代码这个环节,也增加一个限制:只让符合 Angular 规范的 commit message 通过

此功能需借助 @commitlint/config-conventional@commitlint/cli 工具来实现。

安装

npm i @commitlint/cli @commitlint/config-conventional -D

配置

在项目根目录创建 commitlint.config.js 文件,并填入以下内容:

module.exports = {
  extends: ['@commitlint/config-conventional']
}

使用 husky 命令在 .husky 目录下创建 commit-msg 文件,并在此执行验证命令:

npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"

image.png

自动部署

本章节将介绍如何使用 CI(Continuous Integration 持续集成)服务来完成项目部署工作。

本项目使用 GitHub Actions 来完成这一操作。 GitHub Actions 入门教程

创建 GitHub 仓库

因为 GitHub Actions 只对 GitHub 仓库有效,所以创建 GitHub 仓库来托管项目代码

image.png

  • master 分支存储项目源代码
  • gh-pages 分支存储打包后的静态文件

创建 GitHub Token

创建一个有 repo 和 workflow 权限的 GitHub Token

image.png

image.png

添加 Actions secret

将上述创建的 Token 添加到 GitHub 仓库中的 Secrets 里,并将这个新增的 secret 命名为 VITE_VUE_DEPLOY 。

步骤:仓库 -> Settings -> Secrets -> Actions -> New repository secret

image.png

image.png

image.png

修改 package.json

打开 package.json 文件,新增 homepage 字段,表示该应用发布后的根目录(参见官方文档)。

创建 Actions 配置文件

(1)在项目根目录下创建 .github 目录。

(2)在 .github 目录下创建 workflows 目录。

(3)在 workflows 目录下创建 deploy.yml 文件。

image.png

name: Vite Vue Deploy

on:
  push:
    # master 分支有 push 时触发
    branches: [master]

jobs:
  deploy:
    # 指定虚拟机环境
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [14.x, 16.x]

    steps:
      - name: Checkout
        # 拉取 GitHub 仓库代码
        uses: actions/checkout@v3

      - name: Use Node.js ${{ matrix.node-version }}
        # 设定 Node.js 环境
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install
        # 安装依赖
        run: npm install

      - name: Build
        # 打包
        run: npm run build

      - name: Deploy
        uses: JamesIves/github-pages-deploy-action@v4
        with:
          # 部署打包目录
          folder: dist
          # 密钥名
          token: ${{ secrets.VITE_VUE_DEPLOY }}
          # 分支
          branch: gh-pages

参考出处

本文仅作为自己的学习笔记,文章出处以及源码看原作者:威廉王子

从 0 搭建 Vite 3 + Vue 3 前端工程化项目