前端工程化开发方案app-proto

什么是前端工程化?根据具体的业务特点,将前端的开发流程、技术、工具、经验等规范化、标准化就是前端工程化。它的目的是让前端开发能够“自成体系”,最大程度地提高前端工程师的开发效率,降低技术选型、前后端联调等带来的协调沟通成本。

美团点评厦门智能住宿前端研发团队通过多个前端项目开发的探索和实践,基于“约定优于配置”(Convention Over Configuration)的原则制定了一套前端工程化开发方案app-proto。本文将简要介绍其中的一些设计细节和约定。

面临的业务特点

智能住宿前端团队承担的前端业务主要面向B端项目,用户主要是商家、销售、运营、产品经理以及研发人员。

诸如工单管理、信息管理、门锁运营、PMS(Property management system)、CRM(Customer relationship management)及AMS(Asset management system)等项目都是单页面工具类应用,特点是功能交互繁多、复杂表单,非展示类、无SEO(Search engine optimization)需求。

如果这些项目脱离浏览器这个“外壳”,与传统的原生桌面GUI软件无异。换言之,这些项目就是一种运行于浏览器的工具软件。

实际上,部分项目我们也确实利用CEF(Chromium Embedded Framework)等技术给其套个“外壳”,当作传统的桌面GUI应用提供给用户使用。

同时,部分服务需要从智能门锁、控制盒Wifi等硬件设备收录状态数据,限于硬件环境测试的不稳定性,后端的开发测试周期远比前端开发周期长。大部分场景下,前后端需并行开发,后端工程师并不能在第一时间兼顾到前端所需的API接口等服务,给前端开发造成没有必要的“等待期”,影响开发进度。

此外,项目多、敏捷需求多、开发周期短以及面向多后端服务(多个后端团队)等也是我们前端研发团队面临的挑战。

一些前端经验总结

针对多个项目的开发实践和探索,我们在对前端工程化设计中得到如下一些经验总结:

  • 前端开发应该“自成体系”(包括构建、部署及前端运维),不应该和后端项目耦合在一起。
  • 避免“大而全”的重量级框架,一个框架真的满足不了所有的业务场景。项目多了,我们又不想为每个新项目重新造一遍技术“轮子”。
  • 新的前端技术(React、Vue、Angular2等)和工具(Grunt/gulp、webpack、Babel等)不断涌现、迭代,新技术选型应避免“改头换面”式重构。
  • 工程化设计要合理分层且相互独立,随时应对新需求和技术的变化,任何一层能够低成本被替换、淘汰。

设计概览

目前,app-proto将前端工程化项目拆分成三大模块:Node服务(负责数据代理、url路由和服务端渲染)、Web应用开发(专注Web交互体验)以及前端运维(构建、测试、部署及监控等)。整体的结构设计如图1所示。

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/8de86cb9.svg)
app-proto 结构设计图
  • Node服务:用于实现前后端分离,核心功能是实现数据代理中转,附带url路由分发和服务端渲染功能。
  • Web应用开发:纯粹的前端模块,给予前端工程师极大的自由度进行技术选型,专注于Web交互体验的开发。
  • 前端运维:主要指前端项目构建和部署、工程质量(源码质量检查和测试等)及监控服务(日志、性能等)等工作。

前后端分离

正如前文所强调的,前端模块开发应该“自成体系”,而不是后端项目的一部分(Controller或View层)。比如说,前端工程师要在本地跑通完整的项目,就必须配置好后端所需开发环境和各种服务,如果后端涉及的服务多、变化频繁,配置开发联调环境工作往往是耗时耗力的。为了实现彻底的前后端分离,我们在前端开发体系中引入了Node服务层。

在最初的开发中,为了降低Node端的开发和运营成本,我们极力避免在Node服务中“掺合”过多的业务逻辑。经过几个项目的实践,最后“约定”在Node服务中我们仅仅做三件事:数据代理、路由分发和服务端渲染。

数据代理

首先,前端数据从何而来?通过Ajax的形式直接从后端服务中获取数据是传统的方式,但是在应对多后端服务时,还是面临着诸如请求认证、CORS(Cross-origin resource sharing)等困扰。常见的解决方案是通过http-proxy,即在Node端通过HTTP请求得到数据后,Web端再通过Ajax的方式从Node端间接获取后端数据,Node服务起到“桥梁”的作用。

方案http-proxy对已经成熟的后端服务是具备实用价值的,但是在后端服务并没有完成开发(或前后端并行开发)的场景下时,开发阶段前端的数据来源依旧是个问题。同时,前端还面临诸多请求合并、缓存等需求,解决这些困扰,前端工程师需要和后端技术人员做大量的沟通、约定。

在这里,我们基于原有的http-proxy基础上在Node服务中添加datasources模块,尝试在数据的处理上给予前端工程师很大的自由度,并实现“按照约定写代码”。

举例说明,开发某一前端业务时涉及到pmsupm两个后端服务,且提供的API内容如下:

# pms API
pms/api/v2.01/login
pms/api/v2.01/inn/create
pms/api/v2.01/inn/get# upm API
upm/api/v3.15/menu

面对这些接口,理想情况下前端直接通过ajax.post('pms/api/v2.01/login', params)方式获取即可。但是,pms接口服务尚处在开发阶段,面临跨域或不可用问题。upm接口服务虽稳定,但是该服务由第三方团队维护,请求需要权限认证。传统的Ajax方式在这类场景下并不适用。而datasources模块是通过怎样的设计来优化这些问题的呢?首先,我们将前端需要的API映射到前端源码仓库,映射的目录结构如下:

# server/datasources/{后端系统}/{接口目录}
── datasources├── pms│   ├── login.js│   ├── login.json│   └── inn│       ├── create.js│       └── get.js└── upm├── menu.js└── menu.json

其中,每个**.js后缀的文件的内容是将原本Web端Ajax操作转移到Node端的HTTP请求,以pms/login.js为例:

/* async 函数 */
export default async function (params) {const http = this.httpconst pms = this.config.api.pmstry {const apiUri = `${pms.prefix}/login`// http 请求:http.post() 方法封装了权限认证const result = await http.post(apiUri, params)// 简单的数据格式校验if (Number(result.status) === 0 &&('data' in result) &&('bid' in result.data)) {// 将bid值记录至sessionthis.session.bid = result.data.bid}return result} catch (e) {// 后端API出现异常 (实时通知 or 记录日志)}return null
}

当然,对于那些已经成熟稳定的API服务直接通过http-proxy方式实现数据中转即可。但由于需求变更频繁,后端API服务始终处在不断迭代中,前端在进行数据处理过程中总会面临如下的几种情况:

  • 接口校验或数据二次加工:面临多后端服务,API的格式可能不一致;或者对数据列表排序加工等。
  • 合并请求:可以发多个http请求,避免Web端同时发送多个Ajax请求。
  • 前端运维的数据:比如城市字典、阴阳历转换表等固定数据。
  • 缓存数据:如请求的用户信息,短期内不会有大变动,可以采用Half-life cache等算法实现简单缓存。
  • 需权限认证的接口:HTTP Authentication。

这些场景下都建议使用datasources模块进行数据中转,将原本需由前后端沟通协调才能实现的功能全部交给前端自行处理,给予前端工程师处理数据提供自由度的同时也降低了后端API的开发维度。

那该如何快捷地调用datasources目录下的async函数呢?这里我们做了简单封装,将该目录下的所有**.js文件解析到Koa的上下文环境中以this.ds对象进行存储,并按照目录结构进行驼峰式(Camel-Case)命名,转换过程见图2。

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/996ad014.svg)
datasources 目录解析转换过程

在Koa中间件中通过this.ds对象调用,比如src/datasources/pms/login.js函数映射至this.ds.PmsLogin()

// Koa Middlewares
app.use(async (ctx, next) => {// ..`.// 最后一个参数为是否使用mockconst loginData = await this.ds.PmsLogin(params, false)// ...
})

在Web端可以统一封装ds()方法,无需关注Ajax请求Headers、是否跨域等问题:

// Web (Browser)
ds('PmsLogin', { username, password }, true).then(success).catch(error)

Mock支持

正如前文所提到的,后端研发进度一般滞后于前端,在后端API服务可用之前,前端仅有一份API文档供参考。在规范中,**.json后缀的文件就起到Mock作用,同样以pms/login.json举例:

{"status": 0,"message": "成功","data": { "bid": "@string(32)", "innCount": 1 }
}

具体的json格式写法请参考mockjs、Syntax Specification。

简言之,当API服务可用时则执行**.js后缀文件中的async函数来获取数据,不可用时则解析**.json后缀Mock文件,并不需要单独开启一个Mock服务。

路由分发

对url路由的处理和数据代理的做法类似,按照目录结构来管理。url路由配置在server/pages目录下,目录下的文件会自动映射成为路由。

比如url为http://example.com/pms页面,映射到server/pages/pms.js文件的写法如下:

export default {urls: ['/pms', '/pms/error'],       // 多种正则如:['/pms', ['/pms/v1'], ['/pms/v**']]methods: ['GET'],                   // 多种method:['GET', 'POST']js: ['http://code.jquery.com/jquery-1.12.0.min.js'],css: ['http://yui.yahooapis.com/pure/0.6.0/pure-min.css'],template: 'default',                // 服务端渲染模板middlewares: [],                    // 针对本页面的中间件controller: async function(next) {  // Koa中间件最后一环// 可以从this.ds对象中拿数据const loginData = await this.ds.PmsLogin(params)return {foo: '来自服务端数据', loginData}}
}

由于urls支持多种正则,原则上每个根url映射server/pages/目录下一个**.js文件,映射关系如图3所示。

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/dac0ddbf.svg)
pages目录文件与url映射关系

如果对jscsstemplate没有特殊设置(采用默认设置)的情况下,可精简如下:

export default {urls: ['/pms', '/pms/error'],controller: async function (next) {const loginData = await this.ds.PmsLogin(params)return {foo: '来自服务端数据', loginData}}
}

需要注意的是,controller项是Koa中间件的最后一环,要求其返回值是可序列化的对象用于模板渲染的服务端参数,在此处也可以进行权限校验、从this.ds对象中拿数据等操作。

服务端渲染

Node服务端最后一个核心功能是渲染:输出 HTML Shell和 JSON。输出JSON字符串的用途是为了浏览器端能以Ajax形式动态获取数据,而输出的HTML内容则是我们Web应用的所需的HTML“壳子”。

正如前文提到我们的业务特点是“一种运行于浏览器的工具软件”,重操作交互、无SEO需求。因此,同构(Isomorphic JavaScript)不是强需求,不是每次都要依赖服务器来重复处理逻辑和数据。服务端只需要渲染简单完善的HTML结构即可,具体的页面内容则由客户端JavaScript实现。简言之,不鼓励将前端JavaScript脚本再在Node服务端重复执行一遍。

如果了解过Google推崇的 Progressive Web App,你可以参考《The App Shell Model》一文来理解HTML“壳子”更多的用途。

渲染最简单的HTML“壳子”如下:

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"/><title>app-proto</title><script>window.serveData={foo: '来自服务端数据'}</script></head><body><div id="app"></div><script src="//cdn/file-5917b08e4c7569d461b1.js"></script></body>
</html>

提供简单的服务端数据window.serveData供客户端使用,更多渲染则由//cdn/file-5917b08e4c7569d461b1.js进行增量控制。

静态资源与Node端衔接

那Web端构建的静态资源是如何Node服务端做衔接的呢?前端静态资源构建工作与Node服务相互分离,Node服务在开启的过程中会读取前端构建生成的静态资源映射表。前端的构建过程如图4所示,在构建工作完成之后会生成assets.json静态资源映射表。

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/fbd374fe.svg)
静态资源映射文件assets.json构建

前端构建工具基本都提供静态资源映射表生成插件,比如构建工具Webpack就存在插件assets-webpack-plugin来实现该功能。

生成的assets.json映射表内容参考如下:

{"index":        // 对应的页面(url: example.com/index){ "js":"//s0.example.net/pms/index-2abb99.js" },  // 涉及到的静态资源列表(带版本号)"login":{ "js":"//s0.example.net/pms/login-5917b0.js" }
}

比如在渲染页面example.com/index时,Node服务会以index作为键值,读取assets.json中带版本号的静态资源CDN地址列表,用于在“壳子”中与前端资源的衔接工作。

Web端的一些“约定”

Web端的技术选项是没有强制性限制的,无论你采用何种构建工具、前端库,只要生成符合约定供Node端使用的assets.json文件即可。

前端工程师可以根据具体的业务特点、团队技术喜好来选取合理的开发方案,无论是React、Vue还是Angular2并不做强限制。尽管给予Web前端开发很大的自由度,但是鼓励遵循下面几条“约定”:

  • Ajax请求从Node端代理,而非具体后端服务。
  • 鼓励将JavaScript、CSS、HTML视为前端领域的“汇编”。
  • 重视前端页面状态管理,推荐的方案有Redux、vuex及MobX等。
  • 强调组件化,面向组件集开发。

这里重点强调下面向组件集的前端开发。在项目初期我们一般不会马上投入到业务开发,而是针对设计师和产品经理提供的设计稿、产品原型图实现一套组件集或选择合适的开源组件集,积累好基础组件集后再投入到具体业务开发。

在进行前端技术调研时,该技术是否有配套的开源组件集往往是我们考虑的重点。比如基于React实现的开源组件集ant.design、Material-UI等,我们部分前端项目都直接或间接的使用到了,极大地减少了研发成本。

当然,美团点评内部也提供一个组件中心平台(可参考美团点评前端组件中心介绍Slide),鼓励大家将各自项目中的有价值组件分享出来,实现组件跨项目复用。

工程化支持

项目脚手架

项目脚手架的作用是在启动一个新项目时,通过几个简单命令就能快速搭建好项目的开发环境。我们基于Yeoman构建了一个完整的项目脚手架。

# 安装脚手架
$ npm install -g yo
$ npm install -g @ia/generator-app-proto@latest
# 初始化新项目(进行简单选择)
$ yo @ia/app-proto

工程质量保障

我们重视项目的每次commit,同个项目要求遵循同一套编码规范,并采用ESLint等工具进行约束,对于一些复用性高的核心组件也强制要求写测试。 为保障项目质量,每个项目都要求接入美团点评基于[Stash](https://en.wikipedia.org/wiki/Stash_(software)实现的Castle CI系统,每次的源码提交都会自动执行一遍ESLint、测试和构建,并生成构建日志通过公司内部沟通工具大象进行实时消息推送。

标准化测试环境管理

美团点评内部提供了基于Docker实现的测试环境管理服务Cargo,用于提升测试和联调测试效率,促进DevOps开发模式。将项目接入到Cargo服务后,只需在仓库中提供简单的配置文件cargo.yml(配置参考如下),就会自动生成一套测试环境。

# 依赖的镜像
image: registry.cargo.example.com/node:v4.2.1
# 容器占用的端口
ports:- '8998'
# 环境变量
env:-  COMMON_VARIABLE = 'true'-  NODE_ENV = 'cargo'-  DEBUG = 'app-proto,datasource.*'
# 收集的日志文件
logs:-  error = /var/path/logs/app-proto/error.log-  out = /var/path/logs/app-proto/out.log
# 构建脚本
build_script: bin/pre-deploy-staging
# 运行脚本
run_script: bin/cargo-start

总结

前端工程化体系的引入,让前端开发能和原生App应用项目开发一样“自成体系”,脱离了对后端项目的依赖。基于“约定优于配置”、“按照约定写代码”的原则对Node层功能的设定能够降低沟通协调成本,构建、部署等工作的规范化,使前端技术人员的开发重点回归到Web应用的交互体验本身,回归到“纯粹”的前端研发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/478256.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

会议交流 | 最新NLP核心技术与前沿实践分享!

2021年7月10日&#xff0c;09:00-18:10&#xff0c;DataFunSummit&#xff1a;自然语言处理峰会将如约而至&#xff0c;本次峰会由3位主席和6位出品人精心策划而来&#xff0c;邀请来自业界的40余位嘉宾从NLP基础技术、语义表示与计算、多模理解与生成、信息抽取与检索、人机对…

LeetCode 504. 七进制数(进制转换)

1. 题目 给定一个整数&#xff0c;将其转化为7进制&#xff0c;并以字符串形式输出。 示例 1: 输入: 100 输出: "202"示例 2: 输入: -7 输出: "-10" 注意: 输入范围是 [-1e7, 1e7] 。来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xf…

三元组事件抽取与简单代码实现

https://blog.csdn.net/weixin_43718786/article/details/118217222

领域应用 | 常识性概念图谱建设以及在美团场景中的应用

转载公众号 | 美团技术团队常识性概念图谱&#xff0c;是围绕常识性概念建立的实体以及实体之间的关系&#xff0c;同时侧重美团的场景构建的一类知识图谱。本文介绍了美团常识性概念图谱构建的Schema&#xff0c;图谱建设中遇到的挑战以及建设过程中的算法实践&#xff0c;最后…

美团数据库中间件DBProxy开源

随着数据量的不断增大&#xff0c;传统的直连数据库对数据进行访问的方式已经无法满足一般公司的需求。通过数据库中间件&#xff0c;可以对数据库进行水平扩展&#xff0c;由原来单台数据库扩展到多台数据库&#xff0c;数据库中间件通过路由规则将数据的访问请求路由到其中一…

不卷学术了,这次卷一波NLP实战落地经验

作为最典型的 AI 领域之一&#xff0c;NLP 这些年也是越来越热门&#xff0c;基本上是各大厂必备了。随着 OpenAI 等技术的诞生、迁移学习等技术的成功应用&#xff0c;使得 NLP 技术在搜索、推荐、信息流、互联网金融、社交网络等领域不断发展壮大。与此同时&#xff0c;NLP 工…

docker挂载文件躺过的坑

Docker 实现挂载的三种方式 docker: Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused “exec: “-v”: executable file not found in $PATH”: unknown. 看着启动成功了&#xff0c;但查日志启动失败 日…

美团团购订单系统优化记

团购订单系统简介 美团团购订单系统主要作用是支撑美团的团购业务&#xff0c;为上亿美团用户购买、消费提供服务保障。2015年初时&#xff0c;日订单量约400万~500万&#xff0c;同年七夕订单量达到800万。 目标 作为线上S级服务&#xff0c;稳定性的提升是我们不断的追求。尤…

论文浅尝 | emrKBQA: 一个面向临床医疗问答的KBQA数据集

笔记整理 | 谭亦鸣&#xff0c;东南大学博士生来源&#xff1a;BioNLP ’21 workshop, ACL ‘21链接&#xff1a;https://www.aclweb.org/anthology/2021.bionlp-1.7.pdf论文主要包含两个部分的核心工作&#xff1a;emrKBQA数据集&#xff0c;对应的benchmark。基于MIMIC-III …

Fine-tune之后的NLP新范式:Prompt越来越火,CMU华人博士后出了篇综述文章

文 | 刘鹏飞源 | 机器之心CMU 博士后研究员刘鹏飞&#xff1a;近代自然语言处理技术发展的第四范式可能是预训练语言模型加持下的 Prompt Learning。近几年&#xff0c;NLP 技术发展迅猛&#xff0c;特别是 BERT 的出现&#xff0c;开启了 NLP 领域新一轮的发展。从 BERT 开始&…

python学习网站

1 w3school 2 掘金 3 b站 4 牛客网 5 leetcode 6 慕课网

会议交流 | 世界人工智能大会“大数据关联”论坛举行

转载公众号 | 创邻科技2021年7月8日&#xff0c;由世界人工智能大会组委会与浙江创邻科技有限公司共同筹办的“大数据关联时代下的图数据库技术与应用论坛”在上海世博中心圆满落幕。论坛邀请了清华大学、北京大学、复旦大学、浙江大学、天津大学等多位学术届的思想领袖和工商银…

LeetCode 994. 腐烂的橘子(图的BFS)

1. 题目 在给定的网格中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b; 值 1 代表新鲜橘子&#xff1b; 值 2 代表腐烂的橘子。 每分钟&#xff0c;任何与腐烂的橘子&#xff08;在 4 个正方向上&#xff09;相邻的新鲜橘子都会腐烂…

美团酒店直连产品数据一致性演进

美团酒店直连项目自2013年末开始&#xff0c;通过业务上的不断完善和技术上的不断改进&#xff0c;至今已经接入200多家供应商&#xff0c;其中在线酒店3万以上&#xff0c;在线SPU30万以上。经过两年的成长&#xff0c;美团酒店直连平台终于在2015年末发展为国内最大的酒店直连…

直击行业痛点!端侧模型部署的成熟解决方案有了!

深度学习经过多年发展&#xff0c;AI已经深入人心&#xff0c;事实上&#xff0c;在图像、文本领域&#xff0c;AI在多个产业已经落地了诸多应用&#xff0c;我们熟知的自动驾驶、语音助手&#xff0c;背后就大量运用了AI技术。当下&#xff0c;飞桨PaddlePaddle、TensorFlow、…

OpenKG开源系列 | 大规模中文概念图谱OpenConcepts (浙江大学)

OpenKG地址&#xff1a;http://openkg.cn/dataset/openconcept官网&#xff1a;http://openconcepts.openkg.cn&#xff08;提供Dump&#xff09;开放许可协议&#xff1a;CC BY-SA 4.0&#xff08;署名-相同共享)OpenConcepts 介绍OpenConcepts (http://openconcepts.openkg.c…

LeetCode 594. 最长和谐子序列(map)

文章目录1. 题目2. 解题2.1 map两次扫描2.2 map一次扫描1. 题目 和谐数组是指一个数组里元素的最大值和最小值之间的差别正好是1。 现在&#xff0c;给定一个整数数组&#xff0c;你需要在所有可能的子序列中找到最长的和谐子序列的长度。 示例 1: 输入: [1,3,2,2,5,2,3,7] …

MTDDL——美团点评分布式数据访问层中间件

2016年Q3季度初&#xff0c;在美团外卖上单2.0项目上线后&#xff0c;商家和商品数量急速增长&#xff0c;预估商品库的容量和写峰值QPS会很快遇到巨大压力。随之而来也会影响线上服务的查询性能、DB&#xff08;数据库&#xff0c;以下统一称DB&#xff09;主从延迟、表变更困…

python 23中设计模式 工厂模式与抽象工厂及应用场景

#https://zhuanlan.zhihu.com/p/57869247 #工厂模式 # -*- coding:utf-8 -*-class A:def __init__(self):self.word "运行A"def run(self):print(self.word)class B:def __init__(self):self.word "运行B"def run(self):print(self.word)def Interface(c…

详解预训练模型、信息抽取、文本生成、知识图谱、对话系统技术

我们正处在信息爆炸的时代、面对每天铺天盖地的网络资源和论文、很多时候我们面临的问题并不是缺资源&#xff0c;而是找准资源并高效学习。其次&#xff0c;即便网络上的资源非常多&#xff0c;学习是需要成本的&#xff0c;而且越有深度的内容越难找到好的学习资源。如果一门…