Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vite 模式下的 SSR 方案 #4651

Closed
ClarkXia opened this issue Sep 22, 2021 · 0 comments
Closed

vite 模式下的 SSR 方案 #4651

ClarkXia opened this issue Sep 22, 2021 · 0 comments
Assignees

Comments

@ClarkXia
Copy link
Collaborator

ClarkXia commented Sep 22, 2021

背景

目前 webpack 模式下通过 "ssr": true 方式开启 ssr 能力,而在 vite 模式下目前是不支持的,期望 vite 模式下同样可以通过 "ssr": true 的方式一键启动 SSR 方案

具体设计

在 vite 模式下开启 ssr 能力后,新增一个插件 vite-plugin-ssr 完成 html 内容直出的工作:

function vitePluginSSR() {
  return {
    name: 'vite-plugin-ssr',
    config() {
     // 完成 ssr 场景下的定制参数
    },
    configureServer: async (server) => {
      //  定制 ssr 场景下的 server 处理
      const handler = async (req, res, next) => {
        try {
          const ssrEntryPoint = await server.ssrLoadModule(ssrEntry);
          // 执行页面 html 输出
          const { html } = await ssrEntryPoint.renderPage({ htmlTemplateContent, buildConfig, initialContext });
          res.setHeader('Content-Type', 'text/html')
          res.end(html);
        } catch (e) {
          server.ssrFixStacktrace(e);
        }
      };
      return () => server.middlewares.use(handler);
    }
  }
}

 原方案在插件层面重新创建一个 vite server,会导致多次预编译问题,并且在 dev 场景下仅依赖 ssrLoadModule 的 api 便可以完成 server 代码在 node 端执行的逻辑

存在问题

vite 模式下相关 SSR 能力

vite 支持通过 ssrModuleLoader 的方法,直接加载 node ssr 端代码,无须以 bundle 的形式执行:

https://github.com/vitejs/vite/blob/4bdf5d70b52e4d7dd6436efd30e1046250d3bbfe/packages/vite/src/node/ssr/ssrModuleLoader.ts#L31-L36

但是该方法本身存在对于 cjs 的 exports 导出的不兼容,参考 vitejs/vite#2579
在 icejs 框架的 server.ts 运行下存在以下几个包会出现问题:

  • cheerio
  • parseurl
  • chalk

可选的解决方案是提前设置 vite.optimizeDeps.include 方式进行预编译

支持 node 端执行的 esm 规范

目前 icejs 框架依赖的运行时代码库,仅导出了 esm 规范,但未添加 "type": "module" 或者 添加 .mjs 的后缀,在 node 端无法以 esm 的方式执行。

image

具体仓库如下:

  • react-app-renderer
  • create-app-shared
    • universal-env 未导出 esm 产物
    • miniapp-history lib / es 产物均为 cjs
  • @ice/store
  • @ice/runtime

对于上述的仓库,可以通过以下两种方式修复:

  1. 采取规范的 esm,并支持 node 环境下执行(如果采取添加 "type": "module",需要针对文件导入的地方添加 extension 参考 import without extension nodejs/node#30927推荐方式,需要兼顾底层包的规范
  2. 编译 cjs 模块,同时通过 vite.optimizeDeps.include 方式预编译成 esm 兼容模式

node 端执行环境判断

在 webpack 链路下,ssr 的构建是额外的构建链路,因此在 ssr 链路中会设置 process.env.__IS_SERVER__ 的变量,并且该变量将会耦合在运行时代码中

config
.plugin('DefinePlugin')
.tap(([args]) => [{ ...args, 'process.env.__IS_SERVER__': true }]);

对于 vite 而言,node 端执行和 client 端的请求转化均为统一 server 服务(期望上只会启动一个 viteServer),这样变造成代码运行时中依赖 process.env.__IS_SERVER__ 是会造成误判,导致 CSR 过程的失败。

解决方案:
在 ssrLoadModule 阶段执行设置 global.__IS_SERVER__ = true,不在注入 process.env.__IS_SERVER__,并且在所有依赖 process.env.__IS_SERVER__ 的代码增加 global.__IS_SERVER__

 try {
+ global.__IS_SERVER__ = true
   const ssrEntryPoint = await server.ssrLoadModule(ssrEntry);
   // 执行页面 html 输出
   const { html } = await ssrEntryPoint.renderPage({ htmlTemplateContent, buildConfig, initialContext });
- if (process.env.__IS_SERVER__) {
+ if ((typeof global !== 'undefined' && global.__IS_SERVER__) || process.env.__IS_SERVER__) {
    history = createMemoryHistory();
    history.location = location;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants