前言
小希这次带来了进阶版 的Vue3 + Vite项目框架的封装搭建,基础版的包括企业级项目规范以及基础配置在上篇文章,小白或者有兴趣的朋友可以先看看
从0搭建封装Vue3 + Vite项目框架,值得学习 --- 项目规范,基础配置【前端工程化】
本文主要的切入点有
搭建过程
多入口打包
一般情况下,项目开发只有一个入口,只需要配置一个入口,一个项目
但有时多个同业务、同类型的项目,有很多可以复用的业务,组件,工具类等,就可以放在同一个代码库里进行维护,不用新建多个代码库
每个项目都有自己独立的入口 ,可以独立打包并进行部署,低耦合,不会相互影响,同时还可以复用相同的组件,业务等,可以大大地提高开发效率和后期的维护
调整项目结构目录
如上图所示,有两个项目,分别是app1,app2,每个项目都有自己独有 的main.ts
入口文件,App.vue
文件,以及路由,仓库pinia,组件等,同时也有共有的组件,utils工具类等
配置过程
在package.json
中,将下图单入口的配置
改为
json
{
"scripts": {
"dev:app1": "vite serve src/app1/ --config ./vite.config.ts",
"dev:app2": "vite serve src/app2/ --config ./vite.config.ts",
"build:app1": "vue-tsc && vite build",
"build:app2": "vue-tsc && vite build"
},
这样配置可实现项目的独立运行,独立打包
在vite.config.ts
中配置:
php
/* 项目名称 */
//采用这种方式可以动态获取项目名称,当然,如果项目少可以手动配置
let appName = process.env.npm_lifecycle_event
appName = appName.slice(appName.indexOf(':') + 1) //app1、app2
export default defineConfig({
root: `./src/${appName}/`,
build: {
rollupOptions: {
input: {
[appName]: path.resolve(__dirname, `src/${appName}/index.html`)
},
output: {
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
assetFileNames: '[ext]/[name]-[hash].[ext]',
}
}
}
})
测试项目的运行和打包构建
打包构建
pnpm build:app1
pnpm build:app2
自动化生成项目基础模版
基于多入口打包,也就是一个代码库同时维护多个同类型的项目情况下,可以通过配置实现自动化生成项目基础模板,这样,当需要在代码库新建一个新项目时,可以通过命令行快速创建
前置工具
inquirer
这个插件用来询问用户输入项目名称,这是一个比较在处理命令行交互比较常见的库
主要用于实现命令行交互式界面。帮助我们与用户进行交互式交流
它有几个特点:提供错误反馈,询问问题,解析输入,验证答案
详细可参考 命令行交互工具inquirer
安装
csharp
pnpm add inquirer@^8.0.0 -S
配置过程
在package.json
里添加
json
"scripts": {
"init-app": "node ./src/utils/initApp/index.ts"
}
当执行这个命令时,会自动去执行,在本地utils文件夹下的initApp文件里的js脚本,在src目录下会自动生成一个新的文件夹(项目)
在utils下新增initApp文件夹以及index.ts
和temlate
在index.ts
添加以下代码
javascript
#!/usr/bin/env node
console.log('您正在创建项目')
const path = require('path')
const fs = require('fs')
const inquirer = require('inquirer')
const stat = fs.stat
const targetDir = path.resolve(__dirname, './template')
//复制文件目录
const copyFile = (targetDir, resultDir) => {
// 读取文件、目录
fs.readdir(targetDir, function (err, paths) {
if (err) {
throw err
}
paths.forEach(function (p) {
const target = path.join(targetDir, '/', p)
const res = path.join(resultDir, '/', p)
let read
let write
stat(target, function (err, statsDta) {
if (err) {
throw err
}
if (statsDta.isFile()) {
read = fs.createReadStream(target)
write = fs.createWriteStream(res)
read.pipe(write)
} else if (statsDta.isDirectory()) {
fs.mkdir(res, function () {
copyFile(target, res)
})
}
})
})
})
}
const question = [
{
type: 'input',
name: 'name',
message: '请输入项目名称:'
}
]
const createProject = () => {
// 询问用户问题
inquirer
.prompt(question)
.then(({ name }) => {
// name 为输入的项目名称
name = name.trim()
if (!name) {
console.log('项目目录不能为空')
// 如果输入空,继续询问
createProject()
return false
}
// 目标路径,要放在module目录下
const resultDir = path.resolve(__dirname, '../../', name)
// fs.access()方法用于测试文件是否存在
fs.access(resultDir, function (err, data) {
if (err) {
// 创建文件
fs.mkdir(resultDir, function (err, data) {
if (err) {
throw err
}
// 复制模版文件
copyFile(targetDir, resultDir)
})
console.log(`${name} 项目已创建成功`)
} else {
console.log(`${name} 项目目录已存在,请输入其他名称`)
// 不存在,继续询问
createProject()
}
})
})
.catch((err) => {
console.log(err)
})
}
createProject()
注:此代码copy==> Vue3项目框架搭建封装,一次学习,终身受益【万字长文,满满干货】
在temlate
文件夹下新增项目所需要的文件目录,main.ts
以及App.vue
是必须的,因为它是独立的项目
使用
app3项目自动生成
持久化pinia仓库数据
数据存储在缓存(内存)中,优点读写更快,可以保存任意的js类型数据和对象,比如当我们刷新浏览器的时候,数据会丢失,所以需要实现pinia持久化
持久化插件pinia-plugin-persist
安装
csharp
pnpm add pinia-plugin-persist
javascript
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist'
const pinia = createPinia()
pinia.use(piniaPersist)
createApp({})
.use(pinia)
.mount('#app')
使用
typescript
// store/use-user-store.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('storeUser', {
state: () => {
return {
firstName: 'S',
lastName: 'L',
accessToken: 'xxxxxxxxxxxxx'
}
},
actions: {
setToken (value: string) {
this.accessToken = value
}
},
persist: {
enabled: true,
//这里可以单独给每个字段配置存储的形式sessionStorage/localStorage
//paths配置state里的字段,不同的数据采取不同的存储方式
strategies: [
{ storage: sessionStorage, paths: ['firstName', 'lastName'] },
{ storage: localStorage, paths: ['accessToken'] },
],
}
})
strategies
字段说明:
属性 | 描述 |
---|---|
key | 自定义存储的 key,默认是 store.$id |
storage | 可以指定localStorage/sessionStorage,或者自定义存储类型,默认为 sessionStorage |
paths | state 中的字段名,按组打包储存 |
也可以自定义存储类型,更多具体配置戳pinia-plugin-persist插件官网地址
源码解析
核心是通过 store.$subscribe去监听仓库数据,当仓库数据发生变化时会触发回调,更改本地缓存数据,当刷新后就会从本地缓存取出相关的数据
typescript
import { PiniaPluginContext } from 'pinia'
type Store = PiniaPluginContext['store']; //pinia插件上下文
type PartialState = Partial<Store['$state']>;
//调用函数将仓库数据存储到本地
export const updateStorage = (strategy: PersistStrategy, store: Store) => {
const storage = strategy.storage || sessionStorage //可以自定义存储类型,默认为sessionStorage
const storeKey = strategy.key || store.$id //可以自定义存储的 key,默认是 store.$id
//判断是否有配置paths,如果没有就缓存一整个仓库中的state
if (strategy.paths) {
//遍历paths里面的字段,并通过 store.$state[key]获取相应的数据
const partialState = strategy.paths.reduce((finalObj, key) => {
finalObj[key] = store.$state[key]
return finalObj
}, {} as PartialState)
//存储到本地
storage.setItem(storeKey, JSON.stringify(partialState))
} else {
storage.setItem(storeKey, JSON.stringify(store.$state))
}
}
export default ({ options, store }: PiniaPluginContext): void => {
//判断enabled是否为true
if (options.persist?.enabled) {
const defaultStrat: PersistStrategy[] = [{
key: store.$id,
storage: sessionStorage,
}]
const strategies = options.persist?.strategies?.length ? options.persist?.strategies : defaultStrat
strategies.forEach((strategy) => {
const storage = strategy.storage || sessionStorage
const storeKey = strategy.key || store.$id
//根据key判断是否在本地缓存中,如果在刷新后会从本地缓存中将数据赋给pinia仓库的state
const storageResult = storage.getItem(storeKey)
// 如果本地中存在同步数据,更新仓库state数据
//(比如浏览器刷新后会进行判断,如果有数据会赋值给pinia仓库的state,实现pinia持久化)
if (storageResult) {
store.$patch(JSON.parse(storageResult))
updateStorage(strategy, store)
}
})
//通过$subscribe监听state,仓库数据更改会触发回调同步更改本地数据
store.$subscribe(() => {
strategies.forEach((strategy) => {
updateStorage(strategy, store)
})
})
}
}
引入nprogess进度条
通过显示进度条的形式,来提高用户体验,可用在进入/离开路由时触发动画,也可在发接口时使用
安装
csharp
pnpm add nprogress -S
基本用法
只需调用start()
和done()
即可控制进度条。
ini
NProgress.start();
NProgress.done();
使用场景
切换路由
scss
router.beforeEach((to, from, next) => {
NProgress.start()
next()
})
router.afterEach(() => {
NProgress.done()
})
发请求时
javascript
// axios请求拦截器
axios.interceptors.request.use(
config => {
NProgress.start() // 设置加载进度条(开始..)
return config
},
error => {
return Promise.reject(error)
}
)
// axios响应拦截器
axios.interceptors.response.use(
function(response) {
NProgress.done() // 设置加载进度条(结束..)
return response
},
function(error) {
return Promise.reject(error)
}
)
其它详细配置请戳官网:ricostacruz.com/nprogress/
viewport 适配方案 - postCSS插件
介绍
PostCSS
是一种 JavaScript 工具,可将你的 CSS 代码转换为抽象语法树 (AST),然后提供 API(应用程序编程接口)用于使用 JavaScript 插件对其进行分析和修改。
Autoprefixer
主要功能是解析CSS并使用Can I Use中的值向CSS规则添加供应商前缀。以兼容各种浏览器,部分CSS属性需要加上不同的前缀以兼容不同的浏览器。通过配置Autoprefixer,自动为CSS属性添加对应浏览器的前缀。
postcss-px-to-viewport
用于将单位为 px 的尺寸转换为视口单位(vw, vh, vmin, vmax)
下面用到Autoprefixer
和postcss-px-to-viewport
这两个插件进行viewport适配
PostCSS 配置
安装
csharp
pnpm add postcss-px-to-viewport -D
pnpm add autoprefixer -D
创建postcss.config.js
并配置
javascript
// postcss.config.js
module.exports = () => {
return {
plugins: {
autoprefixer: {},
'postcss-px-to-viewport': {
unitToConvert: 'px', // 需要转换的单位,默认为"px"
viewportWidth: 1920, // 设计稿的视口宽度
unitPrecision: 5, // 单位转换后保留的精度
propList: ['*'], // 能转化为vw的属性列表
viewportUnit: 'vw', // 希望使用的视口单位
fontViewportUnit: 'vw', // 字体使用的视口单位
selectorBlackList: [], // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
mediaQuery: false, // 媒体查询里的单位是否需要转换单位
replace: true, // 是否直接更换属性值,而不添加备用属性
exclude: undefined, // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
include: undefined, // 如果设置了include,那将只有匹配到的文件才会被转换
landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
landscapeUnit: 'vw', // 横屏时使用的单位
landscapeWidth: 1920 // 横屏时使用的视口宽度
}
}
}
}
效果如下
不同视口宽度,界面会响应性变化
参考文章
Vue3项目框架搭建封装,一次学习,终身受益【万字长文,满满干货】
Vue 3 + TypeScript + Vite 多入口打包及部署
Vue移动端 / PC端适配解决方案:postcss-px-to-viewport
Pinia 持久化存储插件 pinia-plugin-persist 介绍及源码解读
题外话
希望这篇文章可以帮助到大家,产出不易,给个三连吧,小希与你一起努力,共同进步,一直努力就会成功!!!