多模块优雅管理

1,244 阅读6分钟

背景

随着我们前端技术越来越广泛、系统越来越复杂,导致我们需要兼容多种技术、模块的方式去打造我们的系统

目前前端开发模式分为MPA多页面应用模式和单页面应用模式,它们有着自己的特性,但也有不足之处。

  • MPA模式: 在每次页面跳转的时候,后台服务都会返回一个新的html文档,这种类型的也就是多页应用。这种类型主要是用在中后台多个系统覆盖了多了个模块,由不同的团队去负责,每个业务模块由独立的域名,访问不同的业务模块会重新刷新浏览器或者打开新标签的方式来实现。
  • SPA模式: 只有一个主页面的应用,浏览器一开始加载所有html、js、css,目前也是会根据路由不同按需加载。写页面时也是会分页面片段的,交互则是由路由程序动态载入。前端应用几乎由 SPA 三大主力 Vue、React、Angular 。页面之间切换无需刷新浏览器,极大保证系统的流畅性。但是应用模块也是强耦合的,随着业务的增加,系统会成为顽石。

原理

基于上述考虑,我们要在现有的环境下进行系统改造,形成非常健壮的系统。在现有的基础上我们要考虑多模块混合开发带来的复杂性、多团队联合开发的沟通性、系统稳定性和性能。为了解决和磨合上述问题,我们相应的做了很多内容,如npm包管理机制、微前端应用、iframe嵌入、nginx代理等操作。

image.png

npm包管理

该方案是将系统抽离成多个模块,各个模块可以独立开发,独立维护。主要是处理系统多人维护一个系统,对于开发人员来说不好管理、操作繁琐。

对于单个模块来说,使用monorepo多项目管理。一个仓库维护多个模块,方便版本管理和依赖管理,模块之间的引用和调试都比较方便; 也方便统一生成 changeLog。

对于主系统来说,将子模块生成的npm进行载入和使用,这样可以将每个开发负责人的模块集成到一起进行编写。 image.png

子模块

在单个模块下面,该模块本身就是单个代码仓库管理,将现有代码的分为ui和sdk两个代码仓库,在packages下面分为三个模块:

  • demo(项目部分,用于开发页面)
  • sdk(sdk部分,用于统一处理数据)
  • ui(ui部分打包后将页面、组件抽离到里面)

目录结构如下:

demo
├─ CHANGELOG.md
├─ README.md
├─ build
│  ├─ all.sh      // 加载资源
│  ├─ publish.sh  // 上传包
│  └─ build.sh    // 打包
├─ package.json
├─ packages
│  ├─ demo
│  ├─ sdk
│  └─ ui
├─ tsconfig.json
└─ vue.config.js

当然你可以在某个模块单独抽离使用,demo相当于该模块的运行架子,sdk和ui相当于该架子里面拼图。我们目前采用的是vue进行搭建的,简单的介绍一下。

ui层面我们利用vue.component将页面进行抽离。

import Demo from './demo.vue';

Demo['install'] = (Vue): void => {
  Vue.component('Demo', Demo);
};

export default Demo;

对外输出则需要在输出的方法中增加install用于vue的注入

import Demo from "./pagesComponents/demo";

interface compList {
   [componentsName: string]: any
}

const components: compList = {
   Demo
}

const install = (Vue: any, opts?: LangOptions): void => {
    i18n.use(opts?.lang, opts?.langMessage);

    Object.keys(components).forEach((name) => {
      Vue.component(name, components[name])
    });
}

export { 
  Demo
}

export default {
  install
}

对于sdk来说就很简单了,将我们所写的方法进行打包和压缩即可,生成我们所熟悉js传统语言

最后我们的发布是将ui和sdk进行打包处理,每次发布会将我们主文件里面版本进行提升,然后将子目录进行提升和发布,从而做到版本统一。

主工程

在主工程中需要做的有3个事:引入、接入、依赖

  • 引入是我们将子工程打包的npm包进行引入,你可以直接用latest去加载最新的包,也可以直接写死版本,为了区分预发和线上,我们也可以引入特定版本,如1.0.1-bate.x等。
      "dependencies": {
          "demo-sdk": "latest",
          "demo-ui": "latest",
      }
    
  • 接入就直接用vue.use()方法就可以了
  • 依赖则是需要在主工程创建路由,在对应的页面中将我们子工程打包的内容进行写入
    <template>
      <div>
        <DemoPage
          :service="service"
        />
      </div>
    </template>
    

上面说的场景是同一团队多人维护该如何操作,接下来聊一下多团队如何管理

微前端

微前端是一种类似于微服务的架构,将我们web应用转变为多个小型应用聚合为一个应用。其优点也有很多:

  • 代码简洁、解耦、更易维护
  • 独立部署
  • 可以与时俱进,不断引入新技术/新框架

当前缺点也很明显,如重复依赖、团队之间更加分裂,我前篇文章已经介绍过了,大家可以去看看。

使用也很简单,我们是将微前端各个服务配置都在菜单内进行处理,跟主应用进行隔离开,使用大家看官网就行了,不多介绍了。

iframe

iframe是嵌入式框架, 是html标签, 还是一个内联元素, iframe 元素会创建包含另外一个文档的内联框架(即行内框架) 。 说白了, iframe用来在页面嵌入其他页面。

优点:

  1. 页面和程序分离,几乎不会受到外界任何js或者css的影响, 便于使用。
  2. 可以通过iframe嵌套通用的页面, 提高代码的重用率, 比如页面的头部样式和底部版权信息
  3. 重新加载页面时, 不需要重载iframe框架页的内容, 增加页面重载速度。
  4. iframe可以解决第三方内容加载缓慢的问题。

缺点:

  1. 会产生很多页面,不容易管理
  2. iframe框架的内容无法被搜索引擎捕获, 所以iframe不适用于首页
  3. iframe兼容性较差
  4. iframe有一定的安全风险
  5. iframe会阻塞主页面的Onload事件

上述三点都是在前端服务进行配置,我们抽离一套config,将这些数据进行分化和改造

nginx

nginx反向代理二级页面,需要映射多个域名,并且域名下面要配置。

我们在部署前端代码前可以抽离一个中间层,在这个中间层我们可以叫做网关,利用这个网关去做一些访问服务器前的一些操作,如利用lua 去写入一些脚本、利用代理接入不同的服务。

主工程在网关里面可以这样写:

location =/ {
    proxy_pass  http://demo.xx/;
    rewrite  ^/$ /index;
}

接入第三方应用可以:

location /demo2 {
    proxy_pass http://demo2.xx/;
    proxy_set_header Host $host;
}

结论

不管是系统还是模块,我们这么做的目的主要是将代码解耦、将系统拆分。让系统简单化、模块清晰化,做到内部和外部能够顺利连接。