为脚手架(按需)添加开发常用的插件/配置
一、css相关配置
1. 添加全局样式变量
定义一些全局的主题样式变量,统一各页面样式时十分有用
// vite.config.ts
export default defineConfig({
// ...其他配置
css: {
preprocessorOptions: {
scss: {
// ...其他配置
// 添加额外的代码变量,可以全局引用。如果在这里添加了样式,那么样式在最终编译会重复生成
additionalData: "$color: red;",
// 变量名可以直接替换为文件导入,比如:
// additionalData: @import '@/styles/theme.scss';
}
}
},
// ..其他配置
})
2. 自动填加浏览器前缀
可以避免手动处理浏览器兼容问题,专注在样式开发工作上
-
安装
autoprefixer:autoprefixer是添加前缀(vite已经内置postcss,无需再单独下载)pnpm add autoprefixer -D -
在
vite.config.ts中配置// vite.config.ts import autoprefixer from 'autoprefixer'; export default defineConfig({ // ...其他配置 css: { // ..其他配置 postcss: { plugins: [ autoprefixer({ overrideBrowserslist: [ 'Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8' ], grid: true }) ] } }, // ..其他配置 })
3. 将类名处理成哈希(可选)
避免类名重复导致全局样式冲突
vue的
scoped也能一定程度上解决这个问题。简单项目建议直接使用scoped,复杂样式管理可以使用 CSS modules 文件或者通过postcss-modules自定义将类名处理成哈希值。
CSS modules也是基于postcss-modules实现的,配置 CSS modules 的行为,选项将被传递给postcss-modules。任何以.module.scss为后缀名的文件都被认为是一个导入CSS Module这样的文件会返回一个相应的模块对象
- 下面简单写一下
CSS Module的使用,postcss-modules可以自行了解,也差不多。
<script setup lang="ts">
import { useCssModule } from 'vue';
const style = useCssModule();
</script>
<style module lang="scss">
/** 也可以把样式写在以 `.module.scss` 为后缀名的文件中再导入使用 **/
// @import '**/**.module.scss';
.example {
text-decoration: dashed;
display: flex;
flex-flow: row nowrap;
justify-content: center;
}
</style>
- 可以在
vite.config.ts中配置生成规则:
// vite.config.ts
export default defineConfig({
// ...其他配置
css: {
// ..其他配置
modules: {
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
},
// ..其他配置
})
4. 将px转rem(可选)
自适应不同屏幕
在自适应不同屏幕时会发挥作用,尤其是在还原ui设计图时。如果你在项目中使用了UI框架,请确认框架组件是否支持
pxtorem
- 下载
postcss-pxtorem插件
pnpm add postcss-pxtorem -D
vite.config.ts中配置
// vite.config.ts
// @ts-expect-error postcss-pxtorem还没有官方的ts包
import pxtorem from 'postcss-pxtorem';
export default defineConfig({
// ...其他配置
css: {
// ..其他配置
postcss: {
plugins: [
pxtorem({
// 1rem = 14px
rootValue: 14,
// 需要转换的属性,除 border 外所有px 转 rem
propList: ['*', '!border'],
// 排除node_modules
exclude: '/node_modules/*',
// 是否要在媒体查询中转换px
mediaQuery: false,
// 设置要转换的最小像素值
minPixelValue: 2
})
]
}
},
// ..其他配置
})
- 添加 remUnit 适配代码
// remUnit.ts
function setRem(): void {
const designWidth: number = 1920 // 设计稿宽度
const html: HTMLElement = document.documentElement
const width: number = html.clientWidth
html.style.fontSize = `${(width / designWidth * 14)}px`
}
// 初始化和窗口变化时调用
setRem()
window.addEventListener('resize', setRem)
export default setRem
// 这里用gpt将上面的代码改写成了一段更严谨的代码,随便用哪个都可以
// remUnit.ts
class RemAdapter {
private designWidth: number
private rootValue: number
constructor(designWidth: number = 1920, rootValue: number = 14) {
this.designWidth = designWidth
this.rootValue = rootValue
this.init()
}
private calculateRem(): void {
const html: HTMLElement = document.documentElement
const width: number = html.clientWidth
html.style.fontSize = `${(width / this.designWidth * this.rootValue)}px`
}
private init(): void {
this.calculateRem()
window.addEventListener('resize', this.calculateRem.bind(this))
}
}
// 创建实例
new RemAdapter()
export default RemAdapter
- 在
main.ts中引入:
import './remUnit'
5. tailwindcss v3.x(可选)
可以当做postcss的插件使用。
- 下载和初始化
pnpm add -D tailwindcss@3
// 下载完成后初始化
npx tailwindcss init
- 修改配置文件
// `tailwind.config.ts`
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}"
],
theme: {
extend: {},
},
plugins: [],
}
// vite.config.ts
import tailwindcss from 'tailwindcss';
export default defineConfig({
// ...其他配置
css: {
// ..其他配置
postcss: {
plugins: [
// ...其他配置
tailwindcss()
]
}
},
// ..其他配置
})
- 添加 Tailwind 的基础指令,并在main.ts中引入
/* src/tailwind.css */*
@tailwind base;
@tailwind components;
@tailwind utilities;
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import './tailwind.css'; // 包含 Tailwind 指令的 CSS 文件
import './style.scss'
createApp(App).mount('#app')
6. tailwindcss v4.0(可选)
可以当做 vite 的插件使用。版本4集成了
autoprefixer,所以如果使用这个版本,需要删除autoprefixer插件的使用
- 下载和初始化
pnpm add tailwindcss @tailwindcss/vite
- 修改配置文件
// vite.config.ts
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
tailwindcss(),
],
})
- 添加 Tailwind 的基础指令,并在main.ts中引入
/* src/tailwind.css */
@import "tailwindcss";
/* main.ts */
import { createApp } from 'vue'
import App from './App.vue'
import './tailwind.css'; // 包含 Tailwind 指令的 CSS 文件
import './style.scss'
createApp(App).mount('#app')
二、静态资源处理
静态资源?
静态资源本身并不是标准意义上的模块,因此对它们的处理和普通的代码是需要区别对待的。一方面我们需要解决资源加载的问题,对 Vite 来说就是如何将静态资源解析并加载为一个 ES 模块的问题;另一方面在生产环境下我们还需要考虑静态资源的部署问题、体积问题、网络性能问题,并采取相应的方案来进行优化。
1. 图片压缩
图片资源的体积往往是项目产物体积的大头,如果能尽可能精简图片的体积,那么对项目整体打包产物体积的优化将会是非常明显的
之前使用的是
vite-plugin-imagemin插件,star较多,但是gifsicle这个依赖在国区总是遇到下载问题,所以后面换成了vite-plugin-minipic这个插件,star较少,功能不太全面,但够用,作者活跃度挺高的,而且压缩速度比vite-plugin-imagemin快。
vite-plugin-imagemin是基于sharp实现的,sharp安装时也就遇到一些问题,但是作者都给出了清晰的解决方案:github.com/60late/vite…
- 安装
pnpm add vite-plugin-imagemin -D
vite.config.ts中配置
import minipic from 'vite-plugin-minipic';
export defaul defineConfig({
plugins:[
// 其他配置
// minipic(), // 基础用法
minipic({ // 自定义用法
sharpOptions: {
avif: {
quality: 75 // 压缩质量,默认是 50,和 sharp 的 API 一致,其他参数请查阅文档
},
jpeg: {
quality: 75
},
jpg: {
quality: 75
},
png: {
quality: 75
},
webp: {
quality: 75
},
gif: {}
},
convertPublic: false, // 不开启压缩 public文件夹下的图片,默认 true
cache: false, // 第一次压缩后会将压缩文件存入磁盘:不开启。默认 true
limitInputPixels: false // 至少是 v1.3.1。
/** ⚠️注意:
* 当 sharp 解析 GIF 时,它会尝试把所有帧解码合并为一个大图(称为“pages stacking”),尺寸变成:width x height x pages(帧数),当像素总量(width × height × frames)超过 sharp 的 limitInputPixels 限制(默认 268402689),就会报错。
* 当 sharp 处理较大 gif 时需要配合插件提供limitInputPixels选项,默认为true,如果超出sharp的最大限制,那么就跳过这张gif的压缩。如果该值设置为false,那么就允许用户强制压缩当前gif图片,但是如果机器内存不够会容易造成OOM。
* 另外,我用了一张 6.26M的 gif测试,压缩率只有1.15%(用了467.83Kb的小图测试,压缩率为6.46%,图片越大压缩率越低)效果不是很好;
* but!!如果选择跳过压缩...emm,这个大 gif打包之后是 0kb,应该是编码错误了,不知道怎么解决。所以系统内还是不要用大的 gif 图😅
**/
// 其他参数请查阅文档
}),
]
})
压缩前(图 1)后(图 2)对比:
2. 以组件的形式加载Svg
svg可以作为静态图片加载,但是如果作为一个组件引用的话,可以很方便的修改svg的各种属性,且比img标签等引入方式更加优雅(如果你想在项目中使用雪碧图优化svg,可以先跳过这一小节)
- 安装
pnpm add vite-svg-loader -D
- 在
vite.config.ts中配置
import svgLoader from 'vite-svg-loader'
export default defineConfig({
plugins: [
// 其他配置
svgLoader()
]
})
-
使用
这个插件提供几种显示导入:
url、raw、componentimport svg from './my-icon.svg?url'import svg from './my-icon.svg?raw'import svg from './my-icon.svg?component'
url: 可以使用?url后缀将 SVG 作为 URL 导入
raw: 可以使用?raw后缀将 SVG 作为 字符串 导入
component: 可以使用?component后缀将 SVG 作为 vue组件 导入**当未提供显示参数时,SVG 将默认作为 Vue 组件导入。**可以使用
defaultImport配置设置来更改此设置,具体设置可参考官方说明
3. 动态引入静态资源
使用动态路径访问
assets资源在前面,我们已经为
./src/assets路径下的静态资源设置了别名,在加载图片时就不用手动寻址。比如:<img src="@assets/images/code-icon.png" />。 但是在使用动态路径访问assets资源时会导致打包后路径错误,找不到图片文件。所以我这里在src/utils/tools.ts中添加了一个getAssetsFile方法,用于使用动态路径获取assets下的静态资源,代码如下:
/**
* @description 获取assets静态资源路径,在使用动态路径时很有用
* */
export const getAssetsFile = (url: string) => {
return new URL(`../assets/${url}`, import.meta.url).href;
};
4. 雪碧图优化(可选)
svg 文件一般体积不大,但 Vite 中对于 svg 文件会始终打包成单文件,当项目中使用了较多的svg图标时,会导致网络请求增加,大量的 HTTP 请求会导致网络解析耗时变长,页面加载性能直接受到影响。
(不过这个问题只在http1.1中受影响,因为http2的多路复用设计可以解决大量http请求导致的网络加载性能问题。如果项目依旧使用的是http1.1并且存在较多svg,建议使用雪碧图技术进行优化)
- 安装
pnpm add -D @spiriit/vite-plugin-svg-spritemap
- 在
vite.config.ts中配置
// vite.config.js / vite.config.ts
import VitePluginSvgSpritemap from '@spiriit/vite-plugin-svg-spritemap'
export default {
plugins: [VitePluginSvgSpritemap('./src/icons/*.svg', {
prefix: 'sprites_icon-', // 前缀
output: {
view: true,
use: true
}
})]
}
- 使用
这个插件也支持把svg导入为vue组件使用,如果项目中需要雪碧图优化,那么上面第2点提到的
以组件的形式加载Svg的插件就不用下了,用这一个插件就可以了。
// 该插件支持两种显示引入后缀用于转化组件,也可以不适用后缀,但样式会不太一样:
<script setup lang="ts">
import ViteView from '@assets/icons/vite.svg?view'; // 转化为vue组件,适配大小
import ViteUse from '@assets/icons/vite.svg?use'; // 直接使用,原始大小
</script>
<template>
<ViteView />
<ViteUse />
</template>
- 解决import无法找到模块的报错
官方给的解决方案是在 vite-env.d.ts中添加下面这句代码,如果你添加了之后import语句还在报错,请重启编辑器
/// <reference types="@spiriit/vite-plugin-svg-spritemap/client" />
5. 单文件 or 内联?
在 Vite 中,所有的静态资源都有两种构建方式,一种是打包成一个单文件,另一种是通过 base64 编码的格式内嵌到代码中。
这两种方案到底应该如何来选择呢?
对于比较小的资源,适合内联到代码中,一方面对代码体积的影响很小,另一方面可以减少不必要的网络请求,优化网络性能。而对于比较大的资源,就推荐单独打包成一个文件,而不是内联了,否则可能导致上 MB 的 base64 字符串内嵌到代码中,导致代码体积瞬间庞大,页面加载性能直线下降。
Vite 中内置的优化方案是下面这样的:
- 如果静态资源体积 >= 4KB,则提取成单独的文件
- 如果静态资源体积 < 4KB,则作为 base64 格式的字符串内联
上述的4 KB即为提取成单文件的临界值,当然,这个临界值你可以通过build.assetsInlineLimit自行配置,如下代码所示:
// vite.config.ts
{
build: {
// 8 KB
assetsInlineLimit: 8 * 1024
}
}
svg 格式的文件不受这个临时值的影响,始终会打包成单独的文件,因为它和普通格式的图片不一样,需要动态设置一些属性,所以采用了上面的一些优化措施
三、安装pinia
应该在什么时候使用 Store?
一个 Store 应该包含可以在整个应用中访问的数据。这包括在许多地方使用的数据,例如显示在导航栏中的用户信息,以及需要通过页面保存的数据,例如一个非常复杂的多步骤表单。
另一方面,你应该避免在 Store 中引入那些原本可以在组件中保存的本地数据,例如,一个元素在页面中的可见性。
并非所有的应用都需要访问全局状态,但如果你的应用确实需要一个全局状态,那 Pinia 将使你的开发过程更轻松。 (不要) 滥用 store
1. 安装
// 安装pinia和pinia持久化插件
pnpm add pinia pinia-plugin-persistedstate
2. 配置
在/src下新建文件夹stores,创建index.ts文件如下:
import { createPersistedState } from 'pinia-plugin-persistedstate';
const pinia = createPinia();
pinia.use(
createPersistedState({
// 全局 key 配置接受传入 Store key 的函数,并返回一个新的 storage 密钥。
key: (id: string) => `__persist__${id}`,
// 该配置将会使所有 Store 持久化存储,且必须配置 persist: false 显式禁用持久化。
auto: true
})
);
export default pinia;
3. vs code配置
- 为编辑器添加快速生成
Pinia store模板代码,下面定义了两种Pinia store的模板:
1. 打开vs code
2. 按下 Ctrl+Shift+P (Windows) 或 Cmd+Shift+P (Mac) 打开命令面板
3. 输入 "Configure User Snippets"
4. 选择 "New Global Snippets file..." 或者选择特定的语言(如 "typescript.json")
5. 如果选择新建全局片段,输入文件名(如 "pinia")
在自动创建好的文件中粘贴一下代码:
{
"Pinia Options Store Boilerplate": {
"scope": "javascript,typescript",
"prefix": "pinia-options",
"body": [
"import { defineStore, acceptHMRUpdate } from 'pinia'",
"",
"export const use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store = defineStore('$TM_FILENAME_BASE', {",
" state: () => ({",
" $0",
" }),",
" getters: {},",
" actions: {},",
"})",
"",
"if (import.meta.hot) {",
" import.meta.hot.accept(acceptHMRUpdate(use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store, import.meta.hot))",
"}",
""
],
"description": "Bootstrap the code needed for a Vue.js Pinia Options Store file"
},
"Pinia Setup Store Boilerplate": {
"scope": "javascript,typescript",
"prefix": "pinia-setup",
"body": [
"import { defineStore, acceptHMRUpdate } from 'pinia'",
"",
"export const use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store = defineStore('$TM_FILENAME_BASE', () => {",
" $0",
" return {}",
"})",
"",
"if (import.meta.hot) {",
" import.meta.hot.accept(acceptHMRUpdate(use${TM_FILENAME_BASE/^(.*)$/${1:/pascalcase}/}Store, import.meta.hot))",
"}",
""
],
"description": "Bootstrap the code needed for a Vue.js Pinia Setup Store file"
}
}
配置完成后使用方法:
1. 创建一个新的 .ts 文件
2. 在文件中输入 `pinia-options` 或 `pinia-setup`
3. 按下 Tab 键,代码片段将自动展开
下面的代码是我使用`pinia-setup`生成的:
import { defineStore, acceptHMRUpdate } from 'pinia'
export const useTestStore = defineStore('test', () => {
return {}
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useTestStore, import.meta.hot))
}
- 这里附带贴一下vue的快速生成代码模板:
1. 打开vs code
2. 按下 Ctrl+Shift+P (Windows) 或 Cmd+Shift+P (Mac) 打开命令面板
3. 输入 "Configure User Snippets"
4. 选择'vue'
{
"Vue3 Template": {
"prefix": "v3",
"body": [
"<template>",
"\t<div>",
"\t\t$1",
"\t</div>",
"</template>",
"",
"<script setup>",
"import { ref } from 'vue'",
"",
"$2",
"</script>",
"",
"<style scoped>",
"$3",
"</style>"
],
"description": "Vue 3 基础模板",
},
"Vue3-ts-scss Template": {
"prefix": "v3ts",
"body": [
"<template>",
"\t<div>",
"\t\t$1",
"\t</div>",
"</template>",
"",
"<script lang='ts' setup>",
"",
"$2",
"</script>",
"",
"<style lang='scss' scoped>",
"$3",
"</style>"
],
"description": "Vue 3 ts scss模板"
},
"Vue3 Watch": {
"prefix": "v3watch",
"body": [
"watch(${1:source}, (newValue, oldValue) => {",
"\t${2:// 处理逻辑}",
"})"
],
"description": "Vue 3 侦听器"
},
"Vue3 Props": {
"prefix": "v3props",
"body": [
"const props = defineProps({",
"\t${1:propName}: {",
"\t\ttype: ${2:String},",
"\t\trequired: ${3:true},",
"\t\tdefault: ${4:null}",
"\t}",
"})"
],
"description": "Vue 3 组件 props 定义"
},
"Vue3 Emits": {
"prefix": "v3emits",
"body": [
"const emit = defineEmits(['${1:eventName}'])"
],
"description": "Vue 3 组件 emits 定义"
}
}
四、安装router
1. 安装
pnpm add vue-router@4
2. 添加路由
这一步可以按自己的习惯创建文件,下面是代码示例:
// src/router/modules/home/index.ts
import { RouteRecordRaw } from 'vue-router';
const homeRoutes: RouteRecordRaw[] = [
{
path: '/',
name: 'HelloWorld',
component: () => import('@/components/HelloWorld.vue'),
meta: {
title: 'Hello World',
keepAlive: true
}
}
];
export default homeRoutes;
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import homeRoutes from './modules/home';
const router = createRouter({
history: createWebHistory(),
routes: [...homeRoutes]
});
export default router;
// src/main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
createApp(App).use(router).mount('#app');
// src/App.vue
<template>
<router-view></router-view>
</template>
五、自动生成路由和类型(可选)
默认情况下,该插件检查
src/pages文件夹中是否有任何.vue文件,并根据文件名生成相应的路由结构。这样,您在向应用程序添加路由时不再需要维护routes数组,**只需将新的.vue组件添加到路由文件夹中,然后让这个插件完成剩下的工作!**对于不同复杂度的项目,自动生成路由有不同的优缺点,按需使用。
1. 安装
pnpm add -D unplugin-vue-router
2. 配置
// vite.config.ts
import VueRouter from 'unplugin-vue-router/vite'
export default defineConfig({
plugins: [
VueRouter({
routesFolder: 'src/views',
dts: 'types/typed-router.d.ts'
}),
// ⚠️ Vue must be placed after VueRouter()
Vue(),
],
})
// tsconfig.json
{
"include":[
// other files...
"types/typed-router.d.ts"
],
"compilerOptions":{
// ...
"moduleResolution": "Bundler",
// ...
}
}
- 如果用了自动导入,那么
import中的'vue-router'需要替换为VueRouterAutoImports:
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite';
import { VueRouterAutoImports } from 'unplugin-vue-router';
export default defineConfig(({ mode }) => {
return {
pulgins: [
AutoImport({
// imports: ['vue', 'vue-router', 'pinia']修改为:
imports: ['vue', VueRouterAutoImports, 'pinia']
// ...其他配置
});
]
}
});
3. 创建
使用这个插件后页面组件需要按照指定规则创建
-
src/views文件夹中添加一个index.vue组件。这将在/呈现为首页。 -
如果页面由动态路由链接,那么文件名应该写为
[xx].vue,更多规则可以参看文档,举例:/: ->index.vue/about: ->about.vue/users: ->users/index.vue/users/:id: ->users/[id].vue,id就是路由参数/users/[[id]].vue: ->/users/:id?,id是可选参数/users/[slugs]+.vue: ->/users/:slugs+,slugs是可重复参数/pages/[...path].vue: ->/:path(.*),可捕获所有路由,可用于添加404/pages/index@aux.vue: -> 将定义为命名视图- 注意:
index.vue文件名必须全部小写
-
/src/main.ts或者router.ts中导入插件
import { routes } from 'vue-router/auto-routes'
const router = createRouter({ history: createWebHistory(), routes, })
4. 初始化
- 添加插件后,启动开发服务器,会在
typed-router.d.ts生成类型的第一个版本 - 在
vite-env.d.ts中添加类型,避免ts报错
/// <reference types="unplugin-vue-router/client" />
unplugin-vue-router将添加一个虚拟vue-router/auto模块,该模块从vue-router导出所有内容,并从unplugin-vue-router/runtime导出一些额外功能。建议避免在新项目中使用
vue-router/auto。保留它是为了与使用它的现有项目兼容,并且将来可能会被删除。您可以通过将此设置添加到
.vscode/settings.json来从 VSCode 导入建议中排除vue-router/auto:
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.preferences.autoImportFileExcludePatterns": ["vue-router/auto$"]
}
六、添加页面跳转进度条
1. 安装
pnpm add nprogress
pnpm add @types/nprogress -D
2. 封装
// 新建文件:/src/utils/nporgress.ts
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
//全局进度条的配置
NProgress.configure({
easing: 'ease', // 动画方式
speed: 1000, // 递增进度条的速度
showSpinner: false, // 是否显示加载ico
trickleSpeed: 200, // 自动递增间隔
minimum: 0.3, // 更改启动时使用的最小百分比
parent: 'body', //指定进度条的父容器
})
// 打开进度条
export const start = () => {
NProgress.start()
}
// 关闭进度条
export const close = () => {
NProgress.done()
}