【前端工程化最新版】(二)vite 项目周边技术接入,丰富项目使用和开发体验

221 阅读5分钟

🌻 前言

我们不介绍所有接入方式,只介绍推荐的接入方式。并且一步一步解决遇到的问题和周边扩展

本文将介绍 element-plus / uncoss / pinia / normalize 的 接入, 以及自动化导入ts带来的类型问题,由于eslint9 的升级,导致很多适配的插件,存在滞后性,本文也将用一些方案来解决这些适配问题

🫑 element-plus 接入

🍅 基本配置

官方链接element-plus.org/zh-CN/guide…

  1. 安装element-plus
pnpm add element-plus
  1. 按需导入element-plus 首先你需要安装unplugin-vue-components 和 unplugin-auto-import这两款插件
pnpm add -D unplugin-vue-components unplugin-auto-import
  1. 把下列代码插入到你的 Vite 或 Webpack 的配置文件中
// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  // ...
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

已经生效 image.png

👿 服务型组件带来的问题

  1. 我们为element添加一个消息提示看下效果
import { ElMessage } from 'element-plus'
const infoTip = () => {
  ElMessage('This is a message.')
}

<el-button type="info" @click="infoTip">info message</el-button>

image.png

样式丢失了? 为什么其他组件的样式没有丢失,唯独 <ELMessage> <ElNotification> 等这种弹框类型的组件,样式会丢失呢?

这是因为Element Plus 的服务型组件(如 MessageNotification)的实现机制与普通组件不同,因为它们是通过 JavaScript API 动态调用的,而不是通过模板标签直接使用的。这导致在按需加载时,服务型组件的样式需要额外处理。 通过document.body.appendChild 动态插入到 DOM 中

image.png 由于服务型组件,比如Message 是通过 JavaScript 动态创建的,不像 <el-button> 等普通组件是静态挂载在 Vue 的模板中。由于是动态挂载的,按需加载的工具(如 babel-plugin-component)不会自动引入它的样式。

  1. 解决方案: Element Plus 提供了基于 ES Module 的开箱即用的 Tree Shaking 功能。需要安装 unplugin-element-plus 来导入样式
pnpm i unplugin-element-plus

vite

// vite.config.ts
import { defineConfig } from 'vite'
import ElementPlus from 'unplugin-element-plus/vite'

export default defineConfig({
  // ...
  plugins: [ElementPlus({})],
})

image.png

🧪 改进优化

一般我们对于服务型组件,会放到实例上进行调用,做一个统一的封装

  1. 新建 plugins/element.ts
import type { App } from 'vue'

import { ElMessage, ElNotification, ElMessageBox } from 'element-plus'

export default (app: App) => {
  // 都放到组件的实例上了
  app.config.globalProperties.$message = ElMessage
  app.config.globalProperties.$notify = ElNotification
  app.config.globalProperties.$confirm = ElMessageBox.confirm
  app.config.globalProperties.$alert = ElMessageBox.alert
  app.config.globalProperties.$prompt = ElMessageBox.prompt
}

export type Size = 'default' | 'small' | 'large'

  1. 在main.ts 进行注册
import element from './plugins/element'
app.use(element)
  1. 在组件中进行使用

// 将服务型的组件,放到实例上调用
// import { ElMessage } from 'element-plus'
const { proxy } = getCurrentInstance()!

const infoTip = () => {
  // ElMessage('This is a message.')
  proxy?.$message('This is a message.')
}

此时会报错, image.png

  1. 声明element的类型 在 tsconfig.app.json 中添加如下配置
// tsconfig.json
{
  "compilerOptions": {
    // ...
    "types": ["element-plus/global"]
  }
}

image.png

🍅 element-plus 图标使用

  1. 插件安装
pnpm add @element-plus/icons-vue
  1. 在min.ts 导入
// 如果您正在使用CDN引入,请删除下面一行。
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

const app = createApp(App)
// 注册所有图标 / 也可以
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

🔗 自动导入组件库/方法 可阅读往期文章

juejin.cn/post/735129…

🌹 vue 自动化导入,ts 类型报错问题

🤔 想自动导入其他库/插件,注释掉肯定报错,那我们需要做哪些配置呢?

image.png

  1. 在vite.config.ts 添加要自动导入的插件/库
// vite.config.ts
AutoImport({
       // ... other optioins
      // api
      imports: ['vue', 'vue-router', 'pinia'],
      eslintrc: { enabled: true } // 给eslint生产的配置 只需要一次,
    }),
  1. tsconfig.app.json 添加ts文件配置
"types": ["element-plus/global", "./auto-imports.d.ts", "./components.d.ts"]
  1. 在eslint.config.js 中引入eslintrc-auto-import.json 做全局ts 配置
// eslint.config.js
// esModule  commonjs
// 1.
// import autoImport from "./.eslintrc-auto-import.json" with { type: "json" }

// 2.
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
const autoImport = require('./.eslintrc-auto-import.json')

// 3.fs.readFile
// 以上三种方式都可以,我们选择第二种方式加载 eslintrc-auto-import.json 
export default [
   //  ...other options
  // 2. 定义不同环境的全局变量
  {
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
        ...autoImport.globals // 新增
      }
    }
  },
]

ts 报错解决,

image.png

🍍 pinia 接入

  1. 插件安装
pnpm add pinia
  1. 全局注册
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')

🍅 pinia 基本使用

  1. 定义仓库
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const name = ref('Eduardo')
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, name, doubleCount, increment }
})
  1. 使用仓库
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'

const store = useCounterStore()
// `name` and `doubleCount` are reactive refs
// This will also extract refs for properties added by plugins
// but skip any action or non reactive (non ref/reactive) property
const { name, doubleCount } = storeToRefs(store)
// the increment action can just be destructured
const { increment } = store
</script>

🍅 pinia 持久化处理

  1. 插件安装
pnpm add pinia-plugin-persistedstate
  1. 全局注册
// main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia'
import element from './plugins/element'
// 注册持久化插件
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
const app = createApp(App)
pinia.use(piniaPluginPersistedstate)
app.use(pinia)
app.use(element)
app.mount('#app')

🍅 持久化基本使用

// counter-store.ts

import { defineStore } from 'pinia'
import { computed, ref, reactive } from 'vue'

export const useCounterStore = defineStore(
  'counter',
  () => {
    const count = ref(0)
    const minCount = ref(0)
    const obj = reactive({
      index: 0
    })
    // computed 出来的字段
    const doubleCount = computed(() => count.value * 2)
    function increment() {
      count.value++
      minCount.value++
      obj.index++
    }

    return { count, minCount, obj, doubleCount, increment }
  },
// 只需要新增这一行即可
  {
    persist: true
  }
)


⌚️ 持久化扩展
  • 简单配置
  {
    persist: true
  }
  • 指定字段持久化

默认情况,persist 会将所有显示声明ref/ reactive 都存储到localStorage 中,computed出来的字段,他是不会存储到内存中的

image.png

但是你也可以通过pick手动指定哪些字段可以存储的内存,以及storage指定存储位置

{
    persist: {
      pick: ['count', 'minCount'],
      storage: localStorage // or sessionStorage
    }
  }

image.png

🫑 uncoss 接入 unocss.dev/guide/

  1. 插件安装
pnpm add -D unocss
  1. vite 中基本配置
// vite.config.ts
import UnoCSS from 'unocss/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    UnoCSS(),
  ],
})
  1. main.ts 中 添加 virtual:uno.css
// main.ts
import 'virtual:uno.css'
  1. 新建 uno.config.ts 文件
// uno.config.ts
import { defineConfig } from 'unocss'

export default defineConfig({
  // ...UnoCSS options
})
  1. 使用 可以参考官网

unocss.dev/interactive…

🍅 扩展

  • 插件安装,可以智能提示

image.png

  • 里面还有很多预设,比如 可以用结合 sass 使用指令@apply, 可以直接使用属性的方式,而不是写在class 里,可以自行探索

image.png

🍅 自定义规则

// uno.config.ts
// 自定义规则
  shortcuts: {
    'flex-center': ['flex', 'justify-center', 'items-center']
  }

🐛 normalize 清除默认样式

  1. 插件安装
pnpm add normalize.css
  1. 在main.ts 中导入
// main.ts
import "normalize.css/normalize.css";

🍊 写在最后

如果您看到这里了,并且觉得这篇文章对您有所帮助,希望您能够点赞👍和收藏⭐支持一下作者🙇🙇🙇,感谢🍺🍺!如果文中有任何不准确之处,也欢迎您指正,共同进步。感谢您的阅读,期待您的点赞👍和收藏⭐!

本次项目仓库地址: github.com/github-lear…

后续会做成cli, 新项目直接cli 全套就有了

下一篇

  • 路由表以及动态路由实现