如何通过CDN在React项目中复用远程React组件?
从CDN加载React组件(含TypeScript/Hooks)的可行方案
问题背景
我有一个名为TEST.TSX的React组件,已编译为.js并上传至GitHub,可通过CDN访问。想在另一个React应用中直接通过该CDN使用这个组件,但尝试过Module Federation(额外托管成本太高)、NextJS打包脚本(失败)、直接加载编译后的JS(本地import可用但CDN加载失败)等方法都没成功。也不能用iframe,本质是想直接从CDN托管的文件(.TSX/.JSX/.TS/.JS)使用React组件,包括那些用TypeScript函数和React Hooks的组件。
当前的webpack配置、导出脚本、组件代码以及尝试的CDN加载代码如下:
当前webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); const { ModuleFederationPlugin } = require('webpack').container; const deps = require("./package.json").dependencies; const path = require('path'); module.exports = { mode: 'production', devtool: 'source-map', entry: './app/export-component.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'exports.js', library: 'MyLibrary', // Set a name for the library libraryTarget: 'umd', // Set the module type (Universal Module Definition) globalObject: 'this', // To support both browser and Node.js environments }, devServer: { port: 3001, historyApiFallback: true, }, module: { rules: [ { test: /\.(js|jsx|tsx|ts)$/, use: [ { loader: 'babel-loader', options: { presets: ['solid'], }, }, { loader: 'ts-loader', }, ], exclude: /node_modules/, }, ], }, resolve: { extensions: ['.tsx', '.ts', '.js'], }, };
export-component.js
// export-components.js import DemoClientComponent from './components/DemoClientComponent'; import PayButton from './components/PayButton'; // Option 1: Export individual components export { DemoClientComponent ,PayButton}; // Option 2: Export as an object (if applicable) export default function ExportComponents() { DemoClientComponent, PayButton };
DemoClientComponent.tsx
import React from 'react'; const DemoClientComponent = () => ( <> <h1>Hello from Braintree</h1> </> ); export default DemoClientComponent;
尝试的CDN加载代码(layout.tsx)
"use client" import { useEffect, useState } from "react"; const RemoteFetch = () => { const [innerHtml, setInnerhtml] = useState(); useEffect(() => { const innerHtml = window.MyLibrary.DemoClientComponent(); setInnerhtml(innerHtml) }, []); return ( <> {innerHtml && innerHtml.outerHTML && <div dangerouslySetInnerHTML={{__html : innerHtml.outerHTML}}> </div>} </> );}; export default RemoteFetch;
解决方案
1. 修复webpack配置的核心错误
你的webpack配置里babel用了solid预设(针对SolidJS),而非React的预设,这会导致React组件编译错误。同时需要把React设为外部依赖,避免打包到输出文件中(由宿主应用提供React)。修改后的webpack.config.js:
const path = require('path'); module.exports = { mode: 'production', devtool: 'source-map', entry: './app/export-component.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'exports.js', library: 'MyLibrary', libraryTarget: 'umd', globalObject: 'this', umdNamedDefine: true, // 确保UMD导出能被浏览器和模块系统正确识别 }, externals: { // 声明React为外部依赖,由宿主应用提供 react: 'react', 'react-dom': 'react-dom' }, module: { rules: [ { test: /\.(js|jsx|tsx|ts)$/, use: [ { loader: 'babel-loader', options: { // 替换为React的babel预设 presets: ['@babel/preset-react', '@babel/preset-typescript'], }, }, 'ts-loader', ], exclude: /node_modules/, }, ], }, resolve: { extensions: ['.tsx', '.ts', '.js', '.jsx'], }, };
2. 修正组件导出脚本
确保导出的是React组件本身,而非无效的默认函数。修改export-component.js:
// export-components.js import DemoClientComponent from './components/DemoClientComponent'; import PayButton from './components/PayButton'; // 直接导出组件,确保UMD能正确暴露它们 export { DemoClientComponent, PayButton }; // 可选:默认导出包含组件的对象 export default { DemoClientComponent, PayButton };
3. 正确加载CDN组件(支持Hooks)
不要用dangerouslySetInnerHTML,React组件返回的是React元素而非DOM节点,直接渲染即可。同时要确保CDN脚本加载完成后再访问window.MyLibrary:
"use client" import { useEffect, useState } from "react"; const RemoteFetch = () => { const [Components, setComponents] = useState<{ DemoClientComponent?: React.FC; PayButton?: React.FC; }>({}); useEffect(() => { // 动态加载CDN脚本 const script = document.createElement('script'); script.src = '你的CDN脚本地址'; script.async = true; script.onload = () => { // 脚本加载完成后,获取导出的组件 setComponents(window.MyLibrary); }; document.body.appendChild(script); return () => { document.body.removeChild(script); }; }, []); // 直接渲染React组件,Hooks会正常工作 const DemoClientComponent = Components.DemoClientComponent; return ( <> {DemoClientComponent && <DemoClientComponent />} </> ); }; export default RemoteFetch;
4. 处理TypeScript类型(可选)
如果需要类型支持,可以把组件的类型定义单独打包成.d.ts文件上传到CDN,然后在宿主应用中通过declare module引入:
// 在宿主应用中创建类型声明文件 declare module 'your-cdn-library' { import React from 'react'; export const DemoClientComponent: React.FC; export const PayButton: React.FC; }
关键说明
- 必须确保宿主应用和组件使用的React版本兼容,避免双React实例导致的问题
- 组件中使用的Hooks会正常工作,因为组件是在宿主应用的React环境中渲染的
- 打包时把React设为外部依赖,能大幅减小CDN脚本体积,同时避免版本冲突
内容的提问来源于stack exchange,提问作者Pradeep Yadav
相关产品推荐
相关产品推荐

