环境信息
- vite
- vue3(setup script)
- typescript -标配
- vue-router -vue3生态
- eslint -代码(js、vue)格式化
- stylelint -代码(css)格式化
- tailwind-css -原子css样式库
- element-plus -vue3组件库
脚手架生成初始化项目
vue3官方脚手架生成项目 👇 vue3官方文档
npm init vue@latest
npm create vue@latest
🤔 为什么有不同的命令来初始化项目
2个指令可以看出区别是 init/create
我们运行
npm create --help
👇 得到
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
👆 看出 create
是 init
的别名,并且指引详情文档也是打开 init
因此 这里运行 npm init vue@latest
=== npm create vue@latest
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
等同于👇
npx create-vue@latest
指令 create-vue官方指令将会得到👇命令行问答
✔ 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项目指令
npm create vite <your-project-name> --template vue
同理等同于 npx create-vite
- create-vite github
脚手架项目已有配置
删除冗余代码后的已有配置
.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
文件
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))
}
}
})
👆 只配置了
- 一个
vue3
的plugin
@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
{
"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
/* 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'
}
}
👆 使用的是 CommonJs
的cjs
文件
配置了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
<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
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
router配置
👇 router/index.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
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
对象上暴露环境变量
环境变量文件目录默认从项目根路径读取 通过👇 配置修改 envdir配置-vite官方文档
新建目录 build/env
存放环境变量
👇 build/env/.env.development
# import.meta.env.VITE_SOME_KEY
VITE_SOME_KEY=dev-VITE_SOME_KEY
👇 build/env/.env.production
# import.meta.env.VITE_SOME_KEY
VITE_SOME_KEY=prod-VITE_SOME_KEY
👇 修改vite.config.ts
export default defineConfig({
plugins: [ ... ],
resolve: { ... },
// 修改环境变量配置目录(默认是根目录)
envDir: fileURLToPath(new URL('./build/env'))
})
vite打包配置根据环境区分文件
vite只给环境变量默认设置 dotenv 目录读取配置 其他打包配置并没有区分环境 因此我们使用函数手动分开使用对应环境的配置文件
👇 build/index.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
// build/config/dev.config.ts
export const devConfig = {}
// build/config/prod.config.ts
export const prodConfig = {}
👆 直接抛出一个对象,会由index.ts包装成函数实现懒加载
👇 vite.config.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
插件配置了
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}
新增一个配置文件 👇 .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.tabSize": 2,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
},
"eslint.alwaysShowStatus": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"html",
"vue"
],
}
配置官方自动导入插件-省略手写import资源
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
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
AutoImport({ /* options */ }),
],
})
此时项目没有任何变化 当运行 pnpm dev
时,根目录下自动生成了 auto-imports.d.ts
文件
// Generated by 'unplugin-auto-import'
export {}
declare global {
}
👆 可以看出声明了全局对象,空的
配置自动注入vue的api引入语句
当我们配置插件内容为vue时 AutoImport({ imports: ['vue'] })
👇 编写插件配置项有ts自动补全提示,imports
的值是一个字符串数组 可以点进去查看具体的枚举
再次运行pnpm dev
自动生成的 auto-imports.d.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选项
plugins: [
AutoImport({
imports: ['vue'],
eslintrc: {
enabled: true, // <-- this
},
}),
],
因为刚才启动着vite,检测到了配置发生了改变自动reload,触发了插件生成 .eslintrc-auto-import.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
中才能生效
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 并不是 各自组件库维护的插件
// 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
传入插件- 当组件库是自己发布的包时,需要自己编写参数
另外除了配置自动引入第三方组件库,也可以指定本地目录的组件(规范?)自定义引入
Components({
// relative paths to the directory to search for components.
dirs: ['src/components'],
})
和 unplugin-auto-imports
一样有一些默认配置项,如
- ts,自动生成
.d.ts
在根目录下,需要项目的ts配置认这个文件
👇 我们这里配置为
Components({
// relative paths to the directory to search for components.
dirs: ['src/components'],
dts: 'types/components.d.ts',
})
👇 生成 types/components.d.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 前格式校验