likes
comments
collection
share

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

作者站长头像
站长
· 阅读数 8

前言

小希这次带来了进阶版的Vue3 + Vite项目框架的封装搭建,基础版的包括企业级项目规范以及基础配置在上篇文章,小白或者有兴趣的朋友可以先看看

本文主要的切入点有

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

搭建过程

多入口打包

一般情况下,项目开发只有一个入口,只需要配置一个入口,一个项目

但有时多个同业务、同类型的项目,有很多可以复用的业务,组件,工具类等,就可以放在同一个代码库里进行维护,不用新建多个代码库

每个项目都有自己独立的入口,可以独立打包并进行部署,低耦合,不会相互影响,同时还可以复用相同的组件,业务等,可以大大地提高开发效率和后期的维护

调整项目结构目录

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

如上图所示,有两个项目,分别是app1,app2,每个项目都有自己独有main.ts入口文件,App.vue文件,以及路由,仓库pinia,组件等,同时也有共有的组件,utils工具类等

配置过程

package.json中,将下图单入口的配置

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

改为

{
  "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中配置:

/* 项目名称 */
//采用这种方式可以动态获取项目名称,当然,如果项目少可以手动配置
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]',
      }
    }
  }
})

测试项目的运行和打包构建

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

打包构建

pnpm build:app1
pnpm build:app2

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

自动化生成项目基础模版

基于多入口打包,也就是一个代码库同时维护多个同类型的项目情况下,可以通过配置实现自动化生成项目基础模板,这样,当需要在代码库新建一个新项目时,可以通过命令行快速创建

前置工具

inquirer

这个插件用来询问用户输入项目名称,这是一个比较在处理命令行交互比较常见的库

主要用于实现命令行交互式界面。帮助我们与用户进行交互式交流

它有几个特点:提供错误反馈,询问问题,解析输入,验证答案

详细可参考 命令行交互工具inquirer

安装

pnpm add  inquirer@^8.0.0 -S

配置过程

package.json里添加

"scripts": {
    "init-app": "node ./src/utils/initApp/index.ts"
}

当执行这个命令时,会自动去执行,在本地utils文件夹下的initApp文件里的js脚本,在src目录下会自动生成一个新的文件夹(项目)

在utils下新增initApp文件夹以及index.tstemlate

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

index.ts添加以下代码

#!/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()

temlate文件夹下新增项目所需要的文件目录,main.ts以及App.vue是必须的,因为它是独立的项目

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

使用

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

app3项目自动生成

持久化pinia仓库数据

数据存储在缓存(内存)中,优点读写更快,可以保存任意的js类型数据和对象,比如当我们刷新浏览器的时候,数据会丢失,所以需要实现pinia持久化

持久化插件pinia-plugin-persist

安装

pnpm add pinia-plugin-persist
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')

使用

// 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
pathsstate 中的字段名,按组打包储存

也可以自定义存储类型,更多具体配置戳pinia-plugin-persist插件官网地址

源码解析

核心是通过 store.$subscribe去监听仓库数据,当仓库数据发生变化时会触发回调,更改本地缓存数据,当刷新后就会从本地缓存取出相关的数据

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进度条

通过显示进度条的形式,来提高用户体验,可用在进入/离开路由时触发动画,也可在发接口时使用

安装

pnpm add nprogress -S

基本用法

只需调用start()done()即可控制进度条。

NProgress.start();
NProgress.done();

使用场景

切换路由

router.beforeEach((to, from, next) => {
  NProgress.start()
  next()
})
router.afterEach(() => {
  NProgress.done()
})

发请求时

// 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)

下面用到Autoprefixerpostcss-px-to-viewport这两个插件进行viewport适配

PostCSS 配置

安装

pnpm add postcss-px-to-viewport -D
pnpm add  autoprefixer -D

创建postcss.config.js并配置

// 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 + Vite项目框架进阶版,值得学习收藏【前端工程化】

封装搭建Vue3 + Vite项目框架进阶版,值得学习收藏【前端工程化】

参考文章

pinia-plugin-persist插件官网地址

题外话

希望这篇文章可以帮助到大家,产出不易,给个三连吧,小希与你一起努力,共同进步,一直努力就会成功!!!

转载自:https://juejin.cn/post/7327965216826032154
评论
请登录