1. Node 应用架构设计
2. React 同构
4. 前端代码上CDN、代码发现
6. 灰度环境
关于 Node
Node 是一个基于 Chrome V8 引擎的 JavaScript 运行时。它诞生于2009年,Node 第一次把JavaScript带入到后端服务器开发,另外还可以通过它编写工具,比如代码打包工具,但是它诞生的最初目的还是为了实现高性能 Web 服务器。它内部实现的异步 IO、事件驱动就是为高性能 Web 服务而生的。
需求背景
2019年底网易智慧企业正在打造一款 SCRM 产品—网易互客(https://huke.163.com),它最初主要有3块需求:
1、互客平台。
3、互客官网。
前两部分对交互要求比较高,有一些需求决定技术上需要优先使用单页应用的形式。官网又是对 SEO 有需求的,所以需要有同构渲染的能力(前端使用 React 框架);另外鉴于目前的技术架构对开发效率的提升已经形成瓶颈,所以考虑使用新的技术方案,来完全解放前后端的生产力,最终考虑使用 Node 来实现前后端的完全分离,彻底解决之前前端要写 Java 模版文件和前后端对页面数据理解不一致尴尬局面。
1、页面渲染。
3、页面初始必要数据填充。
另外还有一个目标是通过这个项目,逐步完善智慧企业的 Node 工程工具体系,最终形成智慧企业自己的 Node 生态。
设计和实现
简单介绍下一个完整的用户请求的访问路径。首先用户请求到网关,网关根据 URL 转发规则转发到 Node 或者 Java 应用,从而完成一次页面访问或接口请求。这里面涉及到路由的设计,页面和接口的 URL 要能够通过 path 区分。
另外一个比较重要的问题是用户的登录信息,我们使用了比较偏传统的方案,用户登录功能在 Java 端实现,当用户访问页面时,Node 会检查 cookie 里的登录 token,并进行校验,如果 token 不存在或不正确,就给用户 redirect 到登录页面,当用户填写完信息点击登录按钮时,调用 Java 端的登录接口进行登录,成功后 Java 端会给登录请求的响应带上 cookie ,这样前端、Node 端、Java 端的登录信息就能串起来。
关于同构
一套代码既可以在服务端运行又可以在客户端运行,在服务器端执行一次,用于实现服务器端渲染,在客户端再执行一次,用于接管页面交互,这就是同构应用。简而言之, 就是服务端直出和客户端渲染的组合, 能够充分结合两者的优势,并有效避免两者的不足。
一般前端框架是需要对 DOM 进行操作的,在浏览器环境当然没有问题,而在Node 是没有 DOM 这个概念的,那 React 是如何实现在 Node 端进渲染的呢?这因为 React 中引入的虚拟 DOM,虚拟 DOM 是真实 DOM 的一个 JavaScript 对象映射,React 在做页面操作时,实际上不是直接操作 DOM,而是操作虚拟 DOM,也就是操作普通的 JavaScript 对象,这就使得 SSR 成为了可能。在 Node 端 React 把虚拟 DOM 输出为字符串,而在浏览器端 React 把虚拟 DOM 映射为真实 DOM,完成页面渲染。
结合需求我们选择 `renderToString` 方法。
主要介绍下我们的实现的不一样的地方,首先是配置方式:
```json
fishssr: {
routes: [
{
path: ‘/admin/*’,
Component: () => (require(‘@/page/admin’).default),
controller: ‘page.admin’
},
{
path: ‘/user/*’,
Component: () => (require(‘@/page/user’).default),
controller: ‘user.h5Page’,
},
],
// 页面模版文件路径
template: ‘screen/index.html’,
// 服务端渲染打包后的js文件
serverJs: resolvePath(‘dist/Page.server.js’),
}
```
path: `/admin/*`、`/user/*` 分别对应了一个单页应用。
Component: 对应了页面的 React 组件,内部会处理初始数据,转化为store 的 preloadedState 或 props,里面使用前端路由。
template: 页面的模版文件,内部 `stream` 就是 Node 渲染 React 页面组件之后得到的字符串,文件的内容大致如下:
```html
<!DOCTYPE html>
<html lang=‘zh-CN’>
<head>
<title>网易互客</title>
<link rel=‘stylesheet’ href=‘/css/Page.css’ />
</head>
<body>
<div id=‘app’>
{{stream | safe}}
</div>
<script>
window.__INITIAL_DATA__ = {{ initialData | safe}};
</script>
<script src=‘/js/runtime~Page.js’></script>
<script src=‘/js/Page.js’></script>
</body>
</html>
```
```
const clientRender = async () => {
ReactDOM.hydrate(
<>
{
Routes.map(route => {
const { path, Component } = route
const isMatch = matchPath(window.location.pathname, route)
if ( !isMatch ) {
return null
}
const ActiveComponent = Component()
const WrappedComponent = GetInitialProps(ActiveComponent)
return <WrappedComponent key={path} />
})
}
</>, document.getElementById('app'))
}
const serverRender = async (params) => {
const { initData, path, url } = params
const ActiveComponent = getComponent(Routes, path)()
return (
<StaticRouter location={url} context={initData}>
<ActiveComponent {... initData} />
</StaticRouter>
)
}
export default __isBrowser__ ? clientRender() : serverRender
```
总结
目前使用这个方案的产品**网易互客**已经上线,这个方案解决了文章开头所说技术和业务需求的,同时它带来的新的前后端配合模式也极大的提高了不仅仅是前端的开发效率,对后端来说也是非常友好的。同时前端也可拓宽自己边界,能够承接更多需求,比如我们运营系统、功能性 API,比如微信 JS-SDK 认证,之前只能放在后端,现在放在 Node 端,前端开发起来更加灵活,减少很大的沟通成本。但是目前作为对外服务 Node 应用只有这些还是不够的,还是需要很多工程工具的支持。
听网易CTO讲述前沿观察,看最有价值技术干货,学网易最新实践经验。网易智慧企业技术+,陪你从思考者成长为技术专家。