You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React 18 SSR无需@loadable实现Chunk预加载方法问询

React 18 SSR + Lazy/Suspense 实现Chunk预加载(无需@loadable)

要在React 18流式SSR架构下实现懒加载Chunk的预加载,核心思路是追踪SSR过程中实际用到的懒加载组件对应的Chunk,并将预加载标签插入到HTML输出中。以下是具体实现步骤:

1. 自定义带Chunk追踪的Lazy包装函数

React原生lazy无法直接暴露Chunk信息,我们需要封装一个带追踪能力的版本,结合Webpack魔法注释标记Chunk名,并在SSR环境下收集这些Chunk:

// utils/lazyWithTracking.js
import { lazy } from 'react';

// 用于存储当前请求的Chunk收集器(需保证请求隔离)
let activeCollector = null;

export const setActiveChunkCollector = (collector) => {
  activeCollector = collector;
};

export const lazyWithTracking = (importFn) => {
  const LazyComponent = lazy(importFn);

  // 仅在SSR环境执行追踪逻辑
  if (typeof window === 'undefined') {
    importFn().then((module) => {
      // 从Webpack魔法注释中提取Chunk名
      const chunkNameMatch = importFn.toString().match(/webpackChunkName: "([^"]+)"/);
      const chunkName = chunkNameMatch?.[1];

      if (chunkName && activeCollector) {
        activeCollector.addChunk(chunkName);
      }
    });
  }

  return LazyComponent;
};

2. 实现Chunk收集器类

创建一个收集器类,负责存储当前请求用到的Chunk名,并根据Webpack生成的manifest将Chunk名转换为实际文件名:

// utils/ChunkCollector.js
export class ChunkCollector {
  constructor(webpackManifest) {
    this.manifest = webpackManifest; // Webpack生成的Chunk映射表
    this.chunkNames = new Set(); // 用Set避免重复Chunk
  }

  addChunk(chunkName) {
    this.chunkNames.add(chunkName);
  }

  // 生成预加载标签字符串
  getPreloadTags() {
    return Array.from(this.chunkNames)
      .map((chunkName) => {
        const chunkFilename = this.manifest[chunkName];
        if (!chunkFilename) return '';
        // ES模块用modulepreload,普通脚本用preload as="script"
        return `<link rel="modulepreload" href="/${chunkFilename}" />`;
      })
      .filter(Boolean)
      .join('\n');
  }
}

3. 配置Webpack生成Chunk映射表

使用webpack-manifest-plugin生成manifest.json,该文件会记录Chunk名到实际编译后文件名(带hash)的映射:

// webpack.config.js
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');

module.exports = {
  // ...其他配置
  plugins: [
    new WebpackManifestPlugin({
      fileName: 'manifest.json',
      generate: (seed, files) => {
        const manifest = seed;
        files.forEach((file) => {
          // 匹配带Chunk名的文件(对应魔法注释的命名)
          if (file.name.match(/^[a-zA-Z0-9-]+(\.[a-f0-9]+)?\.js$/)) {
            const chunkName = file.name.split('.')[0];
            manifest[chunkName] = file.name;
          }
        });
        return manifest;
      },
    }),
  ],
};

4. 结合renderToPipeableStream实现流式SSR+预加载

在SSR服务端代码中,为每个请求创建独立的Chunk收集器,渲染完成后将预加载标签插入HTML输出:

// server.js
import { renderToPipeableStream } from 'react-dom/server';
import express from 'express';
import { ChunkCollector, setActiveChunkCollector } from './utils';
import App from './src/App';
import webpackManifest from './dist/manifest.json';

const app = express();
app.use(express.static('dist'));

app.get('*', (req, res) => {
  // 为当前请求创建独立的Chunk收集器
  const collector = new ChunkCollector(webpackManifest);
  setActiveChunkCollector(collector);

  const stream = renderToPipeableStream(<App />, {
    onAllReady() {
      // 生成预加载标签
      const preloadTags = collector.getPreloadTags();

      // 流式输出HTML:先发送头部+预加载标签,再发送渲染内容,最后收尾
      res.write(`<!DOCTYPE html>
<html>
<head>
  <title>SSR App</title>
</head>
<body>
  ${preloadTags}
  <div id="root">`);

      // 管道渲染React内容到响应流
      stream.pipe(res, { end: false });

      // 渲染完成后补全HTML结构
      stream.on('end', () => {
        res.write(`</div>
<script src="/main.js"></script>
</body>
</html>`);
        res.end();
      });
    },
    onError(err) {
      console.error('SSR渲染错误:', err);
      res.status(500).send('Internal Server Error');
    },
  });
});

app.listen(3000, () => console.log('SSR服务启动于http://localhost:3000'));

5. 在组件中使用自定义Lazy函数

替换React原生lazy为我们封装的lazyWithTracking,并添加Webpack魔法注释标记Chunk名:

// src/components/About.js
import { lazyWithTracking } from '../utils/lazyWithTracking';

const About = lazyWithTracking(() => import(/* webpackChunkName: "About" */ './About'));

export default About;

关键注意事项

  • 请求隔离:每个SSR请求必须创建独立的ChunkCollector实例,并通过setActiveChunkCollector设置,避免多请求间的Chunk信息污染。
  • Webpack版本兼容:确保webpack-manifest-plugin版本与你的Webpack版本匹配,避免映射生成错误。
  • 预加载类型:如果你的项目使用非ES模块的脚本,将rel="modulepreload"改为rel="preload" as="script"。

内容的提问来源于stack exchange,提问作者Alexander G

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 06:05:14