Skip to content

环境信息

  • vite
  • vue3(setup script)
  • typescript -标配
  • vue-router -vue3生态
  • eslint -代码(js、vue)格式化
  • stylelint -代码(css)格式化
  • tailwind-css -原子css样式库
  • element-plus -vue3组件库

脚手架生成初始化项目

vue3官方脚手架生成项目 👇 vue3官方文档

bash
npm init vue@latest

👇 create-vue官方指令

bash
npm create vue@latest

🤔 为什么有不同的命令来初始化项目

2个指令可以看出区别是 init/create 我们运行

bash
npm create --help

👇 得到

bash
Usage:
npm init <package-spec> (same as `npx <package-spec>)
npm init <@scope> (same as `npx <@scope>/create`)

Options:
[-y|--yes] [-f|--force] [--scope <@scope>]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces] [--no-workspaces-update] [--include-workspace-root]

aliases: create, innit

Run "npm help init" for more info

👆 看出 createinit 的别名,并且指引详情文档也是打开 init

因此 这里运行 npm init vue@latest === npm create vue@latest


👇 npm init 官方文档

bash
npm init foo -> npm exec create-foo
npm init @usr/foo -> npm exec @usr/create-foo
npm init @usr -> npm exec @usr/create
npm init @[email protected] -> npm exec @usr/[email protected]
npm init @usr/[email protected] -> npm exec @usr/[email protected]

因此 npm init vue@latest 等同于👇

bash
npx create-vue@latest

指令 create-vue官方指令将会得到👇命令行问答

bash
 Project name: <your-project-name>
 Add TypeScript? No / Yes
 Add JSX Support? No / Yes
 Add Vue Router for Single Page Application development? No / Yes
 Add Pinia for state management? No / Yes
 Add Vitest for Unit testing? No / Yes
 Add Cypress for both Unit and End-to-End testing? No / Yes
 Add ESLint for code quality? No / Yes
 Add Prettier for code formatting? No / Yes

Scaffolding project in ./<your-project-name>...
Done.

👇 另外在vite官方文档 创建vue项目指令

bash
npm create vite <your-project-name> --template vue

同理等同于 npx create-vite - create-vite github

脚手架项目已有配置

删除冗余代码后的已有配置

txt
.vscode -vscode编辑器局部配置
public -不参与编译的目录资源(图片、css变量等)
src -项目目录和vue2基本一致
.eslint -eslint配置
.gitignore -git忽略配置
env.d.ts -ts配置(https://cn.vitejs.dev/guide/features.html#typescript)
index.html -模板html
package.json
tsconfig.config.json -ts配置
tsconfig.json -ts配置
vite.config.ts -vite配置

vite配置

👇 vite.config.ts文件

ts
import { fileURLToPath, URL } from 'node:url' // 用于配置目录别名

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

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

👆 只配置了

  • 一个vue3plugin
    • @vitejs/plugin-vue
    • Official plugin that provides Vue SFC support in Vite.
    • 等同于vue2的loader(转译.vue文件)和plugin
  • 别名 alias
    • node:url官方文档
    • fileURLToPath(new URL('./src', import.meta.url))
    • 等同于以前在webpack里使用的自己封装的path.resolve函数
    • path.resolve(__dirname, 'src')
    • TODO:区别?

ts配置

👇 tsconfig.json

json
{
  "extends": "@vue/tsconfig/tsconfig.web.json",
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },

  "references": [
    {
      "path": "./tsconfig.config.json"
    }
  ]
}

TODO: ts配置将单独讲解

eslint配置

👇 .eslint.cjs

js
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')

module.exports = {
  root: true,
  'extends': [
    'plugin:vue/vue3-essential',
    'eslint:recommended',
    '@vue/eslint-config-typescript'
  ],
  parserOptions: {
    ecmaVersion: 'latest'
  }
}

👆 使用的是 CommonJscjs文件

配置了3个预设

  • plugin:vue/vue3-essential
    • eslint 会把这种写法解析为对应的依赖包名不需要引入
    • plugin:vue -> eslint-plugin-vue
    • essential 基本的
  • eslint:recommended 对应依赖包 eslint
  • @vue/eslint-config-typescript
    • 没有采用特殊写法,因此就是对应的依赖包
    • 基于@typescript-eslint/eslint-plugin扩展的vue的ts代码格式校验

配合@vue/eslint-config-typescript 代码格式化需要安装 @rushstack/eslint-patch/modern-module-resolution TODO:

模板html

html
<head>
  <meta charset="UTF-8">
  <link rel="icon" href="/favicon.ico">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>innermanage demo</title>
</head>

👇 使用的是esm

html
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
</body>

router配置

👇 router/index.ts

ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL), // TODO:
  routes: [
    {
      path: '/',
      name: 'home',
      redirect:'home'
    },
    {
      path: '/home',
      name: 'home',
      component: () => import('../views/home.vue')
    }
  ]
})
export default router

👇 main.ts

ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

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

vite环境配置

vite 默认设置环境变量development productment

环境变量目录修改

Vite 在一个特殊的 import.meta.env 对象上暴露环境变量

环境变量和模式-vite官方文档

环境变量文件目录默认从项目根路径读取 通过👇 配置修改 envdir配置-vite官方文档

新建目录 build/env 存放环境变量

👇 build/env/.env.development

bash
# import.meta.env.VITE_SOME_KEY
VITE_SOME_KEY=dev-VITE_SOME_KEY

👇 build/env/.env.production

bash
# import.meta.env.VITE_SOME_KEY
VITE_SOME_KEY=prod-VITE_SOME_KEY

👇 修改vite.config.ts

ts
export default defineConfig({
  plugins: [ ... ],
  resolve: { ... },
  // 修改环境变量配置目录(默认是根目录)
  envDir: fileURLToPath(new URL('./build/env'))
})

vite打包配置根据环境区分文件

vite只给环境变量默认设置 dotenv 目录读取配置 其他打包配置并没有区分环境 因此我们使用函数手动分开使用对应环境的配置文件

👇 build/index.ts

ts
import { devConfig } from './config/dev.config';
import { prodConfig } from './config/prod.config';

// 把配置包装成函数,在运行打包指令时,懒加载函数,不用预编译所有配置文件
export const envResolver = new Map<string,()=>void>([
  ['development', ()=>devConfig], // key为vite打包指令的mode环境变量,默认有development、production(https://cn.vitejs.dev/config/shared-options.html#mode)
  ['production', ()=>prodConfig]
])

👆 抛出一个map对象

  • key为打包指令的环境变量
  • value为一个函数直接抛出对应环境配置文件的内容,实现懒加载

👇 build/config/*.config.ts

ts
// build/config/dev.config.ts
export const devConfig = {}

// build/config/prod.config.ts
export const prodConfig = {}

👆 直接抛出一个对象,会由index.ts包装成函数实现懒加载

👇 vite.config.ts

ts
import { envResolver } from "./build/index";

// 不同打包环境 通用配置
const commonConfig = {
  plugins: [ ... ],
  resolve: { ... },
  envDir: ...
}

// 打包配置函数,改用函数可以获取到打包环境变量mode
// TODO: 怎么定义这个配置函数的参数类型推导
const modeConfigFn = ({ mode }:{mode:string}) => {
  const fn = envResolver.get(mode) || (()=>{})
  return Object.assign(
    commonConfig,
    fn()
  )
}
export default defineConfig(modeConfigFn)

👆 引用import { envResolver } from "./build/index"会有ts报错,找不到对应模块的类型声明 是因为项目的ts配置了编译目录 👇 在 tsconfig.config.json 中添加 "build/**/*.ts

"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*", "build/**/*.ts"],

vscode保存自动格式化

原脚手架已经生成了 .vscode目录 👇 .vscode/extensions.json 插件配置了

json
{
  "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

新增一个配置文件 👇 .vscode/settings.json

json
{
  "editor.formatOnSave": true,
  "editor.tabSize": 2,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
  },
  "eslint.alwaysShowStatus": true,
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "html",
    "vue"
  ],
}

配置官方自动导入插件-省略手写import资源

unplugin-auto-import github

Auto import APIs on-demand for Vite, Webpack, Rollup and esbuild. With TypeScript support. Powered by unplugin-github

利用自定义vite编译插件功能

  • 实现编译时自动按需注入import语句
  • 并按配置项,生成ts规则(用于ts类型校验认为不import是合法的)
  • 并按配置项,生成eslint规则(用于ts格式校验认为不import是合法的)

安装依赖并配置vite

安装 pnpm add unplugin-auto-import -D 配置 👇 vite.config.ts

ts
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  plugins: [
    AutoImport({ /* options */ }),
  ],
})

此时项目没有任何变化 当运行 pnpm dev 时,根目录下自动生成了 auto-imports.d.ts 文件

ts
// Generated by 'unplugin-auto-import'
export {}
declare global {
}

👆 可以看出声明了全局对象,空的

配置自动注入vue的api引入语句

当我们配置插件内容为vue时 AutoImport({ imports: ['vue'] }) 👇 编写插件配置项有ts自动补全提示,imports的值是一个字符串数组 可以点进去查看具体的枚举

再次运行pnpm dev 自动生成的 auto-imports.d.ts 文件发生了改变

ts
// Generated by 'unplugin-auto-import'
export {}
declare global {
  const EffectScope: typeof import('vue')['EffectScope']
  const computed: typeof import('vue')['computed']
  const createApp: typeof import('vue')['createApp']
  const customRef: typeof import('vue')['customRef']
  const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
  const defineComponent: typeof import('vue')['defineComponent']
  const effectScope: typeof import('vue')['effectScope']
  const getCurrentInstance: typeof import('vue')['getCurrentInstance']
  const getCurrentScope: typeof import('vue')['getCurrentScope']
  const h: typeof import('vue')['h']
  const inject: typeof import('vue')['inject']
  const isProxy: typeof import('vue')['isProxy']
  const isReactive: typeof import('vue')['isReactive']
  const isReadonly: typeof import('vue')['isReadonly']
  const isRef: typeof import('vue')['isRef']
  const markRaw: typeof import('vue')['markRaw']
  const nextTick: typeof import('vue')['nextTick']
  const onActivated: typeof import('vue')['onActivated']
  const onBeforeMount: typeof import('vue')['onBeforeMount']
  const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
  const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
  const onDeactivated: typeof import('vue')['onDeactivated']
  const onErrorCaptured: typeof import('vue')['onErrorCaptured']
  const onMounted: typeof import('vue')['onMounted']
  const onRenderTracked: typeof import('vue')['onRenderTracked']
  const onRenderTriggered: typeof import('vue')['onRenderTriggered']
  const onScopeDispose: typeof import('vue')['onScopeDispose']
  const onServerPrefetch: typeof import('vue')['onServerPrefetch']
  const onUnmounted: typeof import('vue')['onUnmounted']
  const onUpdated: typeof import('vue')['onUpdated']
  const provide: typeof import('vue')['provide']
  const reactive: typeof import('vue')['reactive']
  const readonly: typeof import('vue')['readonly']
  const ref: typeof import('vue')['ref']
  const resolveComponent: typeof import('vue')['resolveComponent']
  const resolveDirective: typeof import('vue')['resolveDirective']
  const shallowReactive: typeof import('vue')['shallowReactive']
  const shallowReadonly: typeof import('vue')['shallowReadonly']
  const shallowRef: typeof import('vue')['shallowRef']
  const toRaw: typeof import('vue')['toRaw']
  const toRef: typeof import('vue')['toRef']
  const toRefs: typeof import('vue')['toRefs']
  const triggerRef: typeof import('vue')['triggerRef']
  const unref: typeof import('vue')['unref']
  const useAttrs: typeof import('vue')['useAttrs']
  const useCssModule: typeof import('vue')['useCssModule']
  const useCssVars: typeof import('vue')['useCssVars']
  const useSlots: typeof import('vue')['useSlots']
  const watch: typeof import('vue')['watch']
  const watchEffect: typeof import('vue')['watchEffect']
  const watchPostEffect: typeof import('vue')['watchPostEffect']
  const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}

配置eslint

此时查看示例代码 👆 eslint 报错

配置插件内容的eslintrc选项

ts
plugins: [
  AutoImport({
    imports: ['vue'],
    eslintrc: {
      enabled: true, // <-- this
    },
  }),
],

因为刚才启动着vite,检测到了配置发生了改变自动reload,触发了插件生成 .eslintrc-auto-import.json 文件

json
{
  "globals": {
    "EffectScope": true,
    "computed": true,
    "createApp": true,
    "customRef": true,
    "defineAsyncComponent": true,
    "defineComponent": true,
    "effectScope": true,
    "getCurrentInstance": true,
    "getCurrentScope": true,
    "h": true,
    "inject": true,
    "isProxy": true,
    "isReactive": true,
    "isReadonly": true,
    "isRef": true,
    "markRaw": true,
    "nextTick": true,
    "onActivated": true,
    "onBeforeMount": true,
    "onBeforeUnmount": true,
    "onBeforeUpdate": true,
    "onDeactivated": true,
    "onErrorCaptured": true,
    "onMounted": true,
    "onRenderTracked": true,
    "onRenderTriggered": true,
    "onScopeDispose": true,
    "onServerPrefetch": true,
    "onUnmounted": true,
    "onUpdated": true,
    "provide": true,
    "reactive": true,
    "readonly": true,
    "ref": true,
    "resolveComponent": true,
    "resolveDirective": true,
    "shallowReactive": true,
    "shallowReadonly": true,
    "shallowRef": true,
    "toRaw": true,
    "toRef": true,
    "toRefs": true,
    "triggerRef": true,
    "unref": true,
    "useAttrs": true,
    "useCssModule": true,
    "useCssVars": true,
    "useSlots": true,
    "watch": true,
    "watchEffect": true,
    "watchPostEffect": true,
    "watchSyncEffect": true
  }
}

👆 也是定义了globals 这个文件需要手动配置到 .eslinrc.cjs 中才能生效

js
module.exports = {
  'extends': [
    './.eslintrc-auto-import.json',
  ],
}

配置ts

👆 此时eslint不报错了,ts依然报错 因为自动生成的auto-imports.d.ts 文件,还是需要手动配置

注意,生成.d.ts文件不是imports:["vue"]配置的作用,而是默认配置dts:'./auto-imports.d.ts'

tsconfig.json中配置上"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "auto-imports.d.ts"],

调整自动生成文件的目录

👆 至此,已经开发阶段已经可以省略import {xxx} from 'vue'语句的编写了

但是自动生成的文件,希望整理一下,而不是一股脑放在根目录,因为后续省略组件库引入语句时也要生成文件

修改配置dts: 'types/auto-import.d.ts'

此时运行pnpm dev会报错,找不到 types 目录,需要手动创建目录,再运行即可

删除根路径auto-import.d.ts文件,并重新配置tsconfig.json"auto-imports.d.ts" -> "types/**/*.ts"

配置组件自动导入插件

TODO: 原理单独讲解(需要vite原理基础)

项目内组件库

🤔自动导入感觉不适用于项目内资源,而是适用于第三方库(从 node_modules 读取) 因为第三方库即使省略,也知道来源目录 但是项目内资源,不写引入语句,而是直接使用的话,难以确定模块来源,除非项目成员都明确知道相应规则的模块写法对应的来源

第三方组件库

vant-自动引入组件文档element-plus-自动引入组件文档

unplugin-vue-components 并不是 各自组件库维护的插件

ts
// vite.config.js
import Components from 'unplugin-vue-components/vite'
import {
  AntDesignVueResolver,
  ElementPlusResolver,
  VantResolver,
} from 'unplugin-vue-components/resolvers'

// your plugin installation
Components({
  resolvers: [
    AntDesignVueResolver(),
    ElementPlusResolver(),
    VantResolver(),
  ],
})

👆 unplugin-vue-components 提供2种内容

  • 插件本体 unplugin-vue-components/vite
  • 市面上常用组件库配置数据 unplugin-vue-components/resolvers ,用于通过 resolvers 传入插件
    • 当组件库是自己发布的包时,需要自己编写参数

另外除了配置自动引入第三方组件库,也可以指定本地目录的组件(规范?)自定义引入

js
Components({
  // relative paths to the directory to search for components.
  dirs: ['src/components'],
})

unplugin-auto-imports 一样有一些默认配置项,如

  • ts,自动生成.d.ts在根目录下,需要项目的ts配置认这个文件

👇 我们这里配置为

ts
Components({
  // relative paths to the directory to search for components.
  dirs: ['src/components'],
  dts: 'types/components.d.ts',
})

👇 生成 types/components.d.ts

ts
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'

export {}

declare module '@vue/runtime-core' {
  export interface GlobalComponents {
    AnButton: typeof import('./../src/components/base/button/AnButton.vue')['default']
    RouterLink: typeof import('vue-router')['RouterLink']
    RouterView: typeof import('vue-router')['RouterView']
  }
}

代码提交校验

build 前格式校验 git commit 前格式校验