Skip to content
快看这页儿写了啥...

让项目更健壮

了解项目中每一个文件的作用

到此我们已经初步创建并启动了项目,其实很多人只关注代码开发相关的文件,并不会去纠结项目中和核心开发无关配置文件的作用,这是不对的,我们应该对自己的项目做到极致掌控,了解项目中每一个文件每一行代码对项目的作用,接下来就来一起看看我们创建的项目中所有文件的作用吧!

在初始化创建项目时,默认创建了很多子文件(一些组件、样式文件等等),我们先把不需要的项目无关文件删干净,需要我们处理的无用文件都在 src 文件夹下:

  • 删除 src/views 下所有文件
  • 删除 src/stores 下所有文件
  • 删除 src/components 下所有文件
  • 删除 src/assets 下所有文件

清除干净之后,我们在 src/views 文件夹下新建一个 HomePage.vue 文件,随便写点东西:

html
<script setup></script>

<template>
  <div>hello isboyjc, This is toolsdog home page!</div>
</template>

<style scoped></style>

然后修改一下 router/index.js 路由文件,把之前删掉页面的路由干掉,加上 HomePage 页面的路由

jsx
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 的内容,把无用的删了,只留下面内容即可

html
<script setup>
import { RouterView } from 'vue-router'
</script>

<template>
  <RouterView />
</template>

<style scoped></style>

最后,项目入口文件里,有一行 css 样式的引入,引入的样式文件我们已经删了,所以,把这行代码删除掉

jsx
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,处理好之后我们就会得到一个很干净的项目了,如果你的步骤和我一致,目前的文件目录应该是下面这样的,我们简单介绍下它们的作用。

bash
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 配置文件

再次启动项目,如果没有问题的话,打开浏览器你的页面目前就是下面这样子

https://qiniu.isboyjc.com/picgo/202209260002051.png

接下来我们逐步的加一些目录、依赖和配置让我们的项目更健壮、更好用!

安装组件库

一般我们开发为了省事儿都会用一个开源组件库,我们这里当然少不了,至于用什么,这里我们用字节的 arco.design ,那至于为什么用这个,因为 ElementPlus 用腻了,尝试下新鲜的,我也是头一次用,组件库嘛,用什么都无所谓。

安装 ArcoVue

bash
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 自动引入等等)

先安装

bash
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 文件中配置使用一下插件

jsx
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 组件试一试。

html
<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>

https://qiniu.isboyjc.com/picgo/202209260002340.png

可以看到,我们不需要自己去引入就可以随时随地的使用组件库中的组件了!

配置项目内组件 & API 的自动引入

我们在使用 Vue 的过程中,每个 script 以及 js 文件中或多或少需要引入一些像 ref、reactiveVueAPI,包括 VueRouter、Pinia 等都要引入一些 API,还有我们自己写的组件也都需要我们手动去引入使用。

那既然配置了组件库自动引入,我们接下来也配置API、以及页面组件的自动引入。

还是在 vite.config.js 文件中,依旧还是上面那 2 个插件,我们来写一下配置。

jsx
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 文件,写上下面内容。

html
<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 组件,不要引入,如下:

html
<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 组件中使用了 Vueref API,并没有引入它,而后在 HomePage 页面中使用该组件也没有引入,我们来跑一下项目。

https://qiniu.isboyjc.com/picgo/202209260003175.png

nice!后面我们使用 VueVueRouterPiniaArcoVue 包括自建组件等等都不需要手动引入了,当然,后续你的项目中有用到其他地方你依然可以在插件中去配置!

安装 VueUse

VueUse 大家没用过的话可以先把它理解为一个基于 Vue 的工具库,Vue2Vue3 都可以用,有很多实用的方法、组件包括指令,超级方便,后续我们会用到其中的一些方法,所以先装上

安装

bash
npm i @vueuse/core

// or

pnpm add @vueuse/core

配置自动引入

VueUse 不止有方法,还有组件和指令,所以我们还是需要上面两个自动引入的插件去处理,那由于作者是一个人,解析器都内置在自动引入插件中了,所以我们直接导出用就可以了。

我们配置 VueUse 的组件和指令自动引入需要两个解析器,还是在 vite.config.js 配置文件中引入,如下:

bash
// ArcoVue、VueUse 组件和指令的自动引入解析器
import {
  ArcoResolver,
  VueUseComponentsResolver,
  VueUseDirectiveResolver
} from 'unplugin-vue-components/resolvers'

使用的话,只需要在配置文件 plugins 模块中之前写过的 Components 插件中使用一下这两个解析器就好了:

jsx
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

那上面我们配置了自动引入,但是大家会发现,由于之前我们给项目安装了 ESLintPrettier ,虽然还没有进行配置,但是默认配置会给那些自动引入的 API 报红,就比如下面这样

https://qiniu.isboyjc.com/picgo/202209260003389.png

还有这个

https://qiniu.isboyjc.com/picgo/202209260004229.png

作为一个强迫症患者,这是不能存在的,所以我们配置下 ESLintPrettier ,配置之前我们先看看初始化的配置是什么样子

根目录下的 .eslintrc.cjsESLint 配置,当前默认如下

jsx
/* 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.jsonPrettier 配置,当前默认如下

json
{}

那么如何让我们自动引入的那些 API 不报红呢?

那报红这种检测肯定是 ESLint 干的, 还记得我们自动引入配置的那个导出文件吗?我们所有自动引入的 API 都生成了记录在这个文件,你只需要将它写入 ESLint 配置的 extends 中让 Lint 工具识别下就好了,如下

jsx
/* 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 ,这里直接给大家说,由于我们接下来要使用 Vue3CompositionAPI,那 Vue3 有几个可以直接在 <script setup> 中可用的全局 API,比如 definePropsdefineEmitsdefineExpose,如果你使用 TS,还会用到 withDefaults

那我们的 ESLint 默认是识别不了这些全局 API 的,此时需要向 ESlint 规则中添加需要辨认的全局变量。

ESLint 配置中的 globals 属性就是让项目在 lint 执行期间访问额外的全局变量,简单说就是开发者自定义的全局变量,我们依次加上这些属性就可以了。

jsx
/* 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,删掉它,所以就报红了:

https://qiniu.isboyjc.com/picgo/202209260004390.png

这就是因为我们没有去指定这个文件用 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: 亲测安装了 VSCodeBabel JavaScript 插件后, JS 代码末尾如果不加分号,会导致后续代码高亮、注释不正常,在 VSCode 中禁用这个插件就好了,高亮错误展示如下:

https://qiniu.isboyjc.com/picgo/202209260005104.png

配置完了 ESLint ,我们再来看Prettier,我这边配置了几个常用的,如下

json
{
  "semi": false,
  "singleQuote": true,
  "printWidth": 80,
  "trailingComma": "none",
  "arrowParens": "avoid",
  "tabWidth": 2
}
  • semi 代码结尾是否加分号
  • singleQuote 是否使用单引号
  • printWidth 超过多少字符强制换行
  • trailingComma 代码末尾不需要逗号
  • arrowParens 单个参数的箭头函数不加括号 x => x
  • tabWidth 使用 n 个空格缩进

Prettier 配置就比较简单,按照文档和喜好在 .prettierrc.json 文件中配置就可以,注意配置的时候一定要和 ESLintrules 比较一下,这里是会发生冲突的地方,检测和格式化规则一定要一致。

配置 SVGIcon

项目中我们多多少少会用到一些图标,而目前 SVG 图标是主流,所以我们配置下 SVG 图标。

我们这里还用到 antFu 大佬写的 unplugin-icons 插件,它基于 iconify 图标库支持按需访问上万种图标,当然,我们不使用图标库也是可以的,也可以配置自定义一些 SVG 图标。

同时,因为此插件和上面自动引入的两个插件都是 antFu 大佬写的,所以,我们也可以直接为引入的 Icon 配置自动引入对应图标组件,很方便。

安装插件

bash
npm i -D unplugin-icons

# or

pnpm add unplugin-icons -D

配置插件

配置之前,我们先在 src/assets 文件夹下创建一个 svg 文件夹,然后在其下面新建两个文件夹,一个叫 home 一个叫 user,在下面随便放 2 个 svg 文件,我们下面会给大家简单演示怎么配置和使用自定义的 SVG 图标集合。

这块详细配置相关的就不给大家讲了,因为文章 Vue3!ElementPlus!更加优雅的使用Icon 中已经详细说过了,大家可以先去看看这便文章再来看这块,我这边再写的话比较浪费时间。

整个的配置如下所示:

jsx

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

随便挑几个复制一下名字

https://qiniu.isboyjc.com/picgo/202209260005206.png

然后我们在 HomePage 页面中直接去按规则写组件就行(我们上面配置了自动引入的 icon 组件前缀必须写 <icon- ),那我们使用起来如下:

html
<!-- ep:alarm-clock  改成  ep-alarm-clock -->
<icon-ep-alarm-clock class=""/>

如上,我们 copy 名字就可以直接使用,项目运行时写完保存,项目就可以自动为我们下载这个集合的图标并识别解析这个格式的 icon 组件

https://qiniu.isboyjc.com/picgo/202209260006339.png

使用自定义 Icon

那如何使用我们自定义的图标集,比如上面我们配置中自定义的 home、user 集合,我们在 home 文件夹下放了一个 copy.svg 还有一个 download.svg 文件,使用如下

html
<!-- 相当于 home:xxx,xxx就是home文件夹下的图标文件名 -->
<icon-home-copy class=""/>
<icon-home-download class=""/>

同样,自定义的图标我们也写在 在 HomePage 页面中。

最后我们来看下目前 HomePage 页面代码

html
<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>

最终效果:

https://qiniu.isboyjc.com/picgo/202209260006250.png

安装 VSCode 插件 Iconify IntelliSense

这是 iconify 图标库的 VSCode 插件,VSCode 中搜索插件:

jsx
Iconify IntelliSense

// or 搜索插件ID
****
antfu.iconify

安装之后,我们在使用图标库内图标时,直接可以在 VSCode 中预览到图标,超级方便,如下

https://qiniu.isboyjc.com/picgo/202209260007436.png

图标样式的话你可以直接在 icon 组件中添加,OK,图标也好了!

Styles 公共样式管理、初始化样式

接下来我们简单做一下 CSS 公共样式的处理,我们在项目 src 目录下新增一个 styles 文件夹,此文件夹我们后期可以放一些公共的样式文件。

大家都知道,HTML 标签是有默认样式的,一般我们在写项目时都会直接清除掉这个默认样式,也就是做个重置。

那相较于 Eric Merer 原版的清楚样式文件,Normalize.css 它在默认的 HTML 元素样式上提供了跨浏览器的高度一致性,是一种现代的、为 HTML5 准备的优质替代方案,所以我们直接使用它就好了。

下载 Normalize.cssStyles 文件夹下,当然你也可以直接 npm 安装它,不过我比较喜欢直接下载下来这个文件。

下载下来之后直接在 main.js 最上面引入一下就行了,如下

jsx
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 CSSWindi CSSBootstrapTachyon 等的通用超集,如果你习惯这些框架,依旧可以按照熟悉的方式写,无缝衔接。

不用不知道,用了是真香啊,当然,肯定是有人不喜欢原子化 CSS 的,不喜欢不用,毕竟就是写 CSS 的方式不一样而已,我这边也不会做很多配置,你甚至可以用原生 CSS

安装

bash
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 个包

配置

还是在 Vite 插件配置中,也就是 vite.config.js 文件中配置

jsx
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 中一下 UnoCSScss 文件:

jsx
import { createApp } from 'vue'
import { createPinia } from 'pinia'

import '@/styles/normalize.css'

// 导入Unocss样式
import 'uno.css'

// ...

基础使用如下:

html
<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 样式去搜就可以了,如下:

https://qiniu.isboyjc.com/picgo/202209260007012.png

当然,我是不建议你这么做的,因为个人觉得不太好用,感觉好像不太完善,既然我们用的预设支持 Tailwind / WindiCSS ,所以,我建议大家直接去看看这两个文档,了解一个大概,按照这两个东西的写法来就可以,有啥不会的去这两个的文档里搜

那推荐最好是直接去看 WindiCSS 使用文档,因为作者是一个人嘛,如下,直接搜对应 CSS 看怎样使用就可以了

https://qiniu.isboyjc.com/picgo/202209260007473.png

然后在项目中直接使用就行了,真丫机智!!!

我们上面安装了 @unocss/preset-attributify 属性预设,所以我们也可以使用属性模式,可以将实用程序分为多个属性,这样写:

html
<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

html
<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 扩展中搜索:

jsx
UnoCSS

// or 搜索ID 

antfu.unocss

安装之后,就可以在编辑器中看到我们写的原子化对应的 CSS 样式了,如下:

https://qiniu.isboyjc.com/picgo/202209260008497.png

当然 UnoCSS 还有很多功能,这里不过多描述了,有兴趣文档看一看,后面写项目的过程中也会逐步的去介绍。

如果我们是刚开始使用,肯定会不习惯,但是,只要坚持几天,直接真香警告!毕竟,一切都是为了简单,一切都是为了省事儿,一切都为了少写代码!!!

Utils、Hooks、API 管理

我们在项目 src 目录下添加一个 utils 文件夹,此文件夹用于存放我们项目中用到的一些公共方法文件。

同样的,我们在项目 src 目录下添加一个 hooks 文件夹,此文件夹用于存放我们项目中用到的一些 hooks 文件,因为我们用 Vue3CompsitionAPI,后面用多了自然会有很多 hooks 文件,针对一些公用的,我们统一管理在此文件夹下。

平常我们做项目,一般和请求相关的文件都统一放在一个文件夹下,所以我们在项目 src 目录下添加一个 api 文件夹,用于存放和请求相关的文件,因为项目性质,所以我们应该暂时用不到请求后端接口,那这边 api 文件的一些配置以及 axios 的封装甚至 API Mock 配置这里都先不展开说了,回头我会写好这块代码提交到 GitHub ,后面有需要的话会写一篇 axios 封装相关的文章来单独介绍这块。

其他 Vite 配置

先来说环境的配置,先放个官方文档压压惊

OK,我们在 env 目录下新建下面 3 个文件

  • .env 所有模式下都会加载
  • .env.development 只在开发模式下加载
  • .env.production 只在生产模式下加载

.env 文件在所有模式下都会加载,所以这里我们可以写一些所有环境都通用的环境变量,如下:

bash
# 所有环境都会加载

# 项目标识代码
VITE_APP_CODE="TOOLSDOG"

# 项目名
VITE_APP_NAME="工具狗"

# 项目描述
VITE_APP_DESCRIPTION="你用的到的工具,这里都有!"

注意,我们在 Vite 中配置的环境变量默认只有以 VITE_ 开头的配置,才会暴露给客户端,我们才能在项目中获取到。

开发模式 .env.development 配置

bash
# 开发环境加载

# 环境标识
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 脚本命令如下

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"
  }
}

上面脚本中我们给启动命令搞成了

json
npm run serve

// or

pnpm serve

serve 脚本命令配置中,我们还传了一个 mode,其实这个 mode 就是对应我们的环境文件 .env.[mode]

开发环境默认 mode 就是 development,生产环境默认 mode 就是 development,所以脚本命令这里我不传 mode 也可以,但是如果大家把开发环境文件由 .env.development 改成 .env.dev,那脚本中 mode 就得传 —-mode devbuild 时也是一样的道理,如果有其他环境,那脚本命令传入对应的 mode 就可以了。

如果想要在 vite.config.js 文件中获取对应运行 mode 环境变量的配置,我们可以使用 viteloadEnv API

VitedefineConfig 方法也可以接收一个返回配置对象的回调函数,回调函数的参数里我们可以拿到运行脚本命令时传入的 mode 值,从而使用 loadEnv 方法去在 Vite 配置文件中获取对应 mode 下的环境变量,如下:

// export default defineConfig({}) 修改

export default defineConfig(({mode}) => {
	return {}
})

那其他一些基础配置就不一一说明了,大家可以直接看 Vite 官方文档

目前我们的配置如下:

jsx
/*
 * @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_ 开头的配置,所以在项目运行时,我们可以直接 import.meta.env.VITE_XXX 去查看配置,但是这样太麻烦了,所以我们写一个统一的配置文件去获取环境变量,包括项目后期的一些全局配置也可以写里面

项目 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 对象中写入就可以了,项目中使用起来的话如下:

jsx
import { getConfig, setConfig, resetConfig } from "@/config/config.js"

// 获取配置
getConfig("a")
getConfig("a.b")
getConfig("a.b.c")

// 动态设置
setConfig({ ... })

// 重置配置
resetConfig()

配置 VSCode 推荐扩展插件

打开项目根目录的 .vscode/extensions.json 文件如下:

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

这是创建项目时默认存在的扩展插件推荐配置,此文件的作用上面也介绍过了,就是个扩展推荐,数组里是 VSCode 的扩展插件 ID,你在根目录打开此项目时,如果编辑器没有安装这两个插件,VSCode 就会在右下角自动提示你去安装插件。

https://qiniu.isboyjc.com/picgo/202209260008692.png

那我们没有使用 TS 直接干掉 vue.vscode-typescript-vue-plugin 这一项。

我们想要添加一个插件到推荐里,可以复制插件 ID 写到此配置中,也可以直接点击添加到工作区建议,如下:

https://qiniu.isboyjc.com/picgo/202209260009649.png

回顾一下上文,我们一共装了 2 个插件

bash
Iconify IntelliSense
UnoCSS

那我这里把这两个插件的 ID (注意是 ID 不是插件名字)都写进推荐列表里了:

json
{
  "recommendations": [
    "vue.volar",
    "antfu.iconify",
    "antfu.unocss"
  ]
}

大家如果克隆项目使用 VSCode 启动,就会提示你去安装这些插件了。

不正经的前端