React SSR

    在这篇文章中,我们将向你展示如何使用 WasmEdge 的 QuickJS 运行时来实现 React SSR 的能力。与 Docker + Linux + nodejs + v8 的方案相比,WasmEdge 要轻量得多(仅 1% 的占用),也更安全,能够提供更好的资源隔离和管理机制,并有着和非 JIT(同时是安全的)近似的性能。

    本文将包含对静态渲染和流式渲染两种渲染方式的介绍。静态渲染相对容易理解和实现。而流式渲染则可以提供更好的用户体验,因为用户在浏览器前等待结果时,可以优先看到生成的部分内容。

    本示例的源代码可以在 GitHub 仓库的 文件夹中找到。它展示了运行于 WasmEdge 的 JavaScript 应用程序,是如何编排 HTML 模板并将其渲染成 HTML 字符串的。

    文件 component/Home.jsx 里是 React 的主页模板。

    中会包含 提供的模板,作为页面的一部分。

    1. import React from 'react';
    2. class Page extends React.Component {
    3. render() {
    4. const { dataList = [] } = this.props;
    5. return (
    6. <div>
    7. <div>This is page</div>
    8. </div>
    9. );
    10. }
    11. };
    12. export default Page;
    1. import React from 'react';
    2. import {renderToString} from 'react-dom/server';
    3. import Home from './component/Home.jsx';
    4. const content = renderToString(React.createElement(Home));
    5. console.log(content);

    目录中的 rollup.config.js 文件和 文件用于把 React SSR 的所有依赖和组件打包成一个 WasmEdge 可用的 JavaScript 文件。你可以使用 npm 命令来进行构建,构建产物会输出到 dist/main.js 文件里。

    1. npm install
    2. npm run build

    要运行这个例子,请在命令行中执行以下命令。你会看到,所有的模板成功合成了一个 HTML 字符串。

    本示例的源代码可以在 GitHub 仓库的 example_js/react_ssr_stream 文件夹中找到。它展示了运行于 WasmEdge 的 JavaScript 应用程序,是如何流式地把 HTML 模板渲染成 HTML 字符串的。

    文件 是 React 的主页模板。当外层的 HTML 渲染好并返回给用户 2s 之后,它才会开始“懒”加载内层的页面模板。

    1. import React, { Suspense } from 'react';
    2. async function sleep(ms) {
    3. return new Promise((r, _) => {
    4. setTimeout(() => r(), ms)
    5. });
    6. }
    7. async function loadLazyPage() {
    8. await sleep(2000);
    9. return await import('./LazyPage.jsx');
    10. }
    11. class LazyHome extends React.Component {
    12. render() {
    13. let LazyPage1 = React.lazy(() => loadLazyPage());
    14. return (
    15. <html lang="en">
    16. <head>
    17. <meta charSet="utf-8" />
    18. <title>Title</title>
    19. </head>
    20. <body>
    21. <div>
    22. <div> This is LazyHome </div>
    23. <Suspense fallback={<div> loading... </div>}>
    24. <LazyPage1 />
    25. </Suspense>
    26. </div>
    27. </body>
    28. </html>
    29. );
    30. }
    31. }
    32. export default LazyHome;
    1. import React from 'react';
    2. class LazyPage extends React.Component {
    3. render() {
    4. return (
    5. <div>
    6. <div>
    7. This is lazy page
    8. </div>
    9. </div>
    10. );
    11. }
    12. }

    main.mjs 文件会启动一个异步的 HTTP 服务器,然后把 HTML 页面渲染成多段放入响应。当一个 HTTP 请求进来的时候,handle_client() 函数就会被调用来渲染 HTML,并以流的形式返回结果。

    1. import { renderToPipeableStream } from 'react-dom/server';
    2. import * as http from 'wasi_http';
    3. import * as net from 'wasi_net';
    4. import LazyHome from './component/LazyHome.jsx';
    5. async function handle_client(s) {
    6. let resp = new http.WasiResponse();
    7. resp.headers = {
    8. "Content-Type": "text/html; charset=utf-8"
    9. }
    10. renderToPipeableStream(<LazyHome />).pipe(resp.chunk(s));
    11. }
    12. async function server_start() {
    13. print('listen 8001...');
    14. let s = new net.WasiTcpServer(8001);
    15. for (var i = 0; i < 100; i++) {
    16. let cs = await s.accept();
    17. handle_client(cs);
    18. }
    19. }
    20. server_start();

    目录中的 文件和 package.json 文件用于把 React SSR 的所有依赖和组件打包成一个 WasmEdge 可用的 JavaScript 文件。你可以使用 npm 命令来进行构建,构建产物会输出到 dist/main.js 文件里。

    要运行这个例子,请在命令行上执行以下命令来启动服务器。

    1. cd example_js/react_ssr_stream
    2. nohup wasmedge --dir .:. ../../target/wasm32-wasi/release/wasmedge_quickjs.wasm dist/main.mjs &

    然后通过 curl 或浏览器发送一个 HTTP 请求。

    1. curl http://localhost:8001

    结果如下所示。该服务首先返回一个 HTML 页面,里面包含一个空的内层部分(即 loading 部分)。然后在 2s 后返回内层部分的 HTML 内容,以及将它显示出来的 JavaScript 代码。

    1. % Total % Received % Xferd Average Speed Time Time Time Current
    2. Dload Upload Total Spent Left Speed
    3. 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
    4. 100 211 0 211 0 0 1029 0 --:--:-- --:--:-- --:--:-- 1024
    5. 100 275 0 275 0 0 221 0 --:--:-- 0:00:01 --:--:-- 220
    6. 100 547 0 547 0 0 245 0 --:--:-- 0:00:02 --:--:-- 245
    7. 100 1020 0 1020 0 0 413 0 --:--:-- 0:00:02 --:--:-- 413