「干货」全场景vite2 + vue3 + ts搭建脚手架实践

1,946 阅读14分钟

前言

OK,这里是分享与爱的大鹅鹅鹅,今天我们瞬间移动,来到脚手架实践栏目。这里已经准备了思维导图,让我们一起走近进入vite2 + vue3 + ts搭建脚手架的奇妙之旅,来了解vite和vue3带来了哪些优越的改变,为编程提效做出了哪些努力?

image.png

为什么要用vite?

借用作者的原话:

Vite,一个基于浏览器原生 ES imports 的开发服务器。利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。同时不仅有 Vue 文件支持,还搞定了热更新,而且热更新的速度不会随着模块增多而变慢。针对生产环境则可以把同一份代码用 rollup 打包。虽然现在还比较粗糙,但这个方向我觉得是有潜力的,做得好可以彻底解决改一行代码等半天热更新的问题。

需要注意

  • Vite 主要对应的场景是开发模式,原理是拦截浏览器发出的 ES imports 请求并做相应处理。(生产模式是用 rollup 打包)
  • Vite 在开发模式下不需要打包,只需要编译浏览器发出的 HTTP 请求对应的文件即可,所以热更新速度很快。
  • CommonJS 和 UMD 兼容性: 开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。

生产模式下的打包不是 Vite 自身提供的,因此生产模式下如果你想要用 Webpack 打包也依然是可以的。从这个角度来说,Vite 可能更像是替代了 webpack-dev-server 的一个东西。

为什么要用Vue3?

  1. 性能优化

    1. 数据劫持方面的改变,由 ES5Object.defineProperty() 进行数据劫持,改变为ES6Proxy进行数据代理。(原因:由于proxy可以动态判断该数据是否为深层嵌套数据,而object.defineProperty()为遍历深层嵌套数据,两者在数据劫持,数据变更判断上有时间的巨大差异。)
    2. 打包策略,vue3支持了tree-shaking,排除任何未实际使用的代码,打包体积减小。降低了文件加载时间。
    3. diff优化,vue3对静态节点进行提升,所有的静态节点在进行对比时不会被遍历。减少了进行节点对比所需的时间。
  2. 新增Composition API(组合式API)

    1. 解决了Vue2.x版本的options API不能解决的复用和可读性差的缺陷。

    2. 新增了一个setup()生命周期函数,会在组件创建之前就执行。用setup生命周期代替beforeCreatecreated

        const {ref,reactive,onMountend,toRefs} = Vue
        setup(props){
           // 声明简单数据类型和复杂数据类型
           // 只能通过.value的形式获取数据
           const refValue = ref(null);
           // 只能声明复杂数据类型
           // 声明的数据类型不能解构
           // 声明的数据类型是proxy对象
           const reactiveValue = reactive({state:0});
           // 生命周期的使用(on加生命周期名字)
           onMountend(()=>{
              // 在相应的生命周期内执行相应操作
           })
           // 该类型的数据不能解构,解构就会失去响应式特点
           console.log(props);
           // 该方法将proxy对象转化成ref变量
           toRefs(props);
        } 
      
  3. v-model绑定提升

    支持绑定多个v-model进行双向绑定。

  4. TypeScript支持

    Vue3完全由TS进行重写,对类型判断的定义和使用有很强的表现。

  5. 生命周期优化

    Vue2 ~~~~~~~~~~~ vue3
    beforeCreate  -> setup()(新增)
    created       -> setup()(新增)
    beforeMount   -> onBeforeMount
    mounted       -> onMounted
    beforeUpdate  -> onBeforeUpdate
    updated       -> onUpdated
    beforeDestroy -> onBeforeUnmount(改名)
    destroyed     -> onUnmounted(改名)
    activated     -> onActivated
    deactivated   -> onDeactivated
    

前置准备

  1. 浏览器Chrome(最新版本最佳)

    浏览器插件:Vue.js devtools(调试插件)

  2. 编辑器VsCode

    Vscode 插件:Vue Language Features (Volar)(提供语法高亮提示等功能),Vue 3 Snippets(vue3 快捷输入)

  3. 环境 Nodejs

构建脚手架

编写本文时,版本说明:

  • vite: "2.6.4";
  • vue: "3.2.16";

选对目录后,命令行输入,并回车:

yarn create vite

进入下载构建项目流程,根据图形化指令选择操作。

image.png

vite.config.ts配置

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

function resolve(dir) {
  return path.join(__dirname, dir)
}

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve('src'),
    },
  },
  //启动服务配置
  server: {
    port: 8000,
    open: true,
    https: false,
    cors: false // 允许跨域
    proxy: {
      '/api': {
        target: 'http://172.0.0.1:8089',
        changeOrigin: true,
        ws: true,
      },
    },
  },
  // 生产环境打包配置
  //去除 console debugger
  build: {
    target: 'es2015',
    minify: 'terser', // 是否进行压缩,boolean | 'terser' | 'esbuild',默认使用terser
    manifest: false, // 是否产出maifest.json
    sourcemap: false, // 是否产出soucemap.json
    outDir: 'build', // 产出目录
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
      },
    },
  },
})

代码风格约束

Eslint 支持

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

这三个依赖的作用分别是:

  • eslint: ESLint的核心代码;
  • eslint-plugin-vue:ESLint关于检测vue代码规范的插件;
  • @typescript-eslint/parser:ESLint的解析器,用于解析typescript,从而检查和规范Typescript代码;
  • @typescript-eslint/eslint-plugin:这是一个ESLint插件,包含了各类定义好的检测Typescript代码的规范;

注意:

@typescript-eslint/parser@typescript-eslint/eslint-plugin使用相同的版本号很重要;

配置eslint 校验规则

项目下新建 .eslintrc.js

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
    es2021: true,
  },
  parser: "vue-eslint-parser",
  extends: [
    "eslint:recommended",
    "plugin:vue/vue3-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    // eslint-config-prettier 的缩写
    "prettier",
  ],
  parserOptions: {
    ecmaVersion: 12,
    parser: "@typescript-eslint/parser",
    sourceType: "module",
    ecmaFeatures: {
      jsx: true,
    },
  },
  // eslint-plugin-vue @typescript-eslint/eslint-plugin eslint-plugin-prettier的缩写
  plugins: ["vue", "@typescript-eslint", "prettier"],
  rules: {
    "@typescript-eslint/ban-ts-ignore": "off",
    "@typescript-eslint/no-unused-vars": "off",
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/no-var-requires": "off",
    "@typescript-eslint/no-empty-function": "off",
    "@typescript-eslint/no-use-before-define": "off",
    "@typescript-eslint/ban-ts-comment": "off",
    "@typescript-eslint/ban-types": "off",
    "@typescript-eslint/no-non-null-assertion": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off",
    "no-var": "error",
    "prettier/prettier": "error",
    // 禁止出现console
    "no-console": "warn",
    // 禁用debugger
    "no-debugger": "warn",
    // 禁止出现重复的 case 标签
    "no-duplicate-case": "warn",
    // 禁止出现空语句块
    "no-empty": "warn",
    // 禁止不必要的括号
    "no-extra-parens": "off",
    // 禁止对 function 声明重新赋值
    "no-func-assign": "warn",
    // 禁止在 return、throw、continuebreak 语句之后出现不可达代码
    "no-unreachable": "warn",
    // 强制所有控制语句使用一致的括号风格
    curly: "warn",
    // 要求 switch 语句中有 default 分支
    "default-case": "warn",
    // 强制尽可能地使用点号
    "dot-notation": "warn",
    // 要求使用 === 和 !==
    eqeqeq: "warn",
    // 禁止 if 语句中 return 语句之后有 else"no-else-return": "warn",
    // 禁止出现空函数
    "no-empty-function": "warn",
    // 禁用不必要的嵌套块
    "no-lone-blocks": "warn",
    // 禁止使用多个空格
    "no-multi-spaces": "warn",
    // 禁止多次声明同一变量
    "no-redeclare": "warn",
    // 禁止在 return 语句中使用赋值语句
    "no-return-assign": "warn",
    // 禁用不必要的 return await
    "no-return-await": "warn",
    // 禁止自我赋值
    "no-self-assign": "warn",
    // 禁止自身比较
    "no-self-compare": "warn",
    // 禁止不必要的 catch 子句
    "no-useless-catch": "warn",
    // 禁止多余的 return 语句
    "no-useless-return": "warn",
    // 禁止变量声明与外层作用域的变量同名
    "no-shadow": "off",
    // 允许delete变量
    "no-delete-var": "off",
    // 强制数组方括号中使用一致的空格
    "array-bracket-spacing": "warn",
    // 强制在代码块中使用一致的大括号风格
    "brace-style": "warn",
    // 强制使用骆驼拼写法命名约定
    camelcase: "warn",
    // 强制使用一致的缩进
    indent: "off",
    // 强制在 JSX 属性中一致地使用双引号或单引号
    // 'jsx-quotes': 'warn',
    // 强制可嵌套的块的最大深度4
    "max-depth": "warn",
    // 强制最大行数 300
    // "max-lines": ["warn", { "max": 1200 }],
    // 强制函数最大代码行数 50
    // 'max-lines-per-function': ['warn', { max: 70 }],
    // 强制函数块最多允许的的语句数量20
    "max-statements": ["warn", 100],
    // 强制回调函数最大嵌套深度
    "max-nested-callbacks": ["warn", 3],
    // 强制函数定义中最多允许的参数数量
    "max-params": ["warn", 3],
    // 强制每一行中所允许的最大语句数量
    "max-statements-per-line": ["warn", { max: 1 }],
    // 要求方法链中每个调用都有一个换行符
    "newline-per-chained-call": ["warn", { ignoreChainWithDepth: 3 }],
    // 禁止 if 作为唯一的语句出现在 else 语句中
    "no-lonely-if": "warn",
    // 禁止空格和 tab 的混合缩进
    "no-mixed-spaces-and-tabs": "warn",
    // 禁止出现多行空行
    "no-multiple-empty-lines": "warn",
    // 禁止出现;
    semi: ["warn", "never"],
    // 强制在块之前使用一致的空格
    "space-before-blocks": "warn",
    // 强制在 function的左括号之前使用一致的空格
    // 'space-before-function-paren': ['warn', 'never'],
    // 强制在圆括号内使用一致的空格
    "space-in-parens": "warn",
    // 要求操作符周围有空格
    "space-infix-ops": "warn",
    // 强制在一元操作符前后使用一致的空格
    "space-unary-ops": "warn",
    // 强制在注释中 // 或 /* 使用一致的空格
    // "spaced-comment": "warn",
    // 强制在 switch 的冒号左右有空格
    "switch-colon-spacing": "warn",
    // 强制箭头函数的箭头前后使用一致的空格
    "arrow-spacing": "warn",
    "no-var": "warn",
    "prefer-const": "warn",
    "prefer-rest-params": "warn",
    "no-useless-escape": "warn",
    "no-irregular-whitespace": "warn",
    "no-prototype-builtins": "warn",
    "no-fallthrough": "warn",
    "no-extra-boolean-cast": "warn",
    "no-case-declarations": "warn",
    "no-async-promise-executor": "warn",
  },
  globals: {
    defineProps: "readonly",
    defineEmits: "readonly",
    defineExpose: "readonly",
    withDefaults: "readonly",
  },
};

项目下新建 .eslintignore

# eslint 忽略检查 (根据项目需要自行添加)
**/*.js
"build"
"dist"
"public"
"node_modules"

检查修复

虽然,现在编辑器已经给出错误提示和修复方案,但需要我们一个一个去点击修复,还是挺麻烦的。很简单,我们只需设置编辑器保存文件时自动执行 eslint --fix 命令进行代码风格修复。

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

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

prettier 支持

yarn add prettier eslint-plugin-prettier eslint-config-prettier -D
  • prettier:prettier插件的核心代码
  • eslint-config-prettier:解决ESLint中的样式规范和prettier中样式规范的冲突,以prettier的样式规范为准,使ESLint中的样式规范自动失效;
  • eslint-plugin-prettier:将prettier作为ESLint规范来使用

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

关于prettier ESLint的配置,已经在前面部分的配置****eslint**** 校验规则,配置 .eslintrc.js内容时已配置完成。

项目下新建 .prettierrc.js

配置 prettier 格式化规则:

{
    "trailingComma": "es5",
    "tabWidth": 2,
    "jsxSingleQuote": true,
    "jsxBracketSameLine": true,
    "printWidth": 100,
    "singleQuote": true, 
    "semi": false,
    "overrides": [
        {
            "files": "*.json",
            "options": {
                "printWidth": 200
            }
        }
    ],
    "arrowParens": "always"
}

项目下新建 .prettierignore

# 忽略格式化文件 (根据项目需要自行添加)
node_modules
dist

自动空格缩放保存不一致解决

vscode编辑器开启自动保存时,会自动设置tabsize,检查.prettierrc.js下的"tabWidth": 2,是否和vscode 设置的一致,不然可能会会有保存缩进空格不同步的情况。

MacOS使用Command + Sheft + P,windows使用Ctrl + Sheft + P快捷键---> 搜索“settings.json”。保持prettier的缩进空格同编辑器设置的一致,设置如下:

"editor.tabSize": 2,
//  vscode默认启用了根据文件类型自动设置tabsize的选项
"editor.detectIndentation": false,

package.json 配置

{

  "script": {
    "lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
    "prettier": "prettier --write ."
  }
}

上面配置完成后,可以运行以下命令测试下代码检查个格式化效果:

// eslint 检查
yarn lint
// prettier 自动格式化
yarn prettier

配置 husky + lint-staged

husky 是一个为 git 客户端增加 hook 的工具。我们可以在 pre-commit 中实现一些比如 lint 检查单元测试代码美化等操作。

lint-staged它是对个人要提交的代码的一个规范和约束。

前置条件:

确保在此之前安装并配置所有代码质量工具,如 Prettier 和 ESlint

使用husky + lint-staged助力团队编码规范,husky + lint-staged安装推荐使用 mrm,它将根据 package.json 依赖项中的代码质量工具来安装和配置 huskylint-staged

安装 mrm

npm i mrm -D --registry=https://registry.npm.taobao.org

安装 lint-staged

mrm 安装 lint-staged自动husky 一起安装下来 。

npx mrm lint-staged

安装成功后会发现 package.json 中多了一下几个配置:

image.png

因为我们要结合 prettier 代码格式化,所有修改一下配置:

  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
    }
  },
  "lint-staged": {
    "*.{ts,tsx,vue,js,jsx}": [
      "yarn lint",
      "prettier --write",
      "git add"
    ]
  }

Git Commit Message 提交规范配置

针对 git commit log 提交规范的问题,需要统一规范,如果我们提交的不符合指向的规范, 直接拒绝提交。

 yarn add @commitlint/cli commitizen commitlint-config-cz cz-customizable -D

package.json中配置:

  "config": {
    "commitizen": {
      "path": "node_modules/cz-customizable"
    }
  },
  
  // 结合 Husky
  "husky": {
    "hooks": {
      ...,
      "commit-msg": "commitlint -e $GIT_PARAMS"
    }
  },

配置规则

根目录.cz-config.js 中写入自定义的配置:

'use strict'

module.exports = {
  types: [
    {
      value: '💪 WIP',
      name: '进度: 工作进度',
    },
    {
      value: '✨ feat',
      name: '功能: 新功能',
    },
    {
      value: '🐞 fix',
      name: '修复: 修复bug',
    },
    {
      value: '🛠 refactor',
      name: '重构: 代码重构',
    },
    {
      value: '📚  docs',
      name: '文档: 只有文档变更',
    },
    {
      value: '🏁  test',
      name: '测试: 添加一个测试',
    },
    {
      value: '🗯 chore',
      name: '工具: 没有修改源代码,只是变更构建流程或辅助工具。',
    },
    {
      value: '💅 style',
      name: '样式: 空格,分号等格式修复。',
    },
    {
      value: '⏪ revert',
      name: '回滚: 代码回退。',
    },
    {
      value: '🏎  perf',
      name: '性能: 提升性能。',
    },
    {
      value: '🏗 build',
      name: '构建: 变更项目构建或外部依赖(例如scopes: webpack、gulp、npm等)',
    },
    {
      value: '🕹 ci',
      name:
        '集成: 更改持续集成软件的配置文件和package中的scripts命令,例如scopes: Travis, Circle等',
    },
  ],
  scopes: [],
  messages: {
    type: '选择一种你的提交类型:',
    scope: '选择一个scope (可选):',
    // used if allowCustomScopes is true
    customScope: 'Denote the SCOPE of this change:',
    subject: '短说明:\n',
    body: '长说明,使用"|"换行(可选):\n',
    breaking: '非兼容性说明 (可选):\n',
    footer: '关联关闭的issue,例如:#31, #34(可选):\n',
    confirmCommit: '确定提交说明?',
  },
  footerPrefix: 'TAPD: ',
  allowCustomScopes: true,
  allowBreakingChanges: ['特性', '修复'],
  skipQuestions: ['scope', 'body', 'footer'],
}

根目录 .commitlintrc.js 中写入:

module.exports = {
  extends: [
    'cz'
  ],
  rules: {
  }
};

效果如下:

image.png

工具配置

css预加载配置

Vite 提供了对 .scss, .sass, .less, .styl.stylus 文件的内置支持。但必须安装相应的预处理器依赖。

yarn add less less-loader sass sass-loader dart-sass -D

router配置

路由一定得安装4.0.0以上版本,最好直接安装当前最新版本。

安装:

yarn add vue-router@4 -S       

配置:

新版本的路由配置和之前非常相似,只有些许不同。新版本路由的API全部采用了函数式引入的方式,具备ts的类型提示。在router/index.ts文件下写入:

import {
  createRouter,
  RouteRecordRaw,
  createWebHistory,
  // createWebHashHistory
} from 'vue-router'

const HelloWorld = () => import('@/views/HelloWorld.vue')
const ErrorPage = () => import('@/views/ErrorPage.vue')

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'HelloWorld',
    component: HelloWorld,
  },
  {
    path: '/:pathMatch(.*)*', // 注意此处 404页面匹配规则和以前不相同,得采用这种配置方式才行
    name: '404',
    component: ErrorPage,
  },
]

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

export default router

增加路由守卫:

import { RouteLocationNormalized, NavigationGuardNext,} from 'vue-router'

// 路由守卫和之前的实现方式一致 此处只是做了一个demo仅供演示
router.beforeEach(
  (
    to: RouteLocationNormalized,
    from: RouteLocationNormalized,
    next: NavigationGuardNext
  ) => {
  // 获取userToken,根据业务场景可由localStorage也可由cookie中获取
  const user = localStorage.getItem("user");
  // 路由守卫判断
  if (to.meta.type === "login" && user) {
    next({ name: "home" });
    return;
  }

  if (to.meta.type === "home" && !user) {
    next({ name: "login" });
    return;
  }

  next();
});

状态库配置

以下两者选择:

  • vuex 4.0.0及以上版本;
  • pinia(推荐);

UI库选择

常见的工具库配置

 yarn add lodash-es normalize.css numeral -S 

环境变量配置

vite 提供了两种模式:具有开发服务器的开发模式(development)和生产模式(production),支持.env 文件配置。以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。具体环境变量配置,解释说明见环境变量和模式

项目根目录新建:.env.development :

NODE_ENV=development
VITE_APP_WEB_URL= 'YOUR WEB URL'

项目根目录新建:.env.production :

NODE_ENV=production
VITE_APP_WEB_URL= 'YOUR WEB URL'

组件中使用:

console.log(import.meta.env.VITE_APP_WEB_URL)

配置 package.json:

打包区分开发环境和生产环境

"build:dev": "vue-tsc --noEmit && vite build --mode development",
"build:pro": "vue-tsc --noEmit && vite build --mode production",

typeScript 的智能提示:

默认情况下,Vite 在 vite/client.d.ts 中为 import.meta.env 提供了类型定义。随着在 .env[mode] 文件中自定义了越来越多的环境变量,你可能想要在代码中获取这些以 VITE_ 为前缀的用户自定义环境变量的 TypeScript 智能提示。

要想做到这一点,你可以在 src 目录下创建一个 env.d.ts 文件,接着按下面这样增加 ImportMetaEnv 的定义:

/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string
  // 更多环境变量...
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

VsCode自定义代码片段

一些常用的业务代码,我们可以将其重构,利用vscode,构建自定义代码片段,在项目根目录,创建.vscode文件夹、project.code-snippets文件,目录如.vscode/project.code-snippets

在project.code-snippets文件内编写自定义代码片段。示例:

{
  "components": {
    "scope": "javascript, typescript, vue",
    "prefix": "<component-setup>",
    "body": [
      "<template>",
      "",
      "</template>",
      "",
      "<script setup lang="ts">",
      "import { inject, onMounted, ref } from "vue";",
      "import { IStrings } from "@/locale/locale";",
      "",
      "interface IProps { name: string; }",
      "const strings: IStrings | undefined = inject("strings");",
      "const props = defineProps<IProps>();",
      "onMounted(() => {});",
      "</script>",
      "<style scoped>",
      "</style>"
    ],
    "description": "Default Component Code of Vue3 + Typescript + Setup"
  },
}

使用的话,在.vue文件中,写入<component-setup>,编辑器会自动联想,自定义代码片段。然后进行选择确定,自定义代码片段就写入.vue文件中。

结语

以上就是本篇的全部内容,如果你觉得此文对你有一点点帮助,不妨点个赞,一键三连,鼓励一下大鹅鹅鹅,O(∩_∩)O哈哈~

参考