关于前端:一文带你搞懂-SSR

0次阅读

共计 3880 个字符,预计需要花费 10 分钟才能阅读完成。

欲语还休,欲语还休,却道天凉好个秋 —-《丑奴儿·书博山道中壁》辛弃疾

什么是 SSR

ShadowsocksR?阴阳师?FGO?

Server-side rendering (SSR)是应用程序通过在服务器上显示网页而不是在浏览器中渲染的能力。服务器端向客户端发送一个齐全渲染的页面(精确来说是仅仅是 HTML 页面)。同时,联合客户端的 JavaScript bundle 使得页面能够运行起来。与 SSR 绝对的,还有一种 Client-side rendering(CSR)。CSR 和 SSR 的最大区别只是提供 rendering 的是客户端还是服务端,其本质还有一种货色。故以下如果没有着重提出 CSR 和 SSR 不一样的中央,则默认是统一的。

为什么要 SSR

得益于 React 等前端框架的倒退,前后端拆散,webpack 等编译工具的风行,以及 ajax 实现页面的部分刷新,使得咱们当初的应用程序不再像已经的应用程序个别须要从服务端获取页面,能够动静的批改部分的页面数据,防止页面频繁跳转影响用户体验等问题。也就是 SPA 越来越成为支流应用程序模型。
然而 SPA 的应用,除了以上提到的劣势以外,必然会带来劣势。譬如:

  1. 因为须要在页面加载之前就加载所有页面须要的 JavaScript 库,这使得首次关上页面所须要的工夫比拟久;
  2. 须要研发专门针对于 SPA 的 Web 框架(各种具备 SSR 能力的框架,包含 Next.js 等)
  3. 搜索引擎爬虫
  4. 浏览器历史记录的问题(基于 pushState 的各种 router

为了解决上述提到的 1. 和 3. 的问题,SSR 开始登上历史的舞台。

SSR 怎么做

基于上述的实践,咱们能够设计一个具备 SSR 性能的 React 框架。

首先,咱们通过 create-react-app 命令初始化一个 React 我的项目,能够把初始化实现后的我的项目了解为具备最简略性能的我的项目。咱们将基于该我的项目去实现一个 SSR 的性能。

# Yarn
$ yarn create react-app ssr-demo

⚠️ 同学们实际的时候须要留神,以后版本的 cra 命令新建我的项目的时候,启动会报相似于 Mini.... is not a function的问题。这是因为 mini-css-extract-plugin该插件版本更新导致的,只须要在 package.json外面通过 resolutions 限度 mini-css-extract-plugin 的版本为 2.4.5 即可

生成我的项目的目录如下:

./
├── README.md
├── build
├── node_modules
├── package.json
├── public
├── src
└── yarn.lock

曾经主动装置完依赖,启动我的项目咱们能够在「本地环境」看到一个最简略的页面。

接下来,咱们去实现一个 SSR 性能。首先,咱们须要装置 express(如果是 CSR 的话就不须要这一步)

yarn add express

装置实现后,咱们须要在 server/index.js文件中编写如下代码

import express from "express";
import serverRenderer from "./serverRenderer.js";

const PORT = 3000;
const path = require("path");

const app = express();
const router = express.Router();

// 当爬虫的申请进来的时候,把所有申请导向 serverRenderer 路由
router.use("*", serverRenderer);

app.use(router);
app.listen(PORT, () => console.log(`listening on: ${PORT}`));

其中 serverRenderer 该文件内容如下:

import React from "react";
import ReactDOMServer from "react-dom/server";

import App from "../src/App";

const path = require("path");
const fs = require("fs");

export default (req, res, next) => {
  // 获取以后我的项目的 HTML 模板文件门路
  const filePath = path.resolve(__dirname, "..", "build", "index.html");

  // 读取该文件
  fs.readFile(filePath, "utf8", (err, htmlData) => {if (err) {console.error("err", err);
      return res.status(404).end();}

    // 借助 react-dom 依赖下的办法将 JSX 渲染成 HTML string
    const html = ReactDOMServer.renderToString(<App />);

    // 将 HTML string 替换到 root 中
    return res.send(htmlData.replace('<div id="root"></div>', `<div id="root">${html}</div>`)
    );
  });
};

如上,咱们实现了一个非常简单的具备 SSR 性能的服务端。
然而仅仅如此是不够的,咱们还须要在根目录下,新建 parser.jsESM 转成 CommonJS 运行起来,代码如下:

require("ignore-styles");
require("@babel/register")({ignore: [/(node_modules)/],
  presets: ["@babel/preset-env", "@babel/preset-react"],
});

require("./server");

解释一下下面引入的包的作用:

  • @babel/register:该依赖会将 node 后续运行时所须要 require 进来的扩大名为 .es6.es.jsx.mjs.js 的文件将由 Babel 主动转换。
  • ignore-styles:该依赖也是一个 Babel 的钩子,次要用于在 Babel 编译的过程中疏忽款式文件的导入。

在通过上述的操作之后,咱们先 yarn build出咱们的产物,而后通过 node parser.js 来启动 SSR 服务。


通过上述的操作之后,咱们设计出了一个非常简单的但正当的 SSR 服务端。作为比照,咱们在这里简略的和 Next.js 做比照。

Next.js 我的项目的根目录中的 package.json 中,咱们能够看到同样抉择了 express 作为服务器.

...
"eslint-plugin-react-hooks": "4.2.0",
"execa": "2.0.3",
"express": "4.17.0",
"faker": "5.5.3",
...

咱们能够在 ~/packages/next/server/next.ts文件夹中,发现 Next.js会通过 createServer办法,启动一个 NextServer 对象,该对象负责启动服务器以及渲染模板模板。
命令调用如下:

[Next.js](https://nextjs.org/docs/basic-features/pages#server-side-rendering)的官网中,咱们能够看到其反对在页面通过 getServerSideProps函数,来实现动静获取接口数据。其实,在大多数反对 SSR 的框架库中,都有相似的设计。因为 SPA 的利用,不免须要通过服务端获取动态数据,并渲染页面,而实现渲染动态数据的 SSR 的设计思路都较为统一。即在该页面的组件同一文件中导出一固定办法,并且 return 某一固定格局。框架会将该数据用作初始数据对页面进行 SSR 渲染。


咱们以 Next.js 为例,理解了 SSR 的大抵设计思路,那么接下来咱们理解一下 CSR 的大抵思路.。

CSR 能够了解为阉割版的 SSR,只实现了 SSR 的预渲染性能。个别用于动态网站,不具备动静获取数据的性能。

CSR 的渲染思路同 SSR 统一,不同点在于 SSR 是须要装置 express而 CSR 不须要装置 express。这也就导致了 CSR 和 SSR 在部署流程上的不同。SSR 我的项目如 Next.js利用在执行完 build 命令后,能够通过 start 命令执行启动服务器,不再须要配合 nginx 的反向代理。而 CSR 我的项目如 Umi依然须要 nginx 的代理。

CSR 最大的不同点在于编译后产物的不同。通常一个前端我的项目在编译后的产物包含一下:

  • bundle.js或者 chunk.js
  • index.html
  • index.css
  • public/*
  • 其余相干文件,如 rss.xml

而具备 CSR 的我的项目通过编译后,会有更多的 HTML文件,这些文件的架构会依照路由生成。譬如:咱们目前路由如下:

  • /a
  • /b

别离对应 ComponentAComponentB,那么在咱们编译后产物中会生成 a.htmlb.html。在咱们将产物部署到 nginx 服务上后,就能够实现预渲染性能。

要实现以上性能,最重要的步骤如下:

  • 获取到以后我的项目的路由
  • 获取到路由对应的组件,如果组件未编译过,须要编译
  • 借助 react-dom 的能力将 JSX 渲染成 HTML,并插入到模板 HTML
  • 在编译后产物中依据路由创立文件夹,并将后果 HTML 生成到对应门路中

到这里,咱们理解了整个 SSR 的流程,置信大家对 SSR 都有了肯定水平的理解。目前社区的绝大部分框架都不须要咱们自行去做 SSR。咱们理解渲染过程有助于咱们在应答各种层出不穷的框架时,可能以不变应万变。

正文完
 0