漫谈前端项目框架搭建

2023/10/06 posted in  工程化
Tags: 

前端框架搭建是每一个前端工程师都会遇到,特别是在一些小型团队内,没有专门的架构师,所以这项工作就落到一些普通的前端开发工程师手中,这时很多人可能会从网上去查找一些成熟的框架,虽然能够完成项目,但是有可能会存在一些问题:

  • 对这些框架不够了解,导致出现问题排查困难,后续升级维护困难。
  • 这类框架一般具有通用属性,并不是根据项目按需搭建,导致功能冗余或者改造成本高。
  • 多个项目多人负责,可能会选择不同的框架,导致标准不统一,协作困难,也难以形成团队技术沉淀。

所以,框架搭建是晋升高级前端工程师的必经之路,固有必要探探其究竟。本文不会带大家从编码层面教大家如何写代码,毕竟相关的文章网上比比皆是,本文会带你搞清整个流程和其中的一些关键环节。

何为软件架构

在开始整个流程前,我们先开了解一些何为软件架构。

软件架构是有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计。软件架构会包括软件组件、组件之间的关系,组件特性以及组件间关系的特性。软件架构可以和建筑物的架构相比拟。软件架构是构建计算机软件,开发系统以及计划进行的基础,可以列出开发团队需要完成的任务。

----wiki百科

简言之,软件架构这项工作的实质就是:如何将系统切分成组件,并安排好组件之间的排列关系,以及组件之间的相互通信的方式。目的是更好的对这些组件进行研发维护,可以让系统更容易理解、易于修改、方便维护、并能轻松部署。终极目标是最大化程序员的生产力,同时最小化系统的总运维成本。

当然,很多人可能会说:团队就我一个前端,老夫都是CV一把梭,去TM的架构,项目照样不是完成了。

其实,现在一些成熟的框架比如我们用的Vue,在用脚手架初始化项目后,整个项目架构已初具雏形了。所以开发一些简单的项目是没有什么问题;而且由于前段历史原因,架构这个词在前端领域可能关注度并不高。但是一些大型项目,或者你去看一些著名的开源库内部实现,就会发现,还是能看到软件架构设计的身影,比如MVC、MVVM等其实就是分层架构,重要性不言而喻。

由于本文主题不是讨论架构,就不再赘述,只需要明确一件事,我们搭建的框架目的是为了提升开发效率,保证开发质量。需要遵循一下几点:

  • 易用性:框架封装基础核心功能,开发功能需求时,无需关注复杂的技术架构,只需要关注业务编码工作。
  • 扩展性:各系统部分间保持高内聚低耦合,提供扩展接口,在扩展新功能时,不需要对架构进行改动,新功能不对旧功能产生影响。
  • 可维护性:系统划分清晰合理,代码可读性强,文档健全完善;做好规范约束手段,防止架构腐坏。

技术选型

所谓兵马未动粮草先行,技术栈作为我们项目框架的基石,首先我们需要确定选择什么样的技术栈,这关系到后续的一系列事情。

前端框架搭建过程中,可以从如下几个维度做技术选型:

  • 核心:如VueReact等,他是我们整个技术栈的核心,一旦确定,后续其他维度都是围绕其进行选择。
  • 路由:现在大部分前端应用都是单页应用,需要前端进行路由控制,所以路由一般是必不可少的。只需要在核心层生态中选择一个合适的即可。比如VueRouterReactRouter
  • 视觉:主要是组件库和风格的选择,PC和移动端可能会使用不同的组件库。当然如果是做的偏前端的应用,可能现有组件库很难满足要求,目前大部分都是偏中后台的,这时可以选择自建。
  • 数据:这一层不是必须的,一般只有数据交互较复杂的应用才可能会用到,而且目前Hooks也能实现一部分数据管理,只是采用专业的状态管理工具,可以保证大家在认知和实施层面的统一。当然并不绝对,在下就曾经在一个项目中使用hooks管理状态,也没有什么问题。
/******* state *******/
const state = reactive({
});
/******* getter *******/
const getter = reactive({
});
/******* actions *******/
const actions = {
};

export const useAppStore = () => ({ state, getter, ...actions });
  • 构建工具:当下前端开发是离不开工程化的,这一领域目前正处于发展阶段,虽然各种工具层出不穷,但是各有优缺点、不统一也为开发者带来了一些困扰,但是好在,VRA都提供了自己的脚手架,大部分情况我们是可以放行使用的。当然一般成熟的团队都会在其基础上做更多的配置扩展,形成自己内部成熟的框架模板。
  • 插件:现在前端的轮子是比较多的,开发的时候一般建议优先去npm看看是否有现成的轮子,不要做重复的事情。

image-20221127182555242
image-20221127182555242

但是需要注意,npm上的包虽然多,但是质量参差不齐,可以从几个方面去筛选。

image-20221127183017318
image-20221127183017318

以上是需要进行技术选型的维度,接下来我们就看看通过哪些指标去帮我们做出抉择。

  • 选择团队掌握的技术

如果选择不熟悉的技术,那么在使用过程中出现风险是不可控的。每种技术都是有它特定的适用场景,开发者经常犯的错误就是盲目追新,当一个新语言、框架、工具出现后,特别是开发者自己学会了这种新技术后,就会有种“拿着锤子找钉子”的感觉,将新技术滥用于各种项目。即使选择较新的技术,也要考虑到学习曲线和开发难度问题,主要结合当前团队的技术特点,熟练程度来考虑。

  • 保证统一性

对内保持团队技术栈统一,减少因为技术不统一带来的协作维护难度;对外符合行业标准,降低招聘难度,缩短新人进入开发的时间。

  • 选择拥有强大生态和社区支撑的开源技术

有强大的生态和社区意味着,很多东西你不需要重复去造轮子,或者遇到问题可以很快解决,有更多的选择。从公司层面、使用活跃的技术也比较好招人。另外在选择技术栈时要尽量避免一些公司的KPI成果,有烂尾的风险。

  • 选择成长期的技术

选择一个技术的最低标准是,技术的生命周期必须显著长于项目的生命周期,好的技术栈永远跑在用户需求前面,是面向未来的。主要考量该技术今后几年是否有人维护,是否在我们项目可预见到的生命期内还能继续开发。如果它不开发了,那么遇到平台升级,遇到第三方组件升级,产生的兼容性问题就很致命。如果当前已经进入开发欠活跃阶段,就要考虑是否不予采用。

  • 学会从业务端开始思考

选型需要充分地理解业务,理解用户需求,当下需要解决的首要问题,以及可能的风险有哪些,再将目标进行分解,进行具体的技术选型、模型设计、架构设计。处于初创期的业务,选型的基准是灵活。只要一个技术够用并且开发效率足够高,那么就可以选择它。初创的业务往往带有风险性和不确定性,朝令夕改、反复试错是常态,技术必须适应业务的节奏,然后才是其他方面。等业务进入稳定期,选型的基准是可靠。技术始终是业务的基石,当业务稳定了技术不稳,那就会成为业务的一块短板,就必须要修正。当业务进入维护期,选型的基准是妥协。代码永远有变乱的趋势,一般经过一两年就有必要对代码来一次大一点的重构。在这种时候,必须得正视各种遗留代码的迁移成本,如果改变技术选型会带来遗留代码重写,这背后带来的代价业务无法承受,那么我们就不得不考虑在现有技术选型之上做一些小修小补或者螺旋式上升的重构。

  • 选择API稳定的技术

比较典型的例子就是Angular和Python,API不稳定会导致社区的割裂,也会导致项目升级成本变高、或者无法升级, 最终成为技术债。

当然,对于团队而言也要鼓励学习新的技术、淘汰旧的技术栈。因为一般而言新的技术或解决方案,是为了更高的生产力而诞生的。

  • 先验证,后使用

对于未经验证的新技术、新理念的引入一定要慎重,一定要在全方位的验证过后,再大规模的使用。新技术、新理念的出现,自然有它的诱惑,慎重并不代表保守,技术总是在不断前进,拥抱变化本身没有问题,但是引入不成熟的技术看似能带来短期的收益,但是它的风险或者是后期的成本可能远远大于收益。记住,技术选型是稳定压倒一切。

  • 重视经验

技术选型是个很需要经验的活,得有大量的信息积累和输入,再根据具体现实情况输出一个结果。我们在选型的时候最忌讳的是临时抱佛脚、用网上收集一些碎片知识来决策,这是非常危险的,我们得确保自己所有思考都是基于以前的事实,还要弄清楚这些事实背后的假设,这都需要让知识内化形成经验。

  • 避免法律纠纷问题

选择技术时,要考虑license问题,不要陷入法律纠纷,特别是你参与的产品对外有机会做大做强、面向国际时。

毕竟技术只是实现业务的手段而已,我们也只是实现领导实现目标的工具而已,技术选型有时可能会受业务需求和领导个人偏好影响,不能一概而论,所以这里只是列出技术选型的注意事项,以作参考,具体选型考量还请结合实际情况而定。

做好了技术选型,接下来就只需要把合适的东西扔到合适的地方就行了。所以接下来我们就来看看框架设计。

框架设计

框架设计我们可以从两个层面来入手:

基础层:主要是从架构层面出发,包含一些基础设施,与业务相关性较低。

应用层:贴近业务,用于解决某一类/个业务,不同的业务可能并不能通用。

基础层设计

首先我们要选择一种架构风格,不同人的喜好、不同的项目会倾向不同架构风格。这里仅列出其中两种风格。

工件模型

  • 描述:即按照技术工件(如components、store)职责来进行划分。
  • 特点:该方式对开发人员来说易于理解,因为对他们来说技术工件比功能更有亲和力,但是相反不利于对业务功能的理解,且各功能模块间划分不太清晰,开发和维护一个功能可能要在多个目录下操作。
  • 适用场景:该方式比较通用,一般的项目都会采用此方式。不过在大型项目可能会存在每个工件目录存在很多文件,且存在各个模块的文件。

image-20221127195615915
image-20221127195615915

image-20221127200629453
image-20221127200629453

领域模型

  • 描述:按照系统功能进行划分,每个功能即为一个文件夹,其下包含自己的component、store、router。
  • 特点:面向业务功能,每个功能包含自身需要的技术工件,仅对外提供一个出口服务即可,由应用整合这些服务,构成整个应用。设计偏向微服务化,开发和维护一个功能仅需关注自身的目录即可;有助于开发人员对业务的理解。
  • 适用场景:特别适合大型项目、且各模块耦合度较低的场景,具有高扩展性。

image-20221127195630260
image-20221127195630260

图片(1)
图片(1)

因为当前团队,基本是使用第一种风格,后续我就以此为例,在vite+vue3的基础上做叙述。首先展示一下我目前使用的项目结构设计。

|- docs // 文档
|- dist // 构建成果
|- mock // 数据mock
|- public  // 静态资源,会直接拷贝到dist
|- src
  |- api // api接口管理
  |- assets  // 静态资源,这里的资源会被构建工具编译
  |- components // 全局组件
  |- config // 全局配置
  |- directives // 全局指令
  |- enums // 枚举及一些常量管理
  |- hooks // 全局hooks,用于抽离公用逻辑
  |- layouts // 布局组件
  |- plugins // 第三方插件
  |- router  // 路由
  |- store   // 状态
  |- utils   // 工具函数
  |- views   // 页面
  |- App.vue  // 根组件
  |- main.ts  // 应用入口
|- types   // 类型声明
|- .browserslistrc // 浏览器兼容性配置,可以供多个工具使用
|- .editorconfig // 编辑器配置,统一不同编辑器格式
|- .env.* // 环境变量配置,建议多个环境分别使用不同的文件
|- .npmrc // 统一项目npm镜像源
|- ... // 各工具配置
|- README.md  // 说明,包含项目信息及注意事项
|- index.html // 入口页面
|- vite.config.ts // vite配置

这里我们可以采用在官方脚手架的基础上来完善,以后可以直接使用,或者开源到社区,供别人使用。

在创建Vue项目,现在推荐使用 create-vue

> npm init vue@latest
✔ Project name: … <your-project-name>
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes

Scaffolding project in ./<your-project-name>...
Done.

由于脚手架创建的工程已包含基础层需要的大部分功能,在此基础上可根据需求引入如下功能即可:

  • UI组件库:建议尽量采用按需加载方式,目前大部分组件库都支持使用unplugin-vue-components做自动按需引入,比较舒服。
  • 各编译语言:由于html+css+js自身能力局限,我们可以使用一些编译语言来提升开发效率,如less、typescript等等。

因为类似的文章网上也比较多写的也比较好,这里就不再赘述。

手把手教你用 vite + vue3 + ts + pinia + vueuse 打造企业级前端项目

应用层设计

由于应用层需要结合具体业务情况,这里我就仅谈谈在我开发过程中两个比较常用的功能:

权限控制

一般中后台项目都有权限控制这部分功能,这里前端需要关心的主要是两方面:

登录认证

这个好理解,只有你登录了,系统才知道你是谁,我们要做的也很简单,只需要登录成功后把后端返回的令牌存储到本地,然后后续每次请求带上该信息,后端去做验证,如果验证通过返回200,不通过返回401,直接踢回登录页或者给出提示,另外在路由全局前置守卫也可以做判断。

实现方案我这里就不献丑了,贴上两篇写的很好的文章:

前端鉴权的兄弟们:cookie、session、token、jwt、单点登录

一文教你搞定所有前端鉴权与后端鉴权方案,让你不再迷惘

下面贴下代码:

  • 登录保存认证信息,我这里是token、refreshToken、tokenExpires,需要保存到本地缓存。

image-20221128114406893
image-20221128114406893

  • 请求拦截器注入认证信息,需要根据后端要求去注入,一般都是在header中。

    image-20221128114724023
    image-20221128114724023

​ 这里有个handleCheckAuth,是做了静默token刷新功能,在token要到期前,使用refreshToken去后台换去新的token,代码如下:

/**
 * 刷新token
 * @param {string} refreshToken
 */
export const handleRefreshToken: {
  (token: string | undefined): void;
  refreshDoing?: boolean;
} = (refreshToken) => {
  if (handleRefreshToken.refreshDoing) return;
  handleRefreshToken.refreshDoing = true; // 加锁,防止重复刷新
  auth
    .refreshToken({ refresh_token: refreshToken })
    .then((res) => saveAuthData(res))
    .finally(() => {
      handleRefreshToken.refreshDoing = false;
    });
};
/**
 * 检查更新token
 * @param config
 */
export function handleCheckAuth(config: AxiosRequestConfig) {
  const tokenExpires = Cookie.get(tokenExpiresKey);
  if (Number(tokenExpires) <= Date.now() && !config.skipCheckAuth) {
    handleRefreshToken(Cookie.get(refreshTokenKey));
  }
}
  • 路由拦截,看注释吧

image-20221128115106273
image-20221128115106273

权限控制

所谓权限控制,就是针对不同的用户赋予不同的权限,对应到前端就是根据不同权限的人动态控制呈现不同的资源,这里资源是一个统称,包含页面、按钮、数据等等,比如超管和管理员进入系统看到的菜单和页面中的按钮不一致。

关于系统权限控制如何设计,主要和业务需求有关系,这里我们不去深究,只是了解一下目前比较流行的一种权限模型。

RBAC(基于角色的权限控制)模型的核心是在用户和权限之间引入了角色的概念。取消了用户和权限的直接关联,改为通过用户关联角色、角色关联权限的方法来间接地赋予用户权限(如下图),从而达到用户和权限解耦的目的。比如在创建多个权限相同的用户,我们不需要给每个人单独设置权限,只需要将他们赋予相同的角色即可。

img
img

目前我们大部分系统都是使用该模型,下面我们也就基于该模型来看看前端如何进行权限控制。

其实现在网上讲前端权限控制的文章有很多,总结起来大体思路是,登录成功后获取用户当前的权限数据,然后动态添加路由,生成菜单,页内元素可以通过指令或者vif实现控制。实现方案上有如下两个流派:

前端控制

整个动态路由表是放在前端,每个路由通过meta.role设置允许访问的角色,登录后后台返回当前用户的role标识信息,前端根据这个role去和路由的meta.role做匹配,清洗出匹配的路由,然后动态注册仅路由。由于只是返回了role信息,所以菜单是需要后端另外提供的。

// asyncRoutes 动态路由表
export const asyncRoutes = [
  {
    path: '/permission',
    component: Layout,
    redirect: '/permission/page',
    alwaysShow: true, 
    name: 'Permission',
    meta: {
      title: 'Permission',
      icon: 'lock',
      // admin、editor可以查看该路由
      roles: ['admin', 'editor']
    },
    children: [
      {
        path: 'page',
        component: () => import('@/views/permission/page'),
        name: 'PagePermission',
        meta: {
          title: 'Page Permission',
          // admin可以查看该路由
          roles: ['admin']
        }
      }
     ]
    }
]
// 这个方法是用来把角色和route.meta.role来进行匹配
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}


// 这个方法是通过递归来遍历路由,把有权限的路由给遍历出来
export function filterAsyncRoutes(routes, roles) {
  const res = []
  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })
  return res
}

除了上述说的后端返回role标识,还有一种变种大家可以查看该文章Vue2.0用户权限控制解决方案/#路由控制

前端控制的特点如下:

  • 路由表在前端维护,变动比较方便,对前端比较友好。
  • 实现逻辑比较简单,上手难度低。
  • 后期修改角色权限难度较大,需要修改前端代码,不支持在后台系统中动态配置角色权限。
后端控制

接下来看看后端控制,主要区别就是把路由表挪到了后端,前端可以直接或简单转换使用后端返回的路由表数据注册路由,一般这份数据是可以直接生成菜单数据的,固不需要再单独返回菜单信息。

image-20221128160927872
image-20221128160927872

该方案的特点如下:

  • 可以在后台动态配置,可以实现角色权限灵活配置。下图是之前做过的系统,整个菜单树都可以在后台灵活组装,灵活度很高。

image-20221128163522794
image-20221128163522794

  • 前端实现比较简单,不用自己维护路由表。
  • 由于前端不自己维护路由表,那么要调整路由必须就得去后台,比如每开发一个新页面就需要去后台配置对应的菜单路由信息,而且还必须和前端对应起来。
  • 前后端划分的方式可能有差异,比如某个列表页每条数据的查看详情是新开页面,在后端看来详情亦属于查看,如果能够查看列表肯定就能查看详情,不需要单独配置菜单,但是因为是新开页面,前端就需要一个新的路由,即使能够单独创建一个详情的权限,在后端的划分中,详情应该是属于这个列表页的下级,但是在前端可能需要的是同级,而不是嵌套在列表页路由的children中。
改良一下

我们可以看到如上两种方案都不是完美的,所以我在项目中采用了另外的方式,基于后端控制做了改良。思路如下:

  1. 后台菜单配置时,不再配置url和组件相关信息,毕竟这部分内容是前端路由使用的,前端控制更加合适,也方便前端做调整。然后仅需要填写一个权限表示码就行,降低了前后端数据的耦合性,也就不存在划分不一致带来的问题了。

    image-20221128171252175
    image-20221128171252175

  2. 后台给角色设置拥有的菜单、按钮等资源的权限。

    image-20221128171444752
    image-20221128171444752

  3. 前端维护路由表,和正常路由差不多,但是不用默认注册,而且需要控制的路由要有meta.permission,和前面后台设置的权限标识码对应就行。如果没有permission标识,可以保留或者舍弃,这个根据需求来决定使用哪种方式,我采用的是保留。比如上面我们说的查看详情那里,我就可以不设置permission,这样只要用户有当前菜单的权限(即列表页),那么就不用再设置查看详情的权限了。

     {
        path: '/system',
        name: 'system',
        redirect: '/system/department',
        meta: {
          permission: 'system' // 权限标识码
        },
        children: [
          {
            path: '/system/role',
            name: 'role',
            component: () => import('@/views/system/Role.vue'),
            meta: {
              permission: 'system/role'
            }
          }
       ]
    }
    
  4. 前端登录成功以后,后端提供如下菜单数据。

[
  {
    "id": 1,
    "permission": "system", // 前面填写的权限标识码
    "name": "系统管理",
    "type": 0,
    "parentId": 0
  },
  {
    "id": 2,
    "permission": "system/role",
    "name": "角色管理",
    "type": 0,
    "parentId": 1
  },

  {
    "id": 3,
    "permission": "system/menu",
    "name": "菜单管理",
    "type": 0,
    "parentId": 1
  },
  {
    "id": 4,
    "permission": "user",
    "name": "用户管理",
    "type": 0,
    "parentId": 0
  },
  // ....
]
  1. 通过将菜单表和路由表通过permission权限标识码进行匹配,做数据处理。这里数据处理可能比较复杂一点,需要输出两份数据

    • 路由:根据菜单的permission,将路由表中的数据做清洗,去掉permission在菜单中没有的路由。另外一点就是把菜单数据添加到路由的meta中,主要为了方便通过当前路由获取当前的菜单信息。这个数据用于注册动态路由。

      {
          "path": "/system",
          "name": "system",
          "redirect": "/system/config",
          "meta": {
              "permission": "system",
              "id": 1,
              "title": "系统管理",
              "type": 0,
              "priority": 0,
              "icon": "icon-setting",
              "parentId": 0
          },
          "children": [
              {
                  "path": "/system/config",
                  "name": "config",
                  "meta": {
                      "permission": "system/config",
                      "id": 2,
                      "title": "系统配置",
                      "type": 0,
                      "priority": 0,
                      "icon": "icon-setting",
                      "parentId": 1
                  }
              },
              ...
          ]
      }
      
    • 菜单:因为现在菜单设置没有设置url了,所以我们要通过permission去找到对应的路由,然后将其path作为url。该数据用于显示导航菜单。

      [{
          "id": 1,
          "permission": "system",
          "title": "系统管理",
          "type": 0,
          "priority": 0,
          "icon": "icon-setting",
          "parentId": 0,
          "url": "/system"
      },{
          "id": 2,
          "permission": "system/config",
          "title": "系统配置",
          "type": 0,
          "priority": 0,
          "icon": "icon-setting",
          "parentId": 1,
          "url": "/system/config"
      }]
      

image-20221128172904940
image-20221128172904940

该方案的特点是:

  • 前后端解耦,上帝的归上帝,凯撒的归凯撒,而且仅需保证permission对应就行。
  • 菜单表和路由表进行匹配处理的时候,算法复杂一点。

基于该套方案,我这边进行了封装,可以直接进行使用auth-tool

注意:

  • 路由控制这一步不能少,否则即使菜单没有入口了,但是用户通过输入url地址还是能访问无权限的页面。路由控制除了动态路由还有一种方案就是全量注册路由,然后在前置守卫每次跳转去做验证判断,没权限重定向到403页面。
  • 前端做权限控制只是锦上添花,不是绝对安全,一定需要后端做验证来兜底,后端需要对每一个涉及请求的操作做验证。

另外简单说一说页内元素的控制吧,其实比较简单,后端同样返回有权限的数据,前端通过自定义指令或者Vif做下判断就行。auth-tool也做了封装,需要的自己查看源码吧。

Vue.directive(directiveName, {
  mounted(el:Element, binding) {
    !hasAuth(binding.value) && el.parentNode?.removeChild(el);
  }
});

数据请求

另外一个比较重要的功能就是请求的封装了,其实axios本身还是比较成熟了,之所以要把这个单独拿出来说,主要是之前在项目上遇到过一些问题。

  • 请求建议集中管理,可以按照业务进行模块划分,页面中只需要调用这里提供的方法就行。不建议直接写在页面上,不方便维护,万一接口url做了调整,你都不知道那些页面使用了,只有全局去替换。

    image-20221128180115019
    image-20221128180115019

import { get } from '../../utils/request';
import type { RequestConfig } from '@/utils/request/Request';
const prefix = '/system/config';
export default { list: <T = any>(params: object, config?: RequestConfig) => get<T>(`${prefix}`, params, config) };

我在项目中也封装了一个,展示还没有单独提取出来,有兴趣的可以看看这里

   * 1. 多实例,在存在多个请求服务时使用
   * 2. 拦截器增强,支持全局拦截器和实例拦截器,执行顺序采用洋葱模型
   * 3. 支持重复请求取消功能,重复请求:url、method、参数均一致为重复,也可以自己传入判断函数
   * 4. 支持直接请求绝对地址
   * 5. ts类型推断增强,更好的类型提示

其他

规范约定

如果项目中存在多人开发,那么规范和约定就必不可少,除了我们要形成团队的一些规范文档外,在框架上我们也可以通过工程化去约束:

  • 使用eslint,如果团队有统一的规范,建议直接发布为rule插件。
  • 使用githooks做提交前的验证,当然最保险的还是在流水线去做这个事情,毕竟本地是可以绕过的。
"gitHooks": {
    "pre-commit": "lint-staged --allow-empty"
  },
  "lint-staged": {
    "*.{vue,js,jsx,cjs,mjs,ts,tsx}": [
      "npm run lint",
      "prettier --write"
    ],
    "*.{css,less}": [
      "npm run lint:style",
      "prettier --write"
    ]
  }

版本管理

关于版本管理这块,我之前遇到过得问题就是给多个用户部署不同的版本,但是当某个用户出问题时,不能第一时间知道该用户部署的代码是什么时候的。所以后来在构建时注入了相关信息。

import { GitRevisionPlugin } from 'git-revision-webpack-plugin';
import pkg from './package.json';
let appVersion = `app-version=${pkg.version},build-time=${new Date().toLocaleString()}`;
  if (IS_PRODUCTION) {
    try {
      const GitRevision = new GitRevisionPlugin();
      appVersion += `,git-hash=${GitRevision.version()},git-branch=${GitRevision.branch()}`;
    } catch (error) {
      console.warn('无法获取git信息,无法注入相关内容!');
    }
  }
  
  ...
   createHtmlPlugin({
        minify: true,
        entry: '/src/main.ts',
        // template: 'index.html',
        inject: {
          data: {
            BASE_URL: VITE_BASE_URL,
            title: VITE_TITLE,
            appVersion
          }
        }
      }),

image-20221128182524845
image-20221128182524845

然后关于更新日志和版本发布,可以使用release-itstandard-version去做。

构建优化

优化主要是两个方向:

  • 运行时:提升程序运行效率,就是常规的性能优化手段都可以用上,比如gzip、分包、按需加载等等。
  • 开发时:主要是加快构建速度,提升开发效率,有几个思路可以考虑,减少构建的范围、利用缓存、使用多进程。

脚手架

当你的框架成熟后,可以自己封装一个脚手架,可以做一些定制化的配置,这个比较简单,感兴趣的查看用vue3+ts+electron撸了一个好用的脚手架工具,这样团队成员使用起来就比较方便,也比较好管理。

结语

算了,下班了,没时间写了。

« 利用微信第三方平台发布零码小程序 TypeScript --- 进阶篇 »