1. 首页

搭建ssr并部署serverless

前言:

掘金用vue,知乎、简书用react实现ssr,淘宝网用kissy,京东用nerv,对于seo需求很大的项目,ssr是必须的。

sf同城物流组分享视频链接

在线周杰伦歌曲播放

ssr流程

网上别人画的流程图

难点


1、开发环境的搭建,保证本地也能支持ssr功能 2、server端路由和client端一致 3、全局状态同步(context) 4、样式注入的处理 5、如何刷新页面时nodejs调用接口,页面加载之后不请求接口

对应https://github.com/fridaydream/blog-site中的step_01到step_05分支

技术栈

react、mobx、mui、koa、ejs

用到的技术栈

搭建步骤

服务server

step_01: 搭建基础服务

前端入口文件代码(index.tsx):


import React from 'react' import ReactDOM from 'react-dom' import App from './App' const root = document.getElementById('root') const renderMethod = module.hot ? ReactDOM.render : ReactDOM.hydrate const render = (Component: React.ComponentType) => { renderMethod(<Component />, root) } render(App)

给koa用的react入口文件代码(server-entry.tsx):


import React from 'react' import App from './App' export default <App />

server就是把组件入口暴露出去,通过webpack打包成nodejs使用的代码

开发环境:webpack-dev-server启动csr的8888端口,koa启动server的3333端口,通过webpack处理react项目的入口文件server-entry.tsx,打包libraryTarget设置为commonjs2,监听文件变化,生成nodejs的bundle文件。

生产环境:将react代码打包到dist文件夹中,同时把server-entry.tsx打包成nodejs模块,就可以在nodejs中require模块,运行时生成静态html吐给浏览器

step_02: server端路由和client端一致

如技术栈中,浏览器路由browerRouter,koa路由staticRouter,这样就可以在同构中复用一套代码

server-entry.tsx改成


import React from 'react' import { StaticRouterContext } from 'react-router' import { StaticRouter } from 'react-router-dom' import App from './pages/App' export default (routerContext: StaticRouterContext, url: string) => ( <StaticRouter context={routerContext} location={url}> <App /> </StaticRouter> )

import ReactDomServer from 'react-dom/server' const routerContext: RouterContext = {} const app = createApp(routerContext, ctx.url) const appString = ReactDomServer.renderToString(app)

koa中通过ctx.url拿到刷新页面的路由,传入server-entry.tsx函数中,react-dom中提供的renderToString方法得到静态页面内容

step_03: 全局状态同步(context)

mobx的状态管理中


import { observable, action, toJS } from 'mobx' export default class ThemeStore { constructor({ theme } = { theme: 'light' }) { this.theme = theme; } @observable theme @action setTheme(newTheme: string) { this.theme = newTheme } toJson() { return { theme: this.theme } } }

在koa通过调用ThemeStore类上面的toJson方法,拿到所有初始化状态值(redux中也有类似方法),通过ejs注入window.__INITIAL__STATE__=<%%- initialState %>,浏览器得到静态html之后,执行自己的前端代码,拿到window.__INITIAL__STATE__同步到浏览器中的mobx store中,这样就有了注水和脱水的过程,如果状态不一致,会出现页面闪动(体验不好,有可能就是bug了)。

step_04: 样式注入的处理

server-entry.tsx改成


import React from 'react' import { StaticRouterContext } from 'react-router' import { StaticRouter } from 'react-router-dom' import { useStaticRendering } from 'mobx-react-lite' import { GenerateId, Jss } from 'jss'; import { ThemeProvider, StylesProvider } from '@material-ui/core/styles'; import App from '@/pages/App' import { storesContext } from "@/store/store"; import { IStores } from "@/store/types"; import { theme } from './theme' // 让mobx在服务端渲染的时候不会重复数据变换 useStaticRendering(true) // 使用静态的渲染 export default (stores: IStores, sheetsRegistry: {}, sheetsManager: {}, jss: Jss, generateClassName: GenerateId, routerContext: StaticRouterContext, url: string) => ( <storesContext.Provider value={stores}> <StaticRouter context={routerContext} location={url}> <StylesProvider sheetsRegistry={sheetsRegistry} jss={jss} generateClassName={generateClassName} sheetsManager={sheetsManager}> <ThemeProvider theme={theme}> <App /> </ThemeProvider> </StylesProvider> </StaticRouter> </storesContext.Provider> )

nodejs中server-render.ts


import { create, SheetsRegistry } from 'jss'; const sheetsRegistry = new SheetsRegistry(); const app = createApp(stores, sheetsRegistry, sheetsManager, jss, generateClassName, routerContext, ctx.url)

@material-ui/core/styles中提供了ThemeProvider处理主题和StylesProvider处理样式,执行createApp之后,sheetsRegistry.toString()就得到静态的css样式了,注入到style标签中

step_05: 如何刷新页面时nodejs调用接口,页面加载之后不请求接口

如ssr流程图中,刷新页面,浏览器对服务器发起get请求,服务器根据path查找到具体组件,调用组件的getInitialProps方法,数据注入组件,返回静态html字符串,用户看到界面,这时候用户是不能操作界面的,之后浏览器执行代码,同步store,给元素上面绑定事件,生成新样式,删除之前注入的样式,这个时候用户才能操作界面,之后的跳转都是走前端路由

koa拿到对应的组件执行上面的getInitialProps方法,调用接口,浏览器执行时判断window.location.pathname === window.__SSRPATH__是否相等,相等不执行getInitialProps方法,之后页面前端跳转新路由时,需要执行getInitialProps方法


const getComponent = (Routes: RouteItem[], path: string) => { // 根据请求的path来匹配到对应的component const activeRoute = Routes.find(route => matchPath(path, route)) || { Component: () => NotFound } // 找不到对应的组件时返回NotFound组件 const activeComponent = activeRoute.Component return activeComponent } // 服务端渲染 根据ctx.path获取请求的具体组件,调用getInitialProps并渲染 const ActiveComponent = getComponent(Routes, ctx.path)() ActiveComponent.getInitialProps ? await ActiveComponent.getInitialProps(ctx) : {}

export function requestInitialData(props: InitialStoresProps & QueryProps, component: ComponentType) { useEffect(() => { // 客户端运行时 if (typeof window !== 'undefined') { console.log('window.location.pathname !== window.__SSRPATH__', window.location.pathname, window.__SSRPATH__) // 非同构时,并且getInitialProps存在 if (window.location.pathname !== window.__SSRPATH__ && component.getInitialProps) { console.log('post===') component.getInitialProps(props) } } }, [1]); }

当然还有接口参数的获取,koa中参数从url中来,浏览器中接口参数从mobx中来

小总结

服务server

别人写的,特别好。

疑问

module.hot的概念 热更新还是热重载
  • 热重载live reload: 就是当修改文件之后,webpack自动编译,然后浏览器自动刷新->等价于页面window.location.reload()
  • 热更新HMR: 热重载live reload并不能够保存应用的状态(states),当刷新页面后,应用之前状态丢失。举个列子:页面中点击按钮出现弹窗,当浏览器刷新后,弹窗也随即消失,要恢复到之前状态,还需再次点击按钮。而webapck热更新HMR则不会刷新浏览器,而是运行时对模块进行热替换,保证了应用状态不会丢失,提升了开发效率

参考文章

预渲染和服务端渲染的区别

服务端渲染,首先得有后端服务器(一般是 Node.js)才可以使用,如果我没有后端服务器,也想用在上面提到的两个场景,那么推荐使用预渲染。

预渲染与服务端渲染唯一的不同点在于渲染时机,服务端渲染的时机是在用户访问时执行渲染(即实时渲染,数据一般是最新的),预渲染的时机是在项目构建时,当用户访问时,数据不是一定是最新的(如果数据没有实时性,则可以直接考虑预渲染)。

预渲染(Pre Render)在构建时执行渲染,将渲染后的 HTML 片段生成静态 HTML 文件。无需使用 web 服务器实时动态编译 HTML,适用于静态站点生成。

参考文章

服务端渲染css是否按需加载

通过2个不同路由下,搜索样式,发现A页面中样式在B页面中不存在,说明css是按需加载的

ssr页面有异步请求时,如何控制客户端不再次发送,next中的处理方式

next在server注水时给window.isServer=true,如果isServer===true,浏览器中不执行getInitialProps方法,前端跳转路由时,将window.isServer设置成false

canvas在ssr中处理方式

服务server

或者通过部分ssr方式

服务端与页面状态不一致的情况下,页面会整体重新渲染还是局部渲染

页面没有重新渲染,不一致时,后面的都是执行一次客户端代码,通过在组件中和兄弟组件中console.log只执行了一次

加入css,浏览器渲染时需要先删除服务端渲染的css样式,删除后重新加入样式是否会重绘

通过观察浏览器performance,通过服务端样式和客户端样式一样,重排时间和重绘时间几乎可以忽略不计。大页面没有尝试过

加入css,浏览器渲染时需要先删除服务端渲染的css样式,删除后重新加入样式是否会重绘
不是css in js 的css库怎么处理样式注入

NextJS 内置了styled-jsx方案

antd等组件库可以使用styled-components方案

公共接口怎么处理(常量、用户信息)

没有好的解决方案

部署serverless

now.sh是 ZEIT 推出的一款支持 Docker、Nodejs、静态页面的全球化实时部署服务(Realtime Global Deployments)

可以通过github部署和now终端命令部署

在根目录下面创建now.json


{ "version": 2, "builds": [ { "src": "server/server.ts", "use": "@now/node" }, { "src": "server/list.ts", "use": "@now/node" } ], "routes": [ { "src": "/api/video/info", "dest": "server/list.ts" }, { "src": "/(.*)", "dest": "server/server.ts" } ] }

server.ts添加module.exports = app.callback(),迁移nodejs项目很方便,就是报错信息不好找。

部署参考文章

now.sh介绍文件

作者:fridaydream
链接:https://juejin.im/post/6893880106100457480

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:搭建ssr并部署serverless

链接:https://www.javascriptc.com/4647.html

« cookie localStorage sessionStorage IndexDB合集
拿尤大大写的正则表达式处理一个业务上的问题»
Flutter 中文教程资源

相关推荐

QR code