前端老牌框架衰退,IMVC(同构 MVC)成未来趋势?
Posted IT大咖说
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端老牌框架衰退,IMVC(同构 MVC)成未来趋势?相关的知识,希望对你有一定的参考价值。
内容来源:2017 年 3 月 11 日,携程研发高级经理古映杰在“携程技术沙龙 | 新一代前端技术实践”进行《IMVC(同构 MVC)的前端实践》演讲分享。IT 大咖说(微信id:itdakashuo)作为独家视频合作方,经主办方和讲者审阅授权发布。
阅读字数:2738 | 7分钟阅读
摘要
随着 Backbone 等老牌框架的逐渐衰退,前端 MVC 发展缓慢,有逐渐被 MVVM/Flux 所取代的趋势。然而,纵观近几年的发展,可以发现一点,React / Vue 和 Redux / Vuex 是分别在 MVC 中的 View 层和 Model 层做了进一步发展。如果 MVC 中的 Controller 层也推进一步,将得到一种升级版的 MVC,我们称之为 IMVC(同构 MVC)。
IMVC 可以实现一份代码在服务端和浏览器端皆可运行,具备单页应用和多页应用的所有优势,并且可在这两种模式里通过配置项进行自由切换。配合 Node.js、Webpack、Babel 等基础设施,我们可以得到相比之前更加完善的一种前端架构。
嘉宾演讲视频及PPT回顾:http://suo.im/4VPTN5
IMVC的“I”指的是ISOMORPHIC ,也就是同构,最初它是数学上的概念,描述两个对象之间的某种一致性。在前端领域中ISOMORPHIC javascript 则是指一段前端代码在客户端和服务端都可运行,它在2012年就已经被提出,算是历史悠久的概念了。
同构分为内容同构和形式同构,内容同构指同样的代码在客户端和服务端做等价的事情。形式同构通过判断所处环境来执行某段代码,也就是说在客户端或者服务端始终有一部分代码没有执行。
同构并不是一种非是即彼的判断,它更像是光谱,既可以是小范围的也可以是大范围。小范围的同构,例如原生的js 在浏览器和Node 中代码并没有差异,只是DOM API 和 Node API 不同而已,这就是函数层面的同构,即代码片段相同。还有一种特性层的同构,指的是业务中不同职能特性的同构,比如Vue 2.0在客户端和服务端都能运行,这就是Vue 这个特性层的同构。另外就是框架层同构,框架基本上包含了需要的所有的层次,而框架层的同构就是实现平衡,判断某个部分是否需要同构,并将同构与非同构部分融洽结合起来。
首先是SEO-friendly 的实现。其次第一次打开网页时不必等待JS 加载完成才能看到内容,页面的交互也能够得到即时响应,这就是速度上的优势。同构的运用使得服务端和客户端都使用同一套代码,有效的降低了维护成本。
早期客户端 JS 的作用就只是DOM 操作以及表单验证之类的事情,由服务端去实现业务逻辑、路由跳转、页面渲染等方面的事务。现阶段前端变的越发庞大,原先服务端需要处理的事情一部分被交由前端完成。可以发现早期是服务端臃肿,客户端轻便,现阶段则相反。
未来通过同构可以实现部分功能共享,比如页面的跳转、渲染、业务逻辑。让NodeJS去接管渲染层,后端部分向后再退一层,只负责数据持久化以及提供Restful API。
同构的第一要旨是全盘同构没有意义,服务端和客户端作为不同的平台,专注解决的是不同的问题,全盘同构会抹杀它们固有的差异,也就无法发挥各自的优势。因此,只需要在有交集的部分进行同构。对于内容同构的代码可以直接复用,内容不同构的封装成形式同构。
形式同构的实现思路就是抽象,来看下获取User Agent 字符串的例子。客户端通过navigator.userAgent 直接拿到字符串,服务端则使用req.get(“user-agent”) 。要想实现同构,我们可以在服务端构造一个全局的navigator 对象,模拟客户端环境。也可以封装一个 getUserAgent 函数,自行判断从何处取UserAgent 的值。
Cookies处理在我们的场景里,存在快捷通道,因为我们只专注首次渲染的同构,其它的操作可以放在浏览器端二次渲染的时候再处理。
重定向最少有三种以上的实现方式:
改变前端location 位置
前端使用pushState 方法,只改变路径并触发函数 ,但是不进行页面渲染
服务端采用302 重定向,通过封装函数判断环境以及重定向方法
现在来看下IMVC 所需要实现的目标:
用法简单,初学者也能快速上手
只维护一套ES2015+ 的代码
既是单页应用,优势多页应用(SPA + SSR)
可以部署到任意发布路径(Basename / RootPath)
一条命令启动完备的开发环境
一条命令完成打包 / 部署过程
IMVC 只是一个架构上的理念,理论上并不要求使用特定的技术栈,只需要实现期望的目标就行了。但是,要达成目标还是要做出一些选择,下面是我们现在的选择,当然未来可能升级或者做出改变。
1、Router: create-app = history + path-to-regexp
2、View: React = renderToDOM || renderToString
3、Model: relite = redux-like library
4、Ajax: isomorphic-fetch
可以看到我们的技术选型中使用了很多的React相关的技术,但是却并没有直接使用React 全家桶。
目前的React 全家桶其实是野生的,Facebook 官方并不会使用,只是认知度比较高而已。React-Router的理念也难以满足要求,查看view-source 会发现它没有实现同构。另外Redux 适用于大型应用,而我们的主要场景是中小型。
无论是Redux 还是 React-Router 升级都非常频繁,导致学习成本过高,需要封装一层更简洁的API。
面对社区千变万化的框架,正确的做法应该是业务开发使用一层专属的封装,底层运行时使用社区流行的方案。用create-app 替代 React-Router并不代表需要全盘重写,而是引用需要的部分,抛弃原本的理念。来看下Create-app的组成就了解了。
history 是react-router 依赖的底层库
path-to-regexp 是 expressjs 依赖的底层库
在View(React) 层和Model 层之外实现Controller 层
我们认为React 和 Redux 分别对应MVC 的 View 和 Model,它们都是同构的,我们需要的是实现 Controller 层的同构。
服务端和客户端进行 URL 的输入,Router 解析 URL 匹配对应的mvc组件
调用模块加载器加载组件,然后初始化 Controller
调用 Controller.init 方法,返回view 实例
调用view-engine 将 view 的实例根据环境渲染成 html 或 native-ui 等。
以上就是create-app 的同构理念。
由于客户端模块是异步加载而服务端是同步加载,要想在他们之间做到平衡就需要实现一个Create-app的配置。
服务端和浏览器端分别有自己的入口文件:client-entry.js 和 server.entry.js。我们只需提供不同的配置即可。
在服务端,加载 controller 模块的方式是 commonjsLoader;在浏览器端,加载 controller 模块的方式则为 webpackLoader。
在服务端和浏览器端,view-engine 也被配置为不同的 ReactDOM 和 ReactDOMServer。每个 controller 实例,都有 context 参数,它也是来自配置。通过这种方式,我们可以在运行时注入不同的平台特性。这样既分割了代码,又实现了形式同构。
我们认为正确的服务端渲染应该只有唯一的路由表和请求,仅根据输入的URL 和环境信息返回全部的渲染内容。
├── src // 源代码目录
│ ├── app-demo // demo目录
│ ├── app-abcd // 项目abcd 平台目录
│ │ ├── components // 项目共享组件
│ │ ├── shared // 项目共享方法
│ │ └── BaseController// 继承基类 Controller 的项目层 Controller
│ │ ├── home // 具体页面
│ │ │ ├── controller.js// 控制器
│ │ │ ├── model.js // 模型
│ │ │ └── view.js // 视图
│ │ ├── * // 其他页面
│ │ └── routes.js // abc 项目扁平化路由
│ ├── app-* // 其他项目
│ ├── components // 全局共享组件
│ ├── shared // 全局共享文件
│ │ └── BaseController // 基类Controller
│ ├── index.js // 全局js 入口
│ └── routes.js // 全局扁平化路由
├── static // 源码 build 的目标静态文件夹
上面展示的是 Create-app 的目录结构,它和Redux 的传统目录结构不同。每个页面都是单独的文件夹,包含Controller、model、view。整个项目页面使用routers 路由表串起来。create-app采取了「整站 SPA」的模式,全局只有一个入口文件index.js。
上面谈论的是IMVC 在运行时的功能和特点,下面看下IMVC 的具体工程实施。
node.js 运行时,npm 包管理
expressjs 服务端框架
babel 编译ES2015+ 代码到 ES5
webpack 打包和压缩源码
standard.js 检查代码规范
prettier.js + git-hook 代码自动美化排版
mocha 单元测试
使用webpack 的 node.js API 管理 webpack 进程,客户端采用express + webpack-dev-middleware 在内存里编译,服务端采用memory-fs + webpack + vm-module。服务端的webpack 编译到内存模拟的文件系统,再用 node.js 内置的虚拟机模块执行后得到新的模块。
问题根源:浏览器只在 dom-ready 之前会等待 css 资源加载后再渲染页面
问题描述:当单页跳转到另一个 url,css 资源还没加载完,页面显示成混乱布局
处理办法:将 css 视为预加载的 ajax 数据,以 style 标签的形式按需引入
优化策略:用 context 缓存预加载数据,避免重复加载
不使用webpack-only 的语法require.Ensure。在浏览器里require 被编译为加载函数,异步加载。在node.js 里require 是同步加载。
以代码的 hash 为文件名,增量发布。用webpack.stats.plugin.js 生成静态资源表。Express 使用stats.json 的数据渲染页面。
1、使用 npm-scripts 在 package.json 里完成 git、webpack、test、prettier等任务的串并联逻辑
2、npmstart 启动完整的开发环境
3、npmrun start:client 启动不带服务端渲染的开发环境
4、npmrun build 启动自动化编译,构建与压缩部署的任务
5、npmrun build:show-prod 用 webpack-bundle-analyzer 可视化查看编译结果。
今天的分享就到这里,喜欢请点赞~谢谢大家!有问题可以在评论区讨论。
相关推荐
推荐文章
近期活动
点击【阅读原文】回顾嘉宾演讲视频及PPT
以上是关于前端老牌框架衰退,IMVC(同构 MVC)成未来趋势?的主要内容,如果未能解决你的问题,请参考以下文章