其它反应 CSR SDK
基于用户/租户上下文的动态捆绑加载以用于完全 React App 替换 .
概览
自动反应CSR SDK可以让你 完全替换 您的响应应用程序包基于谁登录.
如何运作
HTML 负载 → 装填器检查本地Storage → 装入自定义 OR 默认捆绑 → 单一反应应用程序启动
安全结构
SDK遵循安全的服务器对服务器架构:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Browser │ │ Vendor │ │ Else │
│ (SDK) │ │ Backend │ │ API │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ GET /api/bundle │ │
├─────────────────►│ │
│ │ GET /vendor-api │
│ ├─────────────────►│
│ │ (with API key) │
│ │ │
│ │ bundle URL │
│ │◄─────────────────┤
│ bundle URL │ │
│◄─────────────────┤ │
│ │ │
SDK loads bundle │ │
- 浏览器: 从未看到 API 密钥, 呼叫供应商后端
- 供应商后端: 认证用户, 调用 Else API 服务器到服务器
- 否则 API: 根据租户配置返回捆绑的 URL
安装
npm install @elsedev/react-csr-sdk
安装后, 运行 init 命令将捆绑加载器添加到您的 HTML 中 :
npx init-else
这将自动更新您的 HTML 从 CDN 装入装入器 .
扩展加载
SDK支持根据URL查询参数自动加载扩展. 这允许通过urls公开共享扩展. 当创建可视化模式“ public” 扩展名时, 用户将获得与格式的共享链接 https://your-domain.com?else ext=extension-id 页面存档备份,存于互联网档案馆
基本用途
import { loadExtensionFromQuery } from '@elsedev/react-csr-sdk'
// Initialize extension loading
await loadExtensionFromQuery(async (identifier) => {
// Call your backend to get bundle info. Replace path with the proper path for your backend.
const response = await fetch(`/api/else/bundle?ext=${identifier}`)
return await response.json()
})
如何运作
- 用户访问 URL数字 :
https://your-domain.com?else_ext=my-extension - SDK 检测参数: 自动查找
else_ext查询参数 - 打电话回电: SDK 调用您提供的回调功能
- 你给后端打电话: 您的回调通过您的后端从 Else API 获取捆绑信息
- 装入扩展名: SDK 装入捆绑并替换应用程序
后端集成
您需要执行调用后端的调用功能, 然后调用 Else API :
// Your callback function
const fetchBundleInfo = async (identifier) => {
// Call YOUR backend (not Else API directly)
const response = await fetch(`/api/else/bundle?ext=${identifier}`)
return await response.json()
}
// Initialize SDK with your callback
await loadExtensionFromQuery(fetchBundleInfo)
您的后端需要一个简单的代理端 :
// Express.js example
app.get('/api/else/bundle', async (req, res) => {
const { ext } = req.query
// Call Else API with your vendor API key
const response = await fetch(
`https://api.else.com/vendor-api/products/your-product/extensions/${ext}/bundle`,
{
headers: {
'Authorization': `Bearer ${process.env.ELSE_VENDOR_API_KEY}`
}
}
)
const bundleInfo = await response.json()
res.json(bundleInfo)
})
高级使用
import { loadExtensionFromQuery, loadExtension, setDebugMode } from '@elsedev/react-csr-sdk'
// Enable debug logging
setDebugMode(true)
// Load extension from else_ext query parameter
await loadExtensionFromQuery(async (identifier) => {
// Your callback: call YOUR backend, which calls Else API
const response = await fetch(`/api/else/bundle?ext=${identifier}`)
return await response.json()
})
// Load extension programmatically
await loadExtension('my-extension-slug', async (identifier) => {
// Your callback: call YOUR backend, which calls Else API
const response = await fetch(`/api/else/bundle?ext=${identifier}`)
return await response.json()
})
快速启动
1. 安装SDK
SDK使用基于CDN的方法,自动更新HTML:
npm install @elsedev/react-csr-sdk
安装后运行 npx @elsedev/react-csr-sdk 将装入器 CDN 脚本添加到您的 index.html。捆绑装货机的主机位于我们的CDN https://sdk-cdn.somethingelse.ai 和您安装的 SDK 版本匹配.
2. 验证 HTML 结构
运行后 npx @elsedev/react-csr-sdk,则您的 HTML 将被更新。 应该是这样的
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Else bundle loader - Automatically added by postinstall script -->
<script src="https://sdk-cdn.somethingelse.ai/react-csr/else-bundle-loader-v1.0.17.js"></script>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Your App</title>
<!-- Optional: Enable debug logging for bundle loader -->
<script>
setTimeout(() => {
if (window.else) {
window.else.setDebug(true);
}
}, 0);
</script>
</head>
<body>
<div id="root"></div>
<!-- IMPORTANT: Add data-else-default-bundle attribute -->
<script data-else-default-bundle type="module" src="/src/main.tsx"></script>
</body>
</html>
关键细节 :
- 捆绑加载器脚本自动添加到
<head>以单位命令 - CDN URL 版本并匹配您已安装的 SDK 版本
- 添加
data-else-default-bundle属性到您主应用程序脚本 - 更新 SDK 后运行
npx @elsedev/react-csr-sdk再次更新 CDN URL
3. 创建供应商后端终点
您的后端应该认证用户并呼叫 Else API :
// GET /api/user/bundle
app.get('/api/user/bundle', authenticateUser, async (req, res) => {
const user = req.user; // Your authenticated user
if (!user.tenantId) {
return res.json({ bundleUrl: undefined });
}
try {
// Call Else API server-to-server with your API key
const response = await fetch(
`${NEXUS_API_URL}/vendor-api/products/${PRODUCT_SLUG}/tenants/${user.tenantId}/bundle`,
{
headers: {
'Authorization': `Bearer ${process.env.ELSE_API_KEY}`, // Keep API keys on server
'Content-Type': 'application/json',
},
}
);
if (response.ok) {
const data = await response.json();
res.json({ bundleUrl: data.bundle_url });
} else {
res.json({ bundleUrl: undefined });
}
} catch (error) {
console.error('Failed to fetch bundle from Else:', error);
res.json({ bundleUrl: undefined });
}
});
4. 在前端使用 SDK
import { setCustomBundle, clearCustomBundle } from '@elsedev/react-csr-sdk';
// Handle user login - call your own backend
async function handleUserLogin(credentials) {
try {
// 1. Authenticate with your backend (your existing auth flow)
const authResponse = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials),
credentials: 'include'
});
if (authResponse.ok) {
// 2. Get bundle URL from your backend
const bundleResponse = await fetch('/api/user/bundle', {
credentials: 'include'
});
if (bundleResponse.ok) {
const { bundleUrl } = await bundleResponse.json();
// setCustomBundle will exit early if bundleUrl is unchanged
setCustomBundle(bundleUrl); // Page reloads only if bundle changed
}
}
} catch (error) {
console.error('Login failed:', error);
}
}
// Clear custom bundle on logout
async function handleUserLogout() {
try {
await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' });
clearCustomBundle(); // Reloads with default bundle
} catch (error) {
console.error('Logout failed:', error);
}
}
API 参考
套装管理
setCustomBundle(bundleUrl, reload?)
设置自定义捆绑的 URL 并可以选择重新装入页面 。 如果捆绑的 URL 不变, 则提前退出( 不重新加载) .
setCustomBundle('https://cdn.example.com/custom-bundle.js', true);
setCustomBundle(undefined); // Clear bundle
参数:
bundleUrl: string | undefined- 捆绑或未定义的 URL 清除reload: boolean- 是否重新装入页面( 默认: 真)
getCustomBundle()
获取当前设置的自定义捆绑 URL.
const currentBundle = getCustomBundle();
// Returns: string | undefined
clearCustomBundle(reload?)
清除自定义捆绑和可选重装以使用默认值.
clearCustomBundle(true);
reloadWithBundle(bundleUrl)
设置捆绑的URL并立即重新装入页面.
reloadWithBundle('https://cdn.example.com/new-bundle.js');
reloadWithBundle(undefined); // Clear and reload
工具函数
setDebugMode(enabled)
启用/ 禁用调试日志 .
setDebugMode(true); // See bundle loading logs
整合模式
用户登录流程
async function handleUserLogin(credentials) {
// 1. Authenticate with your backend
const user = await authenticateUser(credentials);
// 2. Get bundle URL from your backend
if (user) {
try {
const response = await fetch('/api/user/bundle', {
credentials: 'include'
});
if (response.ok) {
const { bundleUrl } = await response.json();
// Only reloads if bundle URL changed
setCustomBundle(bundleUrl);
}
} catch (error) {
console.error('Failed to load custom bundle:', error);
// App continues with default bundle
}
}
}
用户日志流
async function handleUserLogout() {
// 1. Clear session with your backend
await logoutUser();
// 2. Clear custom bundle and reload with default
clearCustomBundle();
}
租户交换
async function switchTenant(newTenantId) {
// 1. Update tenant in your backend
await updateUserTenant(newTenantId);
// 2. Get new tenant's bundle URL
const response = await fetch('/api/user/bundle', {
credentials: 'include'
});
if (response.ok) {
const { bundleUrl } = await response.json();
setCustomBundle(bundleUrl); // Page reloads only if bundle changed
}
}
处理错误
async function loadUserBundle() {
try {
const response = await fetch('/api/user/bundle', {
credentials: 'include'
});
if (response.ok) {
const { bundleUrl } = await response.json();
setCustomBundle(bundleUrl);
}
} catch (error) {
console.error('Bundle loading failed:', error);
// Optional: Show user notification
showNotification('Custom features unavailable, using default app');
// App continues with default bundle - no action needed
}
}
全球 API (不需要模块导入)
如果无法使用ES模块,捆绑加载器会曝光全局功能:
// Available on window.else after bundle-loader.js loads
window.else.setCustomBundle(url);
window.else.getCustomBundle();
window.else.clearCustomBundle();
window.else.reloadWithBundle(url);
window.else.setDebug(enabled);
Bundle 替换工程
- 装入器脚本: 在任何反应代码之前运行
- 本地集成检查: 寻找
else_bundle_url - 捆绑决定数字 :
- 如果自定义 URL 找到 − 防止默认捆绑, 加载自定义捆绑
- 如果没有自定义 URL − 允许默认捆绑正常加载
- 单一应用程序: 只有一个反应应用程序开始
捆绑要求
您的自定义捆绑应该:
- 完整、独立的反应应用程序
- 包含所有依赖( React, ReactDOM等)
- 挂载到相同的 DOM 元素(
#root(中文(简体) ) - 处理自己的路线和国家管理
后端集成要求
您的后端端点( E)/api/user/bundle 或类似)应:
- 认证用户 (会议、JWT等)
- 确定用户的租户身份 从您的用户数据库中
- 呼叫 API 服务器到服务器 带有您的 API 密钥
- 返回捆绑的 URL 到前端( 如果没有自定义捆绑, 未定义)
示例响应格式 :
{
"bundleUrl": "https://cdn.example.com/tenant-123/bundle.js"
}
或者如果没有定制捆绑:
{
"bundleUrl": undefined
}
安全福利
- API 密钥保护: 从未被浏览器曝光
- 用户认证: 您的后端验证身份
- 授权: 您的后端确保用户可以访问其租户
- 审计轨迹: 您的后端可以日志捆绑请求
- 限制费率: 您的后端可以执行限制
调试
启用调试模式以查看捆绑加载日志 :
import { setDebugMode } from '@elsedev/react-csr-sdk';
setDebugMode(true);
// Or globally:
window.else.setDebug(true);
这将日志 :
- 捆绑 URL 分辨率
- 装入尝试
- 后退设想
- 错误细节
解决问题
默认装入和自定义装入同时
症状 : 原始的应用程序和自定义捆绑似乎同时加载,从而产生UI冲突,重复内容,或双重回放挂起.
根原因 : 竞速条件,默认捆绑脚本在捆绑装入器能够阻止它之前执行.
解决方案( 所需步骤 ):
-
校验 CDN 脚本已存在
- 选中此 CDN 捆绑加载器脚本存在于您的
index.html - 运行
npm install如果缺少 CDN 脚本
- 选中此 CDN 捆绑加载器脚本存在于您的
-
添加
data-else-default-bundle属性<script data-else-default-bundle type="module" src="/src/main.tsx"></script>- 帮助捆绑者识别和防止默认捆绑
- 可靠捆绑替换的关键
-
校验生产中的装入顺序
- 构建您的应用程序 :
npm run build - 检查
dist/index.html- 捆绑加载器 CDN 脚本应为 FIRST 脚本<head> - 如果没有,请确保您的构建进程保留脚本顺序
- 构建您的应用程序 :
调试步骤 :
- 启用您的调试模式
index.html数字 :<script> setTimeout(() => { if (window.else) { window.else.setDebug(true); } }, 0); </script> - 检查浏览器控制台中的装入日志
- 查找“ Found 默认脚本 ” 和“ 防止默认捆绑负载 ” 消息
- 如果你看到双倍挂起, 请验证
data-else-default-bundle属性存在
生产中未装入
检查列表 :
- XQ 装入器 CDN URL 存在于
dist/index.html - QQ Bundle 加载器 CDN 脚本是第一个脚本
<head>内dist/index.html - 主脚本有
data-else-default-bundle属性 - QQ 自定义捆绑 URL 有效且可访问
共同问题:
-
Dist/ index.html 中缺少 Bundle 加载器 CDN URL
- 运行
npx @elsedev/react-csr-sdk再次更新 CDN URL - 校验您来源中的脚本标签
index.html已更新
- 运行
-
生产中的脚本顺序错误
- 检查
dist/index.html手动 - 捆绑装载器应先进入<head> - 保证您的构建过程不会删除或重排捆绑加载脚本
- 检查
-
CORS 装入自定义捆绑出错
- 确保自定义捆绑 URL 有适当的 CORS 头
- 检查浏览器网络标签失败的请求
自定义登录后不应用套装
检查列表 :
- 后端端返回有效
bundleUrl setCustomBundle(bundleUrl)正在被调用- 页面正在重新装入( 默认行为)
- 自定义捆绑 URL 可访问( 无 404 或 CORS 错误)
调试 :
// Enable debug mode
window.else.setDebug(true);
// Check what's stored
console.log('Current bundle:', window.else.getCustomBundle());
// Test manual load
window.else.reloadWithBundle('https://your-bundle-url.js');
要查找的控制台日志 :
[Else] Setting custom bundle: <url>- 确认 SDK 函数[Else] Custom bundle URL: <url>- 确认在重新装入时检测到的捆绑[Else] Loading script: <url>- 确认装入未遂[Else] Script loaded successfully: <url>- 确认装入了捆绑
损坏 SDK
SDK在Else工作空间开发扩展时自动被禁用.
要手动禁用 SDK, 请设置一个环境变量 :
维特:
VITE_ELSE_DISABLED=true npm run dev
创建反应 App :
REACT_APP_ELSE_DISABLED=true npm start
当禁用时,自定义捆绑不会加载,所有的SDK功能都会变成无操作.
类型脚本支持
SDK包括了完整的TypeScript定义. 当安装软件包时,全局类型定义 window.else 自动可用 :
// Global window.else types are automatically available
// No need to manually declare the interface!
window.else?.setCustomBundle('https://example.com/bundle.js')
window.else?.inElseDevEnvironment() // Returns boolean
// Import types for advanced usage
import type { ElseConfig, ElseSDK } from '@elsedev/react-csr-sdk';
这个 window.else API在全自动完整支持下自动输入.
实例
见 examples/ 完整执行实例目录.
支助
关于议题或问题,请参看其他文件或联系支助.