IMVC(同构 MVC)的前端实践

内容来源:2017 年 3 月 11 日,携程研发高级经理古映杰在“携程技术沙龙 | 新一代前端技术实践”进行《IMVC(同构 MVC)的前端实践》演讲分享。IT 大咖说(微信id:itdakashuo)做为独家视频合做方,经主办方和讲者审阅受权发布。
css

阅读字数:2738 | 7分钟阅读html

嘉宾演讲视频及PPT回顾:suo.im/4VPTN5前端

摘要

随着 Backbone 等老牌框架的逐渐衰退,前端 MVC 发展缓慢,有逐渐被 MVVM/Flux 所取代的趋势。然而,纵观近几年的发展,能够发现一点,React / Vue 和 Redux / Vuex 是分别在 MVC 中的 View 层和 Model 层作了进一步发展。若是 MVC 中的 Controller 层也推动一步,将获得一种升级版的 MVC,咱们称之为 IMVC(同构 MVC)。node

IMVC 能够实现一份代码在服务端和浏览器端皆可运行,具有单页应用和多页应用的全部优点,而且可在这两种模式里经过配置项进行自由切换。配合 Node.js、Webpack、Babel 等基础设施,咱们能够获得相比以前更加完善的一种前端架构。react

IMVC(同构MVC)

IMVC的“I”指的是ISOMORPHIC ,也就是同构,最初它是数学上的概念,描述两个对象之间的某种一致性。在前端领域中ISOMORPHIC JAVASCRIPT 则是指一段前端代码在客户端和服务端均可运行,它在2012年就已经被提出,算是历史悠久的概念了。webpack

同构的种类

同构分为内容同构和形式同构,内容同构指一样的代码在客户端和服务端作等价的事情。形式同构经过判断所处环境来执行某段代码,也就是说在客户端或者服务端始终有一部分代码没有执行。git

同构的层次

同构并非一种非是即彼的判断,它更像是光谱,既能够是小范围的也能够是大范围。小范围的同构,例如原生的js 在浏览器和Node 中代码并无差别,只是DOM API 和 Node API 不一样而已,这就是函数层面的同构,即代码片断相同。还有一种特性层的同构,指的是业务中不一样职能特性的同构,好比Vue 2.0在客户端和服务端都能运行,这就是Vue 这个特性层的同构。另外就是框架层同构,框架基本上包含了须要的全部的层次,而框架层的同构就是实现平衡,判断某个部分是否须要同构,并将同构与非同构部分融洽结合起来。web

同构的价值

首先是SEO-friendly 的实现。其次第一次打开网页时没必要等待JS 加载完成才能看到内容,页面的交互也可以获得即时响应,这就是速度上的优点。同构的运用使得服务端和客户端都使用同一套代码,有效的下降了维护成本。ajax

同构是将来的趋势

早期客户端 JS 的做用就只是DOM 操做以及表单验证之类的事情,由服务端去实现业务逻辑、路由跳转、页面渲染等方面的事务。现阶段前端变的愈加庞大,原先服务端须要处理的事情一部分被交由前端完成。能够发现早期是服务端臃肿,客户端轻便,现阶段则相反。express

将来经过同构能够实现部分功能共享,好比页面的跳转、渲染、业务逻辑。让NodeJS去接管渲染层,后端部分向后再退一层,只负责数据持久化以及提供Restful API。

同构的实现策略

同构的第一要旨是全盘同构没有意义,服务端和客户端做为不一样的平台,专一解决的是不一样的问题,全盘同构会抹杀它们固有的差别,也就没法发挥各自的优点。所以,只须要在有交集的部分进行同构。对于内容同构的代码能够直接复用,内容不一样构的封装成形式同构。

形式同构的实现思路

形式同构的实现思路就是抽象,来看下获取User Agent 字符串的例子。客户端经过navigator.userAgent 直接拿到字符串,服务端则使用req.get(“user-agent”) 。要想实现同构,咱们能够在服务端构造一个全局的navigator 对象,模拟客户端环境。也能够封装一个 getUserAgent 函数,自行判断从何处取UserAgent 的值。

Cookies处理在咱们的场景里,存在快捷通道,由于咱们只专一首次渲染的同构,其它的操做能够放在浏览器端二次渲染的时候再处理。

重定向最少有三种以上的实现方式:

  1. 改变前端location 位置

  2. 前端使用pushState 方法,只改变路径并触发函数 ,可是不进行页面渲染

  3. 服务端采用302 重定向,经过封装函数判断环境以及重定向方法

IMVC的目标

如今来看下IMVC 所须要实现的目标:

  1. 用法简单,初学者也能快速上手

  2. 只维护一套ES2015+ 的代码

  3. 既是单页应用,优点多页应用(SPA + SSR)

  4. 能够部署到任意发布路径(Basename / RootPath)

  5. 一条命令启动完备的开发环境

  6. 一条命令完成打包 / 部署过程

IMVC的技术选型

IMVC 只是一个架构上的理念,理论上并不要求使用特定的技术栈,只须要实现指望的目标就好了。可是,要达成目标仍是要作出一些选择,下面是咱们如今的选择,固然将来可能升级或者作出改变。

一、Router: create-app = history + path-to-regexp

二、View: React = renderToDOM || renderToString

三、Model: relite = redux-like library

四、Ajax: isomorphic-fetch

为何不直接使用 REACT 全家桶

能够看到咱们的技术选型中使用了不少的React相关的技术,可是却并无直接使用React 全家桶。

目前的React 全家桶实际上是野生的,Facebook 官方并不会使用,只是认知度比较高而已。React-Router的理念也难以知足要求,查看view-source 会发现它没有实现同构。另外Redux 适用于大型应用,而咱们的主要场景是中小型。

不管是Redux 仍是 React-Router 升级都很是频繁,致使学习成本太高,须要封装一层更简洁的API。

用create-app 替代 React-Router

面对社区变幻无穷的框架,正确的作法应该是业务开发使用一层专属的封装,底层运行时使用社区流行的方案。用create-app 替代 React-Router并不表明须要全盘重写,而是引用须要的部分,抛弃本来的理念。来看下Create-app的组成就了解了。

  1. history 是react-router 依赖的底层库

  2. path-to-regexp 是 expressjs 依赖的底层库

  3. 在View(React) 层和Model 层以外实现Controller 层

咱们认为React 和 Redux 分别对应MVC 的 View 和 Model,它们都是同构的,咱们须要的是实现 Controller 层的同构。

Create-app的同构理念

  1. 服务端和客户端进行 URL 的输入,Router 解析 URL 匹配对应的mvc组件

  2. 调用模块加载器加载组件,而后初始化 Controller

  3. 调用 Controller.init 方法,返回view 实例

  4. 调用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 参数,它也是来自配置。经过这种方式,咱们能够在运行时注入不一样的平台特性。这样既分割了代码,又实现了形式同构。

Create-app 的服务端渲染

咱们认为正确的服务端渲染应该只有惟一的路由表和请求,仅根据输入的URL 和环境信息返回所有的渲染内容。

Create-app 的目录结构

├── 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。

ISOMORPHIC-MVC的工程化实施

上面谈论的是IMVC 在运行时的功能和特色,下面看下IMVC 的具体工程实施。

  1. node.js 运行时,npm 包管理

  2. expressjs 服务端框架

  3. babel 编译ES2015+ 代码到 ES5

  4. webpack 打包和压缩源码

  5. standard.js 检查代码规范

  6. prettier.js + git-hook 代码自动美化排版

  7. mocha 单元测试

如何实现代码实时热更新

使用webpack 的 node.js API 管理 webpack 进程,客户端采用express + webpack-dev-middleware 在内存里编译,服务端采用memory-fs + webpack + vm-module。服务端的webpack 编译到内存模拟的文件系统,再用 node.js 内置的虚拟机模块执行后获得新的模块。

如何处理 css 按需加载

问题根源:浏览器只在 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 的数据渲染页面。

如何管理命令行任务

一、使用 npm-scripts 在 package.json 里完成 git、webpack、test、prettier等任务的串并联逻辑

二、npmstart 启动完整的开发环境

三、npmrun start:client 启动不带服务端渲染的开发环境

四、npmrun build 启动自动化编译,构建与压缩部署的任务

五、npmrun build:show-prod 用 webpack-bundle-analyzer 可视化查看编译结果。

相关文章
相关标签/搜索