让项目更健壮
了解项目中每一个文件的作用
到此我们已经初步创建并启动了项目,其实很多人只关注代码开发相关的文件,并不会去纠结项目中和核心开发无关配置文件的作用,这是不对的,我们应该对自己的项目做到极致掌控,了解项目中每一个文件每一行代码对项目的作用,接下来就来一起看看我们创建的项目中所有文件的作用吧!
在初始化创建项目时,默认创建了很多子文件(一些组件、样式文件等等),我们先把不需要的项目无关文件删干净,需要我们处理的无用文件都在 src
文件夹下:
- 删除
src/views
下所有文件 - 删除
src/stores
下所有文件 - 删除
src/components
下所有文件 - 删除
src/assets
下所有文件
清除干净之后,我们在 src/views
文件夹下新建一个 HomePage.vue
文件,随便写点东西:
<script setup></script>
<template>
<div>hello isboyjc, This is toolsdog home page!</div>
</template>
<style scoped></style>
然后修改一下 router/index.js
路由文件,把之前删掉页面的路由干掉,加上 HomePage
页面的路由
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'HomePage',
component: () => import('@/views/HomePage.vue')
}
]
})
export default router
再来修改一下项目根组件 src/App.vue
的内容,把无用的删了,只留下面内容即可
<script setup>
import { RouterView } from 'vue-router'
</script>
<template>
<RouterView />
</template>
<style scoped></style>
最后,项目入口文件里,有一行 css
样式的引入,引入的样式文件我们已经删了,所以,把这行代码删除掉
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
// 删除掉,css文件已经删除过了
// import './assets/main.css'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')
ok,处理好之后我们就会得到一个很干净的项目了,如果你的步骤和我一致,目前的文件目录应该是下面这样的,我们简单介绍下它们的作用。
toolsdog
├─.vscode # vscode配置文件
| └─extensions.json # 项目推荐插件列表(可把项目中用到的vscode插件ID写进去,跑项目时没有安装这些插件会推荐安装)
├─public/ # 公共资源目录
├─src/ # 核心开发目录
| ├─App.vue # 项目根组件
| ├─main.js # 项目入口文件
| ├─views/ # 项目视图目录
| | └─Home/index.vue
| ├─stores/ # 统一状态管理目录-pinia
| ├─router/ # 项目路由目录
| | └─index.js
| ├─components/ # 项目公共组件目录
| ├─assets/ # 项目静态资源目录
├─.eslintrc.cjs # eslint 配置文件
├─.gitignore # git忽略文件
├─.prettierrc.json # prettier 配置文件
├─README.md # 项目说明文件
├─index.html # html入口文件
├─package.json # 项目配置和包管理文件
├─vite.config.js # vite 配置文件
再次启动项目,如果没有问题的话,打开浏览器你的页面目前就是下面这样子
接下来我们逐步的加一些目录、依赖和配置让我们的项目更健壮、更好用!
安装组件库
一般我们开发为了省事儿都会用一个开源组件库,我们这里当然少不了,至于用什么,这里我们用字节的 arco.design ,那至于为什么用这个,因为 ElementPlus
用腻了,尝试下新鲜的,我也是头一次用,组件库嘛,用什么都无所谓。
安装 ArcoVue
npm install --save-dev @arco-design/web-vue
# or
pnpm add -D @arco-design/web-vue
配置按需加载
因为懒,所以我们组件使用 unplugin-vue-components 和 unplugin-auto-import 这两款 vite
插件来开启按需加载及自动导入,插件会自动解析模板中的使用到的组件,并导入组件和对应的样式文件。
其实说白了,这两个插件一个是自动帮我们引入一些组件和指令(只做 HTML
中使用的常规组件例如各种 .vue
组件的引入以及指令的自动引入),另一个是自动帮我们做一些 API
组件的自动引入(像直接在 script
中引入的必须用 API
调用的 Message
组件以及后面我们还会用它做 Vue
的一些 API
自动引入等等)
先安装
npm i unplugin-vue-components -D
npm i -D unplugin-auto-import
# or
pnpm add -D unplugin-vue-components
pnpm add -D unplugin-auto-import
然后我们在 vite.config.js
文件中配置使用一下插件
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ArcoResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ArcoResolver()]
}),
Components({
resolvers: [
ArcoResolver({
sideEffect: true
})
]
})
]
// ...
})
可以看到上面我们在 unplugin-vue-components/resolvers
中导出了一个 ArcoResolver
,它是什么呢?
其实,它是插件内置的解析器,像常用的组件库(element、antd
等)自动引入的一些配置都被内置了 👉🏻 查看内置支持的组件库解析器,我们只需要导出对应 UI
库 的解析器用就行了,如果你使用的组件库没有被内置,你完全可以自己写一个,如果是通用组件库的话,你甚至可以直接 PR
到插件 GitHub
,混一个开源项目的 Contributors
玩,并不复杂,因为后面写项目的时候应该会简单写到几个解析函数,不多解释了。
OK,现在组件库和自动引入都做好了,先试一试,我们在 home
页面分别用 ArcoVue
的普通按钮 AButton
组件和全局提示 AMessage
组件试一试。
<script setup>
const handleClickMini = () => {
AMessage.info('hello isboyjc, click mini AButton!')
};
</script>
<template>
<div>hello isboyjc, This is toolsdog home page!</div>
<a-space>
<a-button type="primary" size="mini" @click="handleClickMini">Mini</a-button>
<a-button type="primary" size="small">Small</a-button>
<a-button type="primary">Medium</a-button>
<a-button type="primary" size="large">Large</a-button>
</a-space>
</template>
<style scoped lang="scss"></style>
可以看到,我们不需要自己去引入就可以随时随地的使用组件库中的组件了!
配置项目内组件 & API 的自动引入
我们在使用 Vue
的过程中,每个 script
以及 js
文件中或多或少需要引入一些像 ref、reactive
等 VueAPI
,包括 VueRouter、Pinia
等都要引入一些 API
,还有我们自己写的组件也都需要我们手动去引入使用。
那既然配置了组件库自动引入,我们接下来也配置API、以及页面组件的自动引入。
还是在 vite.config.js
文件中,依旧还是上面那 2 个插件,我们来写一下配置。
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ArcoResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
// 需要去解析的文件
include: [
/\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
/\.vue$/,
/\.vue\?vue/, // .vue
/\.md$/ // .md
],
// imports 指定自动引入的包位置(名)
imports: ['vue', 'pinia', 'vue-router'],
// 生成相应的自动导入json文件。
eslintrc: {
// 启用
enabled: true,
// 生成自动导入json文件位置
filepath: './.eslintrc-auto-import.json',
// 全局属性值
globalsPropValue: true
},
resolvers: [ArcoResolver()]
}),
Components({
// imports 指定组件所在目录,默认为 src/components
dirs: ['src/components/', 'src/view/'],
// 需要去解析的文件
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
resolvers: [
ArcoResolver({
sideEffect: true
})
]
})
]
// ...
})
如上,在 API
自动引入插件 AutoImport
中我们写了指定要去解析的文件 include
配置,然后在 import
选项中指定了自动引入的包名,并且所有自动引入的 API
在被自动引入时会添加记录到根目录的 ./.eslintrc-auto-import.json
文件中,方便我们查看都自动引入了哪些东西,后面我们使用这几个包的 API
,就不需要手动引入了,插件会帮我们在文件解析时自动引入。
同样的,在组件自动引入插件 Components
中,我们配置了指定要去解析的文件 include
配置,然后在 import
选项中指定了自动引入的组件目录,以后只要是在这几个目录下写的组件,我们在使用时都必须要手动去引入了
ok,我们来试一下。
我们在 src/components
文件夹下新建一个 HelloWorld.vue
文件,写上下面内容。
<script setup>
const name = ref('isboyjc')
</script>
<template>
<div>hello {{ name }}, this is helloworld components</div>
</template>
<style scoped></style>
然后,直接在 src/views/HomePage.vue
文件中使用 HelloWorld
组件,不要引入,如下:
<script setup>
const handleClickMini = () => {
AMessage.info('hello isboyjc, click mini AButton!')
}
</script>
<template>
<div>hello isboyjc, This is toolsdog home page!</div>
<a-space>
<a-button type="primary" size="mini" @click="handleClickMini">
Mini
</a-button>
<a-button type="primary" size="small">Small</a-button>
<a-button type="primary">Medium</a-button>
<a-button type="primary" size="large">Large</a-button>
</a-space>
<!-- 这里 -->
<HelloWorld />
</template>
<style scoped lang="scss"></style>
上面我们在创建的 HelloWorld
组件中使用了 Vue
的 ref API
,并没有引入它,而后在 HomePage
页面中使用该组件也没有引入,我们来跑一下项目。
nice!后面我们使用 Vue
、VueRouter
、Pinia
、ArcoVue
包括自建组件等等都不需要手动引入了,当然,后续你的项目中有用到其他地方你依然可以在插件中去配置!
安装 VueUse
VueUse 大家没用过的话可以先把它理解为一个基于 Vue
的工具库,Vue2
、Vue3
都可以用,有很多实用的方法、组件包括指令,超级方便,后续我们会用到其中的一些方法,所以先装上
安装
npm i @vueuse/core
// or
pnpm add @vueuse/core
配置自动引入
VueUse
不止有方法,还有组件和指令,所以我们还是需要上面两个自动引入的插件去处理,那由于作者是一个人,解析器都内置在自动引入插件中了,所以我们直接导出用就可以了。
我们配置 VueUse
的组件和指令自动引入需要两个解析器,还是在 vite.config.js
配置文件中引入,如下:
// ArcoVue、VueUse 组件和指令的自动引入解析器
import {
ArcoResolver,
VueUseComponentsResolver,
VueUseDirectiveResolver
} from 'unplugin-vue-components/resolvers'
使用的话,只需要在配置文件 plugins
模块中之前写过的 Components
插件中使用一下这两个解析器就好了:
plugins: [
Components({
// ...
VueUseComponentsResolver(),
VueUseDirectiveResolver()
})
]
那 API
方法的自动引入就很简单了,还是配置文件中只需要在之前用过的 AutoImport
插件中添加一个 VueUse
包名配置就行了:
plugins: [
AutoImport({
// 新增 '@vueuse/core'
imports: ['vue', 'pinia', 'vue-router', '@vueuse/core'],
resolvers: [ArcoResolver()]
}),
]
这样我们就可以在项目中随时随地的使用 VueUse
了!建议大家有时间可以去看看 VueUse
的源码实现,也并不复杂,它有很多最佳实践,可以给我们使用 Vue3
提供很大的帮助!
配置 ESLint 和 Prettier
那上面我们配置了自动引入,但是大家会发现,由于之前我们给项目安装了 ESLint
和 Prettier
,虽然还没有进行配置,但是默认配置会给那些自动引入的 API
报红,就比如下面这样
还有这个
作为一个强迫症患者,这是不能存在的,所以我们配置下 ESLint
和 Prettier
,配置之前我们先看看初始化的配置是什么样子
根目录下的 .eslintrc.cjs
是 ESLint
配置,当前默认如下
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-prettier'
],
parserOptions: {
ecmaVersion: 'latest'
}
}
根目录下的 .prettierrc.json
是 Prettier
配置,当前默认如下
{}
那么如何让我们自动引入的那些 API
不报红呢?
那报红这种检测肯定是 ESLint
干的, 还记得我们自动引入配置的那个导出文件吗?我们所有自动引入的 API
都生成了记录在这个文件,你只需要将它写入 ESLint
配置的 extends
中让 Lint
工具识别下就好了,如下
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
// 这里
'./.eslintrc-auto-import.json',
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-prettier'
],
parserOptions: {
ecmaVersion: 'latest'
}
}
注意,extends
这个继承配置的是一个数组,最终会将所有规则项进行合并,出现冲突的时候,后面的会覆盖前面的,我们在初始化项目安装时默认给加上去了 3 个,我们看看这三个是什么?
- plugin:vue/vue3-essential
ESLint Vue3
插件扩展
- eslint:recommended
ESLint
官方扩展
- @vue/eslint-config-prettier
Prettier NPM
扩展
我们把 Prettier
扩展放到最后面,原因是 Prettier
会格式化代码,是为了保证最终代码格式统一。
ok,保存在看看,是不是不报红了。
那么我们还需要配置什么?
可能很多同学没有用过 Vue3
,这里直接给大家说,由于我们接下来要使用 Vue3
的 CompositionAPI
,那 Vue3
有几个可以直接在 <script setup>
中可用的全局 API
,比如 defineProps
、defineEmits
、defineExpose
,如果你使用 TS
,还会用到 withDefaults
。
那我们的 ESLint
默认是识别不了这些全局 API
的,此时需要向 ESlint
规则中添加需要辨认的全局变量。
那 ESLint
配置中的 globals
属性就是让项目在 lint
执行期间访问额外的全局变量,简单说就是开发者自定义的全局变量,我们依次加上这些属性就可以了。
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'./.eslintrc-auto-import.json',
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-prettier'
],
// 这里
globals: {
defineEmits: "readonly",
defineProps: "readonly",
defineExpose: "readonly",
withDefaults: "readonly",
},
parserOptions: {
ecmaVersion: 'latest'
}
}
如上,添加全局属性时,readonly
代表只读,writable
代表可写,可写就是可以手动覆盖这个全局变量的意思,我们当然是不允许覆盖了,所以全部都设置成了 readonly
。
我们可以看到在 .eslintrc.cjs
文件中第一行有个 /* eslint-env node */
注释,它是用来指定文件为 node
环境的。
作为一个比较严格的强迫症,这种莫名其妙的注释是不允许出现的,OK,删掉它,所以就报红了:
这就是因为我们没有去指定这个文件用 node
规则解析,其实我们不需要去在开头写这么一行注释指定文件环境,我们给 ESLint
指定一下常用环境,即 env
属性配置,让 ESLint
自己去匹配,我们不写这个配置的话默认它只支持浏览器 browser
的规则解析,写上环境配置,我们最终的配置文件如下:
require("@rushstack/eslint-patch/modern-module-resolution");
module.exports = {
// 这里
env: {
// 浏览器环境
browser: true,
// Node环境
node: true,
// 启用除了modules以外的所有 ECMAScript 6 特性
es2021: true,
},
root: true,
extends: [
"./.eslintrc-auto-import.json",
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-prettier",
],
globals: {
defineEmits: "readonly",
defineProps: "readonly",
defineExpose: "readonly",
withDefaults: "readonly",
},
parserOptions: {
ecmaVersion: "latest",
},
rules: {
semi: ["warn", "never"], // 禁止尾部使用分号
"no-debugger": "warn", // 禁止出现 debugger
},
};
如上所示,我们在环境这块配置了三个
- browser ── 浏览器环境
- node ──
Node
环境 - es6 ── 启用除了
modules
以外的所有ECMAScript 6
特性
都用的到,直接都开启就好。
可能我们也都发现了,我们新增了一个 rules
属性,如单词字面意思,就是规则的配置,可以配置启用一些规则及其各自的错误级别,那由于每个人的喜好不同,所以我没有过多配置,只配置了 2 个。
rules
的规则配置有三种:
off 或 0
关闭对该规则的校验warn 或 1
启用规则,不满足时抛出警告,不会退出编译进程error 或 2
启用规则,不满足时抛出错误,会退出编译进程
注意,如果某项规则,有额外的选项,可以通过数组进行传递,数组的第一位必须是错误级别,就比如我们配置的 semi
规则中的 never
就是额外配置项。
那我们就配置到这里,后续大家按照自己的喜好在 rules
中配置一些规则就可以了。
PS:
亲测安装了 VSCode
的 Babel JavaScript
插件后, JS
代码末尾如果不加分号,会导致后续代码高亮、注释不正常,在 VSCode
中禁用这个插件就好了,高亮错误展示如下:
配置完了 ESLint
,我们再来看Prettier
,我这边配置了几个常用的,如下
{
"semi": false,
"singleQuote": true,
"printWidth": 80,
"trailingComma": "none",
"arrowParens": "avoid",
"tabWidth": 2
}
semi
代码结尾是否加分号singleQuote
是否使用单引号printWidth
超过多少字符强制换行trailingComma
代码末尾不需要逗号arrowParens
单个参数的箭头函数不加括号x => x
tabWidth
使用 n 个空格缩进
Prettier
配置就比较简单,按照文档和喜好在 .prettierrc.json
文件中配置就可以,注意配置的时候一定要和 ESLint
的 rules
比较一下,这里是会发生冲突的地方,检测和格式化规则一定要一致。
配置 SVGIcon
项目中我们多多少少会用到一些图标,而目前 SVG
图标是主流,所以我们配置下 SVG
图标。
我们这里还用到 antFu
大佬写的 unplugin-icons 插件,它基于 iconify 图标库支持按需访问上万种图标,当然,我们不使用图标库也是可以的,也可以配置自定义一些 SVG
图标。
同时,因为此插件和上面自动引入的两个插件都是 antFu
大佬写的,所以,我们也可以直接为引入的 Icon
配置自动引入对应图标组件,很方便。
安装插件
npm i -D unplugin-icons
# or
pnpm add unplugin-icons -D
配置插件
配置之前,我们先在 src/assets
文件夹下创建一个 svg
文件夹,然后在其下面新建两个文件夹,一个叫 home
一个叫 user
,在下面随便放 2 个 svg
文件,我们下面会给大家简单演示怎么配置和使用自定义的 SVG 图标集合。
这块详细配置相关的就不给大家讲了,因为文章 Vue3!ElementPlus!更加优雅的使用Icon 中已经详细说过了,大家可以先去看看这便文章再来看这块,我这边再写的话比较浪费时间。
整个的配置如下所示:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// API自动引入插件
import AutoImport from "unplugin-auto-import/vite"
// 组件自动引入插件
import Components from "unplugin-vue-components/vite"
import {
ArcoResolver,
VueUseComponentsResolver,
VueUseDirectiveResolver
} from 'unplugin-vue-components/resolvers'
// icon 插件
import Icons from "unplugin-icons/vite"
// icon 自动引入解析器
import IconsResolver from "unplugin-icons/resolver"
// icon 加载 loader
import { FileSystemIconLoader } from "unplugin-icons/loaders"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
include: [
/\.[tj]sx?$/,
/\.vue$/,
/\.vue\?vue/,
/\.md$/,
],
imports: ["vue", "pinia", "vue-router"],
eslintrc: {
enabled: true,
filepath: "./.eslintrc-auto-import.json",
globalsPropValue: true,
},
resolvers: [ArcoResolver()],
}),
Components({
dirs: ["src/components/", "src/view/", "@vueuse/core"],
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
resolvers: [
ArcoResolver({
sideEffect: true,
}),
VueUseComponentsResolver(),
VueUseDirectiveResolver(),
// icon组件自动引入解析器使用
IconsResolver({
// icon自动引入的组件前缀 - 为了统一组件icon组件名称格式
prefix: "icon",
// 自定义的icon模块集合
customCollections: ["user", "home"],
}),
],
}),
// Icon 插件配置
Icons({
compiler: "vue3",
customCollections: {
// user图标集,给svg文件设置 fill="currentColor" 属性,使图标的颜色具有适应性
user: FileSystemIconLoader("src/assets/svg/user", (svg) =>
svg.replace(/^<svg /, '<svg fill="currentColor" ')
),
// home 模块图标集
home: FileSystemIconLoader("src/assets/svg/home", (svg) =>
svg.replace(/^<svg /, '<svg fill="currentColor" ')
),
},
autoInstall: true,
}),
]
})
使用图标库中 Icon
打开 iconify
的官方图标网站 icones
随便挑几个复制一下名字
然后我们在 HomePage
页面中直接去按规则写组件就行(我们上面配置了自动引入的 icon
组件前缀必须写 <icon-
),那我们使用起来如下:
<!-- ep:alarm-clock 改成 ep-alarm-clock -->
<icon-ep-alarm-clock class=""/>
如上,我们 copy
名字就可以直接使用,项目运行时写完保存,项目就可以自动为我们下载这个集合的图标并识别解析这个格式的 icon
组件
使用自定义 Icon
那如何使用我们自定义的图标集,比如上面我们配置中自定义的 home、user
集合,我们在 home
文件夹下放了一个 copy.svg
还有一个 download.svg
文件,使用如下
<!-- 相当于 home:xxx,xxx就是home文件夹下的图标文件名 -->
<icon-home-copy class=""/>
<icon-home-download class=""/>
同样,自定义的图标我们也写在 在 HomePage
页面中。
最后我们来看下目前 HomePage
页面代码
<script setup>
const handleClickMini = () => {
AMessage.info("hello isboyjc, click mini AButton!")
}
</script>
<template>
<div>hello isboyjc, This is toolsdog home page!</div>
<a-space>
<a-button type="primary" size="mini" @click="handleClickMini">
Mini
</a-button>
<a-button type="primary" size="small">Small</a-button>
<a-button type="primary">Medium</a-button>
<a-button type="primary" size="large">Large</a-button>
</a-space>
<HelloWorld />
<!-- Icon -->
<div>
<icon-ep-alarm-clock class="" />
<icon-home-copy class="" />
<icon-home-download class="" />
</div>
</template>
<style scoped></style>
最终效果:
安装 VSCode 插件 Iconify IntelliSense
这是 iconify
图标库的 VSCode
插件,VSCode
中搜索插件:
Iconify IntelliSense
// or 搜索插件ID
****
antfu.iconify
安装之后,我们在使用图标库内图标时,直接可以在 VSCode
中预览到图标,超级方便,如下
图标样式的话你可以直接在 icon
组件中添加,OK,图标也好了!
Styles 公共样式管理、初始化样式
接下来我们简单做一下 CSS
公共样式的处理,我们在项目 src
目录下新增一个 styles
文件夹,此文件夹我们后期可以放一些公共的样式文件。
大家都知道,HTML
标签是有默认样式的,一般我们在写项目时都会直接清除掉这个默认样式,也就是做个重置。
那相较于 Eric Merer 原版的清楚样式文件,Normalize.css
它在默认的 HTML
元素样式上提供了跨浏览器的高度一致性,是一种现代的、为 HTML5
准备的优质替代方案,所以我们直接使用它就好了。
下载 Normalize.css 到 Styles
文件夹下,当然你也可以直接 npm
安装它,不过我比较喜欢直接下载下来这个文件。
下载下来之后直接在 main.js
最上面引入一下就行了,如下
import { createApp } from "vue"
import { createPinia } from "pinia"
// 这里
import "@/styles/normalize.css"
import App from "./App.vue"
import router from "./router"
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount("#app")
其他的公共 css
文件我们用到的时候也可以这样引入一下就可以了。
配置 UnoCSS
CSS
这块,我们的原则是能简单就简单,所以我们基于 ACSS
即原子化的 CSS
框架来做。
不了解 原子化 的同学建议先看下这篇文章 「前端工程四部曲」模块化的前世今生(下)
Tailwind CSS 大家应该都知道, WindiCSS 呢,算是他的一个超集,本来我想用 WindiCSS
的,但是 WindiCSS
作者们都不咋维护了,然后 UnoCSS 又这么便捷,配置文件都不需要写,直接引入 Vite
插件和对应的预设就可以了,So,上 UnoCSS
。。。
UnoCSS
,官方说它是一个按需原子 CSS
引擎,它默认提供了流行实用程序优先框架 Tailwind CSS
、Windi CSS
、Bootstrap
、Tachyon
等的通用超集,如果你习惯这些框架,依旧可以按照熟悉的方式写,无缝衔接。
不用不知道,用了是真香啊,当然,肯定是有人不喜欢原子化 CSS
的,不喜欢不用,毕竟就是写 CSS
的方式不一样而已,我这边也不会做很多配置,你甚至可以用原生 CSS
!
安装
npm install --save-dev unocss @unocss/preset-uno @unocss/preset-attributify @unocss/transformer-directives
# or
pnpm i -D unocss @unocss/preset-uno @unocss/preset-attributify @unocss/transformer-directives
如上,我们装了 4 个包
- unocss 核心插件
- @unocss/preset-uno 默认预设,
Tailwind / WindiCSS
等超集 - @unocss/preset-attributify 属性预设,为其他预设和规则提供属性模式,下面会介绍
- @unocss/transformer-directives 指令转换器插件,允许使用
@apply
指令在style
中写原子化css
,后面还会介绍
配置
还是在 Vite
插件配置中,也就是 vite.config.js
文件中配置
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// ...
// Unocss 插件
import Unocss from 'unocss/vite'
// Unocss 默认预设
import presetUno from '@unocss/preset-uno'
// Unocss 属性模式预设
import presetAttributify from '@unocss/preset-attributify'
// Unocss 指令转换插件
import transformerDirective from '@unocss/transformer-directives'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
// ...
// 新增一个 Unocss 插件配置
Unocss({
// 预设
presets: [presetUno(), presetAttributify()],
// 指令转换插件
transformers: [transformerDirective()],
// 自定义规则
rules: []
}),
]
// ...
})
OK,就这么简单。
使用
在使用之前我们先在入口文件 main.js
中一下 UnoCSS
的 css
文件:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import '@/styles/normalize.css'
// 导入Unocss样式
import 'uno.css'
// ...
基础使用如下:
<button class="bg-blue-400 hover:bg-blue-500 text-sm text-white font-mono font-light py-2 px-4 rounded border-2 border-blue-200 dark:bg-blue-500 dark:hover:bg-blue-600">
Button
</button>
如果你对这些原子化样式不太熟悉的话,直接打开样式查询地址 https://uno.antfu.me/
输入对应 CSS
样式去搜就可以了,如下:
当然,我是不建议你这么做的,因为个人觉得不太好用,感觉好像不太完善,既然我们用的预设支持 Tailwind / WindiCSS
,所以,我建议大家直接去看看这两个文档,了解一个大概,按照这两个东西的写法来就可以,有啥不会的去这两个的文档里搜
那推荐最好是直接去看 WindiCSS
使用文档,因为作者是一个人嘛,如下,直接搜对应 CSS
看怎样使用就可以了
然后在项目中直接使用就行了,真丫机智!!!
我们上面安装了 @unocss/preset-attributify
属性预设,所以我们也可以使用属性模式,可以将实用程序分为多个属性,这样写:
<button
bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600"
text="sm white"
font="mono light"
p="y-2 x-4"
border="2 rounded blue-200"
>
Button
</button>
肯定有人觉得原子化 CSS
全写在 HTML
中,太多的话,看上去眼花缭乱很不爽,那所以我们还安装了指令转换器插件 @unocss/transformer-directives
就是为了解决这个问题了。
它允许我们使用 @apply
指令在 style
中写原子化 CSS
:
<button class="btn-style">
Button
</button>
<style>
.btn-style{
@apply bg-blue-400 text-sm text-white font-mono font-light py-2 px-4 rounded border-2 border-blue-200;
@apply hover:bg-blue-500;
@apply dark:bg-blue-500 dark:hover:bg-blue-600;
}
</style>
安装 VSCode 插件 UnoCSS
我们安装一下 UnoCSS
官方的 VSCode
插件,VSCode
扩展中搜索:
UnoCSS
// or 搜索ID
antfu.unocss
安装之后,就可以在编辑器中看到我们写的原子化对应的 CSS
样式了,如下:
当然 UnoCSS
还有很多功能,这里不过多描述了,有兴趣文档看一看,后面写项目的过程中也会逐步的去介绍。
如果我们是刚开始使用,肯定会不习惯,但是,只要坚持几天,直接真香警告!毕竟,一切都是为了简单,一切都是为了省事儿,一切都为了少写代码!!!
Utils、Hooks、API 管理
我们在项目 src
目录下添加一个 utils
文件夹,此文件夹用于存放我们项目中用到的一些公共方法文件。
同样的,我们在项目 src
目录下添加一个 hooks
文件夹,此文件夹用于存放我们项目中用到的一些 hooks
文件,因为我们用 Vue3
的 CompsitionAPI
,后面用多了自然会有很多 hooks
文件,针对一些公用的,我们统一管理在此文件夹下。
平常我们做项目,一般和请求相关的文件都统一放在一个文件夹下,所以我们在项目 src
目录下添加一个 api
文件夹,用于存放和请求相关的文件,因为项目性质,所以我们应该暂时用不到请求后端接口,那这边 api
文件的一些配置以及 axios
的封装甚至 API Mock
配置这里都先不展开说了,回头我会写好这块代码提交到 GitHub
,后面有需要的话会写一篇 axios
封装相关的文章来单独介绍这块。
其他 Vite 配置
先来说环境的配置,先放个官方文档压压惊
OK,我们在 env
目录下新建下面 3 个文件
.env
所有模式下都会加载.env.development
只在开发模式下加载.env.production
只在生产模式下加载
.env
文件在所有模式下都会加载,所以这里我们可以写一些所有环境都通用的环境变量,如下:
# 所有环境都会加载
# 项目标识代码
VITE_APP_CODE="TOOLSDOG"
# 项目名
VITE_APP_NAME="工具狗"
# 项目描述
VITE_APP_DESCRIPTION="你用的到的工具,这里都有!"
注意,我们在 Vite
中配置的环境变量默认只有以 VITE_
开头的配置,才会暴露给客户端,我们才能在项目中获取到。
开发模式 .env.development
配置
# 开发环境加载
# 环境标识
VITE_APP_ENV="development"
# 公共基础路径
VITE_BASE="/"
# 代理URL路径
VITE_BASE_URL ="/api"
# 模拟数据接口路径
VITE_BASE_MOCK_URL ="/mock-api"
# 服务端接口路径
VITE_BASE_SERVER_URL = "..."
# 打包是否使用Mock
VITE_APP_PRODMOCK=false
那生产环境除了环境标识 VITE_APP_ENV
和开发模式标识不同,其他配置项应尽量保持一致,只是配置项的内容不同而已,不一一的展示了。
接下来修改下 package.json
脚本命令如下
{
"scripts": {
"serve": "vite --mode development",
"build": "vite build --mode production",
"preview": "vite preview --port 8081",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
}
}
上面脚本中我们给启动命令搞成了
npm run serve
// or
pnpm serve
在 serve
脚本命令配置中,我们还传了一个 mode
,其实这个 mode
就是对应我们的环境文件 .env.[mode]
开发环境默认 mode
就是 development
,生产环境默认 mode
就是 development
,所以脚本命令这里我不传 mode
也可以,但是如果大家把开发环境文件由 .env.development
改成 .env.dev
,那脚本中 mode
就得传 —-mode dev
,build
时也是一样的道理,如果有其他环境,那脚本命令传入对应的 mode
就可以了。
如果想要在 vite.config.js
文件中获取对应运行 mode
环境变量的配置,我们可以使用 vite
的 loadEnv API。
Vite
的 defineConfig
方法也可以接收一个返回配置对象的回调函数,回调函数的参数里我们可以拿到运行脚本命令时传入的 mode
值,从而使用 loadEnv
方法去在 Vite
配置文件中获取对应 mode
下的环境变量,如下:
// export default defineConfig({}) 修改
export default defineConfig(({mode}) => {
return {}
})
那其他一些基础配置就不一一说明了,大家可以直接看 Vite 官方文档
目前我们的配置如下:
/*
* @LastEditors: isboyjc
* @Description: Vite 项目配置
* @Date: 2022-09-17 14:35:02
* @LastEditTime: 2022-09-23 01:56:21
* @Author: isboyjc
*/
import { fileURLToPath, URL } from 'node:url'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
// API自动引入插件
import AutoImport from 'unplugin-auto-import/vite'
// 组件自动引入插件
import Components from 'unplugin-vue-components/vite'
// ArcoVue、VueUse 组件和指令自动引入解析器
import {
ArcoResolver,
VueUseComponentsResolver,
VueUseDirectiveResolver
} from 'unplugin-vue-components/resolvers'
// icon 插件
import Icons from 'unplugin-icons/vite'
// icon 自动引入解析器
import IconsResolver from 'unplugin-icons/resolver'
// icon 加载 loader
import { FileSystemIconLoader } from 'unplugin-icons/loaders'
// Unocss 插件
import Unocss from 'unocss/vite'
// Unocss 默认预设
import presetUno from '@unocss/preset-uno'
// Unocss 属性模式预设
import presetAttributify from '@unocss/preset-attributify'
// Unocss 指令插件
import transformerDirective from '@unocss/transformer-directives'
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
const viteEnv = loadEnv(mode, './')
return {
base: viteEnv.VITE_BASE,
server: {
host: '0.0.0.0',
port: '8080',
open: true,
// 端口占用直接退出
strictPort: true
// 本地服务 CORS 是否开启
// cors: true,
// proxy: {
// [viteEnv.VITE_BASE_URL]: {
// target: viteEnv.VITE_BASE_SERVER_URL,
// // 允许跨域
// changeOrigin: true,
// rewrite: path => path.replace(viteEnv.VITE_BASE_URL, '/')
// }
// }
},
build: {
outDir: 'dist',
assetsDir: 'static/assets',
// sourcemap: true,
// 规定触发警告的 chunk 大小,消除打包大小超过500kb警告
chunkSizeWarningLimit: 2000,
// 静态资源打包到dist下的不同目录
rollupOptions: {
output: {
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
}
}
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
plugins: [
vue(),
// 使用Unocss
Unocss({
// 预设
presets: [presetUno(), presetAttributify()],
// 指令转换插件
transformers: [transformerDirective()],
// 自定义规则
rules: []
}),
AutoImport({
include: [
/\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
/\.vue$/,
/\.vue\?vue/, // .vue
/\.md$/ // .md
],
imports: ['vue', 'pinia', 'vue-router', '@vueuse/core'],
// 生成相应的自动导入json文件。
// eslint globals Docs - https://eslint.org/docs/user-guide/configuring/language-options#specifying-globals
eslintrc: {
enabled: true,
filepath: './.eslintrc-auto-import.json', // Default `./.eslintrc-auto-import.json`
globalsPropValue: true // Default `true`, (true | false | 'readonly' | 'readable' | 'writable' | 'writeable')
},
resolvers: [ArcoResolver()]
}),
Components({
// imports 指定组件所在位置,默认为 src/components
dirs: ['src/components/', 'src/view/'],
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
resolvers: [
ArcoResolver({
sideEffect: true
}),
VueUseComponentsResolver(),
VueUseDirectiveResolver(),
IconsResolver({
// icon自动引入的组件前缀 - 为了统一组件icon组件名称格式
prefix: 'icon',
// 自定义的icon模块集合
customCollections: ['user', 'home']
})
]
}),
Icons({
compiler: 'vue3',
customCollections: {
// user图标集,给svg文件设置fill="currentColor"属性,使图标的颜色具有适应性
user: FileSystemIconLoader('src/assets/svg/user', svg =>
svg.replace(/^<svg /, '<svg fill="currentColor" ')
),
// home 模块图标集
home: FileSystemIconLoader('src/assets/svg/home', svg =>
svg.replace(/^<svg /, '<svg fill="currentColor" ')
)
},
autoInstall: true
})
]
}
})
那项目配置文件暂时就到这里了,后期写项目还会用到一些东西,我们到时候再配置。
添加 Config 配置
上面说了,环境变量默认以 VITE_
开头的配置,才会暴露给客户端,我们也写了几个 VITE_
开头的配置,所以在项目运行时,我们可以直接 i
去查看配置,但是这样太麻烦了,所以我们写一个统一的配置文件去获取环境变量,包括项目后期的一些全局配置也可以写里面
项目 src
目录下新建 config/config.js
文件,写入下面文件:
/*
* @LastEditors: isboyjc
* @Description: 全局config配置文件
* @Date: 2022-09-17 14:35:02
* @LastEditTime: 2022-09-17 14:35:02
* @Author: isboyjc
*/
// 获取环境变量
const ENV = import.meta.env
// 配置文件
let config = {}
// 默认配置文件
const configSource = {
appCode: ENV.VITE_APP_CODE,
// 项目标识代码
projectCode: `${ENV.VITE_APP_CODE}_${ENV.VITE_APP_ENV}`,
// 项目名
projectName: ENV.VITE_APP_NAME,
// 项目描述
projectDesc: ENV.VITE_APP_DESCRIPTION,
// 资源base地址
base: ENV.VITE_BASE,
// 接口代理URL路径
baseUrl: ENV.VITE_BASE_URL,
// 模拟数据接口路径
mockBaseUrl: ENV.VITE_BASE_MOCK_URL,
// 服务端接口路径
serverUrl: ENV.VITE_BASE_SERVER_URL
}
/**
* @Author isboyjc
* @Date 2022-09-17 14:35:02
* @description 设置全局配置
* @param {Object} cfg 配置项
* @return {Object} 新的全局配置 config
*/
const setConfig = cfg => {
config = Object.assign(config, cfg)
return config
}
/**
* @Author isboyjc
* @Date 2022-09-17 14:35:02
* @description 重置全局配置
* @param {*}
* @return {Object} 全局默认配置 configSource
*/
const resetConfig = () => {
config = { ...configSource }
return config
}
resetConfig()
/**
* @Author isboyjc
* @Date 2022-09-17 14:35:02
* @description 获取全局配置
* @param {String} key 配置项,支持 'a.b.c' 的方式获取
* @return {Object} 新的全局配置 config
*/
const getConfig = key => {
if (typeof key === 'string') {
const arr = key.split('.')
if (arr && arr.length) {
let data = config
arr.forEach(v => {
if (data && typeof data[v] !== 'undefined') {
data = data[v]
} else {
data = null
}
})
return data
}
}
if (Array.isArray(key)) {
const data = config
if (key && key.length > 1) {
let res = {}
key.forEach(v => {
if (data && typeof data[v] !== 'undefined') {
res[v] = data[v]
} else {
res[v] = null
}
})
return res
}
return data[key]
}
return { ...config }
}
export { getConfig, setConfig, resetConfig }
上面代码不复杂,所以不过多解释了,介绍下为什么用这种方式,而不是直接写一个对象导出,其实是因为有次写项目有用到了动态修改全局配置的需求,所以就把全局配置项获取写成动态的了。
我们写入配置时,只需要在 configSource
对象中写入就可以了,项目中使用起来的话如下:
import { getConfig, setConfig, resetConfig } from "@/config/config.js"
// 获取配置
getConfig("a")
getConfig("a.b")
getConfig("a.b.c")
// 动态设置
setConfig({ ... })
// 重置配置
resetConfig()
配置 VSCode 推荐扩展插件
打开项目根目录的 .vscode/extensions.json
文件如下:
{
"recommendations": [
"vue.volar",
"vue.vscode-typescript-vue-plugin"
]
}
这是创建项目时默认存在的扩展插件推荐配置,此文件的作用上面也介绍过了,就是个扩展推荐,数组里是 VSCode
的扩展插件 ID
,你在根目录打开此项目时,如果编辑器没有安装这两个插件,VSCode
就会在右下角自动提示你去安装插件。
那我们没有使用 TS
直接干掉 vue.vscode-typescript-vue-plugin
这一项。
我们想要添加一个插件到推荐里,可以复制插件 ID
写到此配置中,也可以直接点击添加到工作区建议,如下:
回顾一下上文,我们一共装了 2 个插件
Iconify IntelliSense
UnoCSS
那我这里把这两个插件的 ID
(注意是 ID
不是插件名字)都写进推荐列表里了:
{
"recommendations": [
"vue.volar",
"antfu.iconify",
"antfu.unocss"
]
}
大家如果克隆项目使用 VSCode
启动,就会提示你去安装这些插件了。