如何在 gulp/node 中预渲染 React 应用程序?

How can I pre-render a react app in gulp/node?

如何以编程方式在 gulp 和节点 12 中呈现 React 应用程序?

我接管了一个旧的 React (0.12.0) 应用并将其升级到最新版本。这也涉及升级到 ES6。 React代码本身就搞定了,但是我们还需要prerender应用(app是交互文档,必须要被搜索引擎抓取)。

以前,gulp 构建过程 运行 browserify 代码,然后 运行 使用 vm.runInContext:

// source code for the bundle
const component = path.resolve(SRC_DIR + subDir, relComponent);

vm.runInNewContext(
  fs.readFileSync(BUILD_DIR + 'bundle.js') + // ugly
    '\nrequire("react").renderToString(' +
    'require("react").createElement(require(component)))',
  {
    global: {
      React: React,
      Immutable: Immutable,
    },
    window: {},
    component: component,
    console: console,
  }
);

我很惊讶它以前有效,但它确实有效。但是现在它失败了,因为源码使用了ES6。 我寻找预制的解决方案,但它们似乎都针对旧的反应版本,其中反应工具仍然存在。

我用 browserify 和 babel 打包了下面的特殊服务器端脚本,然后 运行 使用 运行InNewContext。它不会失败但也不会输出任何代码,它只是记录一个空对象

import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './index';

const content = renderToString(<App />);

我找到了很多关于“服务器端渲染”的文章,但它们似乎都是关于使用 express 渲染的,并且使用与上面脚本相同的行。我不能 运行 直接在 gulp 中编写该代码,因为它不能很好地与 ES6 导入一起使用,ES6 导入仅在节点 14 之后可用(并且是实验性的)。

我未能显示 gulp-browserify 任务,它直接呈现应用程序组件,而不是上面的 server-side 入口点脚本。如果有人需要这样做,这里有一个可行的解决方案。

使用 vm.runInNewContext 允许我们定义合成浏览器上下文,而 require 则不能。如果您在应用程序的任何位置访问 window,这一点很重要。

src/server.js:

import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './index';

const content = renderToString(<App />);
global.output = content;

以上脚本作为 browserify 的入口点。 Gulp 要编译的任务:

function gulpJS() {
  const sourcePath = path.join(SRC_DIR, 'src/server.js');
  return browserify(sourcePath, { debug:true })
    .transform('babelify', {
      presets: [
        ["@babel/preset-env", { targets: "> 0.25%, not dead" }],
        "@babel/preset-react",
      ],
    })
    .bundle()
    .pipe(source('server_output.js'))
    .pipe(buffer())
    .pipe(sourcemaps.init({loadMaps: true}))
    .pipe(sourcemaps.write('.'))
    .pipe(dest(BUILD_DIR));
}

生成的文件现在可以用于以后的任务,例如将呈现的内容插入 HTML 文件。

const componentContent = fs.readFileSync(path.join(BUILD_DIR, 'server.js'));
const context = {
  global: {
    React: React,
    Immutable: Immutable,
    data: {
      Immutable
    },
  },
  window: {
    addEventListener() { /* fake */ },
    removeEventListener() { /* fake */ },
  },
  console,
};

vm.runInNewContext(componentContent, context);
const result = context.global.output;