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
相关产品推荐
相关产品推荐

