如何在Node与网页端共用ES6导入实现React+HTM的SSR/CSR兼容
问题原因
浏览器原生ES模块不支持裸模块标识符(比如直接写import React from 'react'),它要求模块路径必须是相对路径(.//../)、绝对路径(/开头)或完整URL。而Node.js环境下能解析'react'是因为Node会自动去node_modules目录查找对应的包,这是Node的模块解析机制,浏览器没有这个能力,所以抛出Failed to resolve module specifier "react"错误。
解决方案
下面提供几种让React、htm同时在Node服务端和浏览器客户端正常运行的方案:
方案1:使用CDN ES模块 + 条件导入
服务端保持原有导入逻辑(Node能解析node_modules),客户端改用CDN提供的ES模块版本,可通过环境判断复用代码:
通用逻辑代码(可同时在两端运行)
let React, htm; // 判断当前环境是服务端还是客户端 if (typeof window === 'undefined') { // 服务端:从node_modules导入 const reactModule = await import('react'); const htmModule = await import('htm'); React = reactModule.default; htm = htmModule.default; } else { // 客户端:从CDN导入ES模块 const reactModule = await import('https://cdn.skypack.dev/react'); const htmModule = await import('https://cdn.skypack.dev/htm'); React = reactModule.default; htm = htmModule.default; } const html = htm.bind(React.createElement); // 示例组件 function App() { return html`<h1>Hello from React + htm</h1>`; } export default App;
客户端HTML引入
<div id="root"></div> <script type="module"> import App from './app.js'; import ReactDOM from 'https://cdn.skypack.dev/react-dom/client'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render(App()); </script>
方案2:使用轻量打包工具处理客户端代码
如果需要生产环境优化(比如tree-shaking、代码压缩),可以用esbuild、Rollup这类轻量打包工具,将客户端代码中的裸模块转换成浏览器可识别的路径:
1. 安装esbuild
npm install esbuild --save-dev
2. 编写打包脚本(build.js)
import esbuild from 'esbuild'; await esbuild.build({ entryPoints: ['client.js'], // 你的客户端入口文件 bundle: true, outfile: 'dist/client.bundle.js', format: 'esm', platform: 'browser', treeShaking: true, });
3. 运行打包
node build.js
4. 客户端HTML引入打包后的文件
<div id="root"></div> <script type="module" src="/dist/client.bundle.js"></script>
服务端代码无需修改,依旧直接从node_modules导入React和htm即可。
方案3:使用浏览器Import Map(现代浏览器支持)
通过importmap在HTML中定义裸模块到CDN地址的映射,这样客户端代码可以和服务端完全一致,无需修改导入语句:
1. 在HTML头部添加importmap
<head> <script type="importmap"> { "imports": { "react": "https://cdn.skypack.dev/react", "react-dom/client": "https://cdn.skypack.dev/react-dom/client", "htm": "https://cdn.skypack.dev/htm" } } </script> </head>
2. 客户端代码(与服务端完全一致)
import React from 'react'; import htm from 'htm'; import ReactDOM from 'react-dom/client'; const html = htm.bind(React.createElement); const root = ReactDOM.createRoot(document.getElementById('root')); root.render(html`<h1>Hello from React + htm</h1>`);
注意:Import Map仅支持现代浏览器(Chrome 89+、Firefox 108+等),如果需要兼容旧浏览器,需配合polyfill。
内容的提问来源于stack exchange,提问作者berto.dev
相关产品推荐
相关产品推荐

