steedos-webapps
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSteedos Webapps | Steedos 软件包自定义 React 应用
Steedos Webapps | Steedos 软件包自定义 React 应用
Overview | 概述
概述
Steedos packages can contain React + Vite sub-projects in the directory. Each webapp is an independent Vite application that can be developed standalone and compiled as an IIFE script that self-registers as an amis Renderer component.
webapps/Steedos 软件包通过 目录管理 React 子项目。每个子项目是独立的 Vite 应用,可独立开发调试,也可编译为 amis 自注册组件。
webapps/Steedos软件包可在目录中包含React + Vite子项目。每个Web应用都是独立的Vite应用,可独立开发并编译为自注册为amis Renderer组件的IIFE脚本。
webapps/Steedos 软件包通过 目录管理 React 子项目。每个子项目是独立的 Vite 应用,可独立开发调试,也可编译为 amis 自注册组件。
webapps/Directory Structure | 目录结构
目录结构
my-package/ # Steedos package root
├── webapps/ # React sub-projects
│ ├── designer/ # webapp 1
│ │ ├── src/
│ │ │ ├── components/ # React components
│ │ │ ├── amis-entry.ts # amis registration entry
│ │ │ ├── amis-jsx-shim.ts # JSX Runtime bridge
│ │ │ └── amis-renderer.css
│ │ ├── dist/amis-renderer/ # Build output
│ │ ├── vite.config.ts # Standard dev config
│ │ ├── vite.amis.config.ts # amis IIFE build config
│ │ ├── package.json
│ │ └── tailwind.config.js
│ │
│ └── dashboard/ # webapp 2
│ ├── src/
│ │ ├── components/
│ │ ├── amis-entry.ts
│ │ └── amis-jsx-shim.ts
│ ├── vite.amis.config.ts
│ └── package.json
│
├── main/default/
│ ├── client/ # Client loader files
│ │ ├── designer.client.js # Loads designer amis renderer
│ │ └── dashboard.client.js # Loads dashboard amis renderer
│ └── routes/ # Express SPA routers
│ ├── designer.router.js # SPA access for designer
│ └── dashboard.router.js # SPA access for dashboard
│
├── public/ # Deployed output (copied from each webapp)
│ ├── designer/
│ │ ├── amis-renderer.js
│ │ └── amis-renderer.css
│ └── dashboard/
│ ├── amis-renderer.js
│ └── amis-renderer.css
│
├── package.json # Package root package.json
└── package.service.js # Moleculer service definitionKey Concepts:
- Each subdirectory is an independent React + Vite project
webapps/ - Two build modes: standard Vite build (dev) + IIFE build (amis component)
- IIFE output is copied to , served as static files by Steedos
public/<webapp-name>/ - triggers loading of the IIFE into the Steedos frontend
main/default/client/<webapp-name>.client.js - Each webapp is isolated — can use different dependencies and versions
my-package/ # Steedos软件包根目录
├── webapps/ # React子项目
│ ├── designer/ # Web应用1
│ │ ├── src/
│ │ │ ├── components/ # React组件
│ │ │ ├── amis-entry.ts # amis注册入口
│ │ │ ├── amis-jsx-shim.ts # JSX运行时桥接文件
│ │ │ └── amis-renderer.css
│ │ ├── dist/amis-renderer/ # 构建输出
│ │ ├── vite.config.ts # 标准开发配置
│ │ ├── vite.amis.config.ts # amis IIFE构建配置
│ │ ├── package.json
│ │ └── tailwind.config.js
│ │
│ └── dashboard/ # Web应用2
│ ├── src/
│ │ ├── components/
│ │ ├── amis-entry.ts
│ │ └── amis-jsx-shim.ts
│ ├── vite.amis.config.ts
│ └── package.json
│
├── main/default/
│ ├── client/ # 客户端加载文件
│ │ ├── designer.client.js # 加载designer amis渲染器
│ │ └── dashboard.client.js # 加载dashboard amis渲染器
│ └── routes/ # Express SPA路由
│ ├── designer.router.js # designer的SPA访问路由
│ └── dashboard.router.js # dashboard的SPA访问路由
│
├── public/ # 部署输出(从各个Web应用复制而来)
│ ├── designer/
│ │ ├── amis-renderer.js
│ │ └── amis-renderer.css
│ └── dashboard/
│ ├── amis-renderer.js
│ └── amis-renderer.css
│
├── package.json # 软件包根目录package.json
└── package.service.js # Moleculer服务定义关键概念:
- 下的每个子目录都是独立的React + Vite项目
webapps/ - 两种构建模式:标准Vite构建(开发)+ IIFE构建(amis组件)
- IIFE输出复制到,由Steedos作为静态文件提供服务
public/<webapp-name>/ - 触发将IIFE加载到Steedos前端
main/default/client/<webapp-name>.client.js - 每个Web应用相互隔离——可使用不同的依赖及版本
Scaffold Selection | 脚手架选择
脚手架选择
Before creating a webapp, choose a UI scaffold. This determines which component library and styling approach to use. You can use any React-compatible UI library — below are two recommended options.
创建 webapp 前,先选择 UI 脚手架,决定使用哪个组件库和样式方案。支持任意 React 兼容的 UI 库,以下是两个推荐选项。
| Scaffold | Description | When to Use |
|---|---|---|
| antd (default) | Ant Design component library. Reuses host page's antd via | Default choice — enterprise forms, tables, standard UI. Recommended when unsure. |
| shadcn/ui | Tailwind CSS + Radix UI primitives. Components are copied into project (not a dependency). | Custom/modern UI, full design control, lightweight output. |
| Other | Any React UI library (MUI, Chakra, Mantine, headless, etc.). Follow the same IIFE build pattern. | Specific design system requirements or team preference. |
创建Web应用前,先选择UI脚手架,决定使用哪个组件库和样式方案。支持任意React兼容的UI库,以下是两个推荐选项。
创建 webapp 前,先选择 UI 脚手架,决定使用哪个组件库和样式方案。支持任意 React 兼容的 UI 库,以下是两个推荐选项。
| 脚手架 | 描述 | 使用场景 |
|---|---|---|
| antd(默认) | Ant Design组件库。通过 | 默认选择——企业级表单、表格、标准UI。不确定时推荐使用。 |
| shadcn/ui | Tailwind CSS + Radix UI基础组件。组件被复制到项目中(非依赖)。 | 定制/现代UI、完全设计控制、轻量化输出。 |
| 其他 | 任意React UI库(MUI、Chakra、Mantine、无样式组件等)。遵循相同的IIFE构建模式。 | 特定设计系统要求或团队偏好。 |
Key Differences | 关键区别
关键区别
| antd | shadcn/ui | |
|---|---|---|
| Styling | CSS-in-JS (host page antd) | Tailwind CSS utility classes |
| Bundle | | Components copied into |
| Tailwind | Optional | Required |
| | |
| PostCSS plugins | | |
| antd | shadcn/ui | |
|---|---|---|
| 样式方案 | CSS-in-JS(宿主页面antd) | Tailwind CSS工具类 |
| 打包 | | 组件复制到 |
| Tailwind | 可选 | 必填 |
| | |
| PostCSS插件 | | |
antd Scaffold Setup | antd 脚手架
antd脚手架设置
bash
cd my-package/webapps
npm create vite@latest my-widget -- --template react-ts
cd my-widget
npm install
npm install antd
npm install -D @tailwindcss/postcss autoprefixer postcss-prefix-selector terserIn , mark antd as external:
vite.amis.config.tstypescript
rollupOptions: {
external: ['react', 'react-dom', 'antd'],
output: {
globals: {
react: 'amisRequire("react")',
'react-dom': 'amisRequire("react-dom")',
'antd': 'antd',
},
},
},bash
cd my-package/webapps
npm create vite@latest my-widget -- --template react-ts
cd my-widget
npm install
npm install antd
npm install -D @tailwindcss/postcss autoprefixer postcss-prefix-selector terser在中,将antd标记为外部依赖:
vite.amis.config.tstypescript
rollupOptions: {
external: ['react', 'react-dom', 'antd'],
output: {
globals: {
react: 'amisRequire("react")',
'react-dom': 'amisRequire("react-dom")',
'antd': 'antd',
},
},
},shadcn/ui Scaffold Setup | shadcn/ui 脚手架
shadcn/ui脚手架设置
bash
cd my-package/webapps
npm create vite@latest my-widget -- --template react-ts
cd my-widget
npm install
npx shadcn@latest init
npm install -D @tailwindcss/postcss autoprefixer postcss-prefix-selector terser
npm install -D tailwindcssIn , do NOT externalize antd (shadcn/ui doesn't use it):
vite.amis.config.tstypescript
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'amisRequire("react")',
'react-dom': 'amisRequire("react-dom")',
},
},
},⚠️ shadcn/ui uses Tailwind v4 — you MUST add the 3 PostCSS workaround plugins (see Tailwind CSS v4 Workarounds section).
bash
cd my-package/webapps
npm create vite@latest my-widget -- --template react-ts
cd my-widget
npm install
npx shadcn@latest init
npm install -D @tailwindcss/postcss autoprefixer postcss-prefix-selector terser
npm install -D tailwindcss在中,不要将antd标记为外部依赖(shadcn/ui不使用它):
vite.amis.config.tstypescript
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'amisRequire("react")',
'react-dom': 'amisRequire("react-dom")',
},
},
},⚠️ shadcn/ui使用Tailwind v4——必须添加3个PostCSS兼容插件(见Tailwind CSS v4兼容方案章节)。
Other Scaffolds | 其他脚手架
其他脚手架
Any React-compatible UI library works. The core requirements are the same:
- Mark and
reactasreact-domin rollup (useexternal)amisRequire - If the library is already on the host page (like antd), mark it as too
external - Use for CSS isolation
postcss-prefix-selector - If using Tailwind v4, add the 3 PostCSS workaround plugins
任意React兼容的UI库均可使用。核心要求如下:
- 在rollup中标记和
react为react-dom(使用external)amisRequire - 如果库已在宿主页面加载(如antd),也将其标记为
external - 使用实现CSS隔离
postcss-prefix-selector - 如果使用Tailwind v4,添加3个PostCSS兼容插件
Creating a New Webapp | 创建新 webapp
创建新Web应用
Step 1: Initialize Vite Project | 初始化
步骤1:初始化Vite项目
bash
cd my-package/webapps
npm create vite@latest my-widget -- --template react-ts
cd my-widget
npm installbash
cd my-package/webapps
npm create vite@latest my-widget -- --template react-ts
cd my-widget
npm installStep 2: Add Build Dependencies | 添加构建依赖
步骤2:添加构建依赖
bash
undefinedbash
undefinedCommon dependencies (both scaffolds)
通用依赖(两种脚手架)
npm install -D @tailwindcss/postcss autoprefixer postcss-prefix-selector terser
npm install -D @tailwindcss/postcss autoprefixer postcss-prefix-selector terser
antd scaffold
antd脚手架
npm install antd
npm install antd
shadcn/ui scaffold
shadcn/ui脚手架
npx shadcn@latest init
npm install -D tailwindcss
undefinednpx shadcn@latest init
npm install -D tailwindcss
undefinedStep 3: Create amis Integration Files | 创建 amis 集成文件
步骤3:创建amis集成文件
Each webapp needs 3 amis-specific files:
webapps/my-widget/src/
├── amis-entry.ts # Registration entry
├── amis-jsx-shim.ts # JSX bridge (copy from other webapp)
└── amis-renderer.css # Style entry (imported by amis-entry.ts)每个Web应用需要3个amis专属文件:
webapps/my-widget/src/
├── amis-entry.ts # 注册入口
├── amis-jsx-shim.ts # JSX桥接文件(从其他Web应用复制)
└── amis-renderer.css # 样式入口(由amis-entry.ts导入)Step 4: Configure Build Scripts | 配置构建脚本
步骤4:配置构建脚本
In the webapp's :
package.jsonjson
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"build:amis": "vite build --config vite.amis.config.ts && rm -rf ../../public/my-widget && cp -R dist/amis-renderer ../../public/my-widget",
"build:all": "tsc -b && vite build && vite build --config vite.amis.config.ts && rm -rf ../../public/my-widget && cp -R dist/amis-renderer ../../public/my-widget"
}
}在Web应用的中:
package.jsonjson
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"build:amis": "vite build --config vite.amis.config.ts && rm -rf ../../public/my-widget && cp -R dist/amis-renderer ../../public/my-widget",
"build:all": "tsc -b && vite build && vite build --config vite.amis.config.ts && rm -rf ../../public/my-widget && cp -R dist/amis-renderer ../../public/my-widget"
}
}Step 5: Create SPA Router | 创建 SPA 路由
步骤5:创建SPA路由
Create to serve the webapp as a standalone SPA:
main/default/routes/<webapp-name>.router.js创建 ,让 webapp 可以作为独立 SPA 访问:
main/default/routes/<webapp-name>.router.jsjavascript
// main/default/routes/my-widget.router.js
'use strict';
const express = require('express');
const router = express.Router();
const { requireAuthentication } = require("@steedos/auth");
const path = require('path');
const packageRoot = path.dirname(require.resolve('@steedos-labs/my-package/package.json'));
const webappDistPath = path.join(packageRoot, 'webapps', 'my-widget', 'dist');
const amisRendererDistPath = path.join(webappDistPath, 'amis-renderer');
// Main page entry (requires auth)
router.get('/api/my-package/my-widget', requireAuthentication, async (req, res) => {
try {
if (process.env.NODE_ENV === 'development') {
return res.redirect('http://localhost:5173');
}
res.sendFile(path.join(webappDistPath, 'index.html'), { dotfiles: 'allow' });
} catch (e) {
res.status(500).send({ errors: [{ errorMessage: e.message }] });
}
});
// Static assets
router.use('/api/my-package/my-widget', express.static(webappDistPath));
// SPA fallback (frontend routing support)
router.use('/api/my-package/my-widget', (req, res, next) => {
if (req.method !== 'GET') return next();
if (path.extname(req.path)) return next();
if (req.path === '/' || req.path === '') return next();
requireAuthentication(req, res, () => {
try {
if (process.env.NODE_ENV === 'development') {
return res.redirect('http://localhost:5173');
}
res.sendFile(path.join(webappDistPath, 'index.html'), { dotfiles: 'allow' });
} catch (e) {
res.status(500).send({ errors: [{ errorMessage: e.message }] });
}
});
});
// amis renderer static assets
router.use('/api/my-package/my-widget-amis', express.static(amisRendererDistPath));
exports.default = router;Also update the webapp's to set matching the router path:
vite.config.tsbase同时更新 webapp 的 ,设置 与路由路径一致:
vite.config.tsbasetypescript
// webapps/my-widget/vite.config.ts
export default defineConfig(({ command }) => ({
base: command === 'build' ? '/api/my-package/my-widget/' : '/',
// ... other config
}))创建,让Web应用可以作为独立SPA访问:
main/default/routes/<webapp-name>.router.js创建 ,让 webapp 可以作为独立 SPA 访问:
main/default/routes/<webapp-name>.router.jsjavascript
// main/default/routes/my-widget.router.js
'use strict';
const express = require('express');
const router = express.Router();
const { requireAuthentication } = require("@steedos/auth");
const path = require('path');
const packageRoot = path.dirname(require.resolve('@steedos-labs/my-package/package.json'));
const webappDistPath = path.join(packageRoot, 'webapps', 'my-widget', 'dist');
const amisRendererDistPath = path.join(webappDistPath, 'amis-renderer');
// 主页面入口(需认证)
router.get('/api/my-package/my-widget', requireAuthentication, async (req, res) => {
try {
if (process.env.NODE_ENV === 'development') {
return res.redirect('http://localhost:5173');
}
res.sendFile(path.join(webappDistPath, 'index.html'), { dotfiles: 'allow' });
} catch (e) {
res.status(500).send({ errors: [{ errorMessage: e.message }] });
}
});
// 静态资源
router.use('/api/my-package/my-widget', express.static(webappDistPath));
// SPA fallback(支持前端路由)
router.use('/api/my-package/my-widget', (req, res, next) => {
if (req.method !== 'GET') return next();
if (path.extname(req.path)) return next();
if (req.path === '/' || req.path === '') return next();
requireAuthentication(req, res, () => {
try {
if (process.env.NODE_ENV === 'development') {
return res.redirect('http://localhost:5173');
}
res.sendFile(path.join(webappDistPath, 'index.html'), { dotfiles: 'allow' });
} catch (e) {
res.status(500).send({ errors: [{ errorMessage: e.message }] });
}
});
});
// amis渲染器静态资源
router.use('/api/my-package/my-widget-amis', express.static(amisRendererDistPath));
exports.default = router;同时更新Web应用的,设置与路由路径一致:
vite.config.tsbase同时更新 webapp 的 ,设置 与路由路径一致:
vite.config.tsbasetypescript
// webapps/my-widget/vite.config.ts
export default defineConfig(({ command }) => ({
base: command === 'build' ? '/api/my-package/my-widget/' : '/',
// ... 其他配置
}))Step 6: Register in Package Root | 在软件包根目录注册
步骤6:在软件包根目录注册
json
{
"name": "@steedos-labs/my-package",
"files": [
"main/default/client",
"main/default/routes",
"webapps/my-widget/dist",
"public/my-widget",
"package.service.js"
],
"scripts": {
"build:my-widget": "cd webapps/my-widget && npm run build:all",
"build:webapps": "npm run build:my-widget"
}
}json
{
"name": "@steedos-labs/my-package",
"files": [
"main/default/client",
"main/default/routes",
"webapps/my-widget/dist",
"public/my-widget",
"package.service.js"
],
"scripts": {
"build:my-widget": "cd webapps/my-widget && npm run build:all",
"build:webapps": "npm run build:my-widget"
}
}Key Files | 关键文件
关键文件
vite.amis.config.ts — IIFE Build Config
vite.amis.config.ts — IIFE构建配置
Compiles React components into self-executing IIFE scripts loadable by amis.
typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/postcss'
import autoprefixer from 'autoprefixer'
import path from 'path'
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
const prefixSelector = require('postcss-prefix-selector')
// CSS scope prefix — all styles scoped under this selector
const SCOPE = '.my-widget'
export default defineConfig({
plugins: [react({ jsxRuntime: 'classic' })],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'react/jsx-runtime': path.resolve(__dirname, './src/amis-jsx-shim.ts'),
'react/jsx-dev-runtime': path.resolve(__dirname, './src/amis-jsx-shim.ts'),
},
},
define: {
'process.env.NODE_ENV': JSON.stringify('production'),
'process.env': JSON.stringify({}),
},
build: {
outDir: 'dist/amis-renderer',
emptyOutDir: true,
lib: {
entry: path.resolve(__dirname, 'src/amis-entry.ts'),
name: 'MyWidget',
formats: ['iife'],
fileName: () => 'amis-renderer.js',
},
rollupOptions: {
external: ['react', 'react-dom', 'antd'],
output: {
globals: {
react: 'amisRequire("react")',
'react-dom': 'amisRequire("react-dom")',
'antd': 'antd',
},
assetFileNames: 'amis-renderer.[ext]',
},
},
assetsInlineLimit: 8192,
cssCodeSplit: false,
minify: 'terser',
},
css: {
postcss: {
plugins: [
tailwindcss(),
autoprefixer(),
prefixSelector({
prefix: SCOPE,
transform(prefix, selector, prefixedSelector, _filePath, rule) {
if (selector === ':root') return selector;
if (/^(html|body)(\s|,|$)/.test(selector)) return selector;
const parent = rule.parent;
if (parent?.type === 'atrule' && /^keyframes/.test(parent.name)) return selector;
return prefixedSelector;
},
}),
],
},
},
})Critical configuration points:
| Config | Purpose |
|---|---|
| Self-executing script, registers on load |
| React NOT bundled — reuse amis SDK's React |
| Prevent 3rd-party libs from bundling their own jsx-runtime |
| Scope all CSS under prefix to prevent style leaks |
| Use |
将React组件编译为可被amis加载的自执行IIFE脚本。
typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/postcss'
import autoprefixer from 'autoprefixer'
import path from 'path'
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
const prefixSelector = require('postcss-prefix-selector')
// CSS作用域前缀——所有样式都在此选择器下生效
const SCOPE = '.my-widget'
export default defineConfig({
plugins: [react({ jsxRuntime: 'classic' })],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'react/jsx-runtime': path.resolve(__dirname, './src/amis-jsx-shim.ts'),
'react/jsx-dev-runtime': path.resolve(__dirname, './src/amis-jsx-shim.ts'),
},
},
define: {
'process.env.NODE_ENV': JSON.stringify('production'),
'process.env': JSON.stringify({}),
},
build: {
outDir: 'dist/amis-renderer',
emptyOutDir: true,
lib: {
entry: path.resolve(__dirname, 'src/amis-entry.ts'),
name: 'MyWidget',
formats: ['iife'],
fileName: () => 'amis-renderer.js',
},
rollupOptions: {
external: ['react', 'react-dom', 'antd'],
output: {
globals: {
react: 'amisRequire("react")',
'react-dom': 'amisRequire("react-dom")',
'antd': 'antd',
},
assetFileNames: 'amis-renderer.[ext]',
},
},
assetsInlineLimit: 8192,
cssCodeSplit: false,
minify: 'terser',
},
css: {
postcss: {
plugins: [
tailwindcss(),
autoprefixer(),
prefixSelector({
prefix: SCOPE,
transform(prefix, selector, prefixedSelector, _filePath, rule) {
if (selector === ':root') return selector;
if (/^(html|body)(\s|,|$)/.test(selector)) return selector;
const parent = rule.parent;
if (parent?.type === 'atrule' && /^keyframes/.test(parent.name)) return selector;
return prefixedSelector;
},
}),
],
},
},
})关键配置点:
| 配置项 | 用途 |
|---|---|
| 自执行脚本,加载时自动注册 |
| React不打包——复用amis SDK的React |
| 防止第三方库打包自己的jsx-runtime |
| 将所有CSS限定在指定前缀下,防止样式泄露 |
| 使用 |
amis-jsx-shim.ts — JSX Runtime Bridge
amis-jsx-shim.ts — JSX运行时桥接文件
Rollup can only externalize , not the sub-path. Third-party dependencies (e.g. ) import from , which would bundle an incompatible React copy. This shim delegates / to the externalized .
externalreactreact/jsx-runtime@tiptap/reactreact/jsx-runtimejsx()jsxs()React.createElement()This file is identical across all webapps — copy directly without modification.
typescript
import React from 'react';
export function jsx(
type: React.ElementType,
props: Record<string, unknown>,
key?: string,
): React.ReactElement {
const { children, ...rest } = props;
if (key !== undefined) (rest as Record<string, unknown>).key = key;
return children !== undefined
? React.createElement(type, rest as React.Attributes, children as React.ReactNode)
: React.createElement(type, rest as React.Attributes);
}
export function jsxs(
type: React.ElementType,
props: Record<string, unknown>,
key?: string,
): React.ReactElement {
const { children, ...rest } = props;
if (key !== undefined) (rest as Record<string, unknown>).key = key;
if (Array.isArray(children)) {
return React.createElement(type, rest as React.Attributes, ...children);
}
return children !== undefined
? React.createElement(type, rest as React.Attributes, children as React.ReactNode)
: React.createElement(type, rest as React.Attributes);
}
export const jsxDEV = jsx;
export const Fragment = React.Fragment;Rollup的只能外部化,无法外部化子路径。第三方依赖(如)会从导入,这会打包一个不兼容的React副本。此垫片将/委托给外部化的。
externalreactreact/jsx-runtime@tiptap/reactreact/jsx-runtimejsx()jsxs()React.createElement()所有Web应用的此文件完全相同——直接复制无需修改。
typescript
import React from 'react';
export function jsx(
type: React.ElementType,
props: Record<string, unknown>,
key?: string,
): React.ReactElement {
const { children, ...rest } = props;
if (key !== undefined) (rest as Record<string, unknown>).key = key;
return children !== undefined
? React.createElement(type, rest as React.Attributes, children as React.ReactNode)
: React.createElement(type, rest as React.Attributes);
}
export function jsxs(
type: React.ElementType,
props: Record<string, unknown>,
key?: string,
): React.ReactElement {
const { children, ...rest } = props;
if (key !== undefined) (rest as Record<string, unknown>).key = key;
if (Array.isArray(children)) {
return React.createElement(type, rest as React.Attributes, ...children);
}
return children !== undefined
? React.createElement(type, rest as React.Attributes, children as React.ReactNode)
: React.createElement(type, rest as React.Attributes);
}
export const jsxDEV = jsx;
export const Fragment = React.Fragment;amis-entry.ts — Registration Entry
amis-entry.ts — 注册入口
IIFE build entry point. Imports styles, defines a bridge component, registers via .
amisLib.Renderer()typescript
import './amis-renderer.css';
import { MyReactComponent } from './components/MyReactComponent';
declare global {
function amisRequire(mod: string): any;
}
function register() {
if (typeof amisRequire === 'undefined') {
console.error('[my-widget] amisRequire is not defined. Load amis SDK first.');
return;
}
const React = amisRequire('react');
const amisLib = amisRequire('amis');
if (!amisLib?.Renderer) {
console.error('[my-widget] amis.Renderer not found.');
return;
}
// Bridge component: amis props → React component props
function MyWidget(props: any) {
const { $schema, data, dispatchEvent } = props;
// Register to amis ScopedContext (makes getComponentById work)
const ScopedContext = amisLib.ScopedContext;
const scoped = React.useContext(ScopedContext);
const compRef = React.useRef(null);
const scopedRef = React.useRef(null);
const componentMethods = {
getValue: () => compRef.current?.getValue(),
validate: async () => compRef.current?.validate(),
};
if (scopedRef.current === null) {
scopedRef.current = { ...componentMethods };
} else {
Object.assign(scopedRef.current, componentMethods);
}
Object.defineProperty(scopedRef.current, 'props', {
get: () => props,
configurable: true,
});
React.useEffect(() => {
if (!scoped || !($schema.id || props.id)) return;
scoped.registerComponent(scopedRef.current);
return () => scoped.unRegisterComponent(scopedRef.current);
}, [$schema.id || props.id]);
// Read custom properties from $schema (configured in amis JSON schema)
const title = $schema.title || '';
const config = $schema.config || {};
// Read from amis data scope
const contextValue = data?.someKey || '';
// Event callback — dispatch events to amis
const handleChange = (val: any) => {
dispatchEvent?.('change', { value: val });
};
// Render the actual React component
return React.createElement(MyReactComponent, {
ref: compRef,
title,
config,
value: contextValue,
onChange: handleChange,
className: 'my-widget', // MUST match CSS scope prefix
});
}
// Register as amis Renderer
amisLib.Renderer({
type: 'my-widget', // type name used in amis JSON schema
autoVar: true,
})(MyWidget);
console.log('[my-widget] amis Renderer registered.');
}
register();IIFE构建的入口点。导入样式,定义桥接组件,通过注册。
amisLib.Renderer()typescript
import './amis-renderer.css';
import { MyReactComponent } from './components/MyReactComponent';
declare global {
function amisRequire(mod: string): any;
}
function register() {
if (typeof amisRequire === 'undefined') {
console.error('[my-widget] amisRequire未定义。请先加载amis SDK。');
return;
}
const React = amisRequire('react');
const amisLib = amisRequire('amis');
if (!amisLib?.Renderer) {
console.error('[my-widget] 未找到amis.Renderer。');
return;
}
// 桥接组件:amis props → React组件props
function MyWidget(props: any) {
const { $schema, data, dispatchEvent } = props;
// 注册到amis ScopedContext(使getComponentById生效)
const ScopedContext = amisLib.ScopedContext;
const scoped = React.useContext(ScopedContext);
const compRef = React.useRef(null);
const scopedRef = React.useRef(null);
const componentMethods = {
getValue: () => compRef.current?.getValue(),
validate: async () => compRef.current?.validate(),
};
if (scopedRef.current === null) {
scopedRef.current = { ...componentMethods };
} else {
Object.assign(scopedRef.current, componentMethods);
}
Object.defineProperty(scopedRef.current, 'props', {
get: () => props,
configurable: true,
});
React.useEffect(() => {
if (!scoped || !($schema.id || props.id)) return;
scoped.registerComponent(scopedRef.current);
return () => scoped.unRegisterComponent(scopedRef.current);
}, [$schema.id || props.id]);
// 从$schema读取自定义属性(在amis JSON schema中配置)
const title = $schema.title || '';
const config = $schema.config || {};
// 从amis数据作用域读取
const contextValue = data?.someKey || '';
// 事件回调——向amis分发事件
const handleChange = (val: any) => {
dispatchEvent?.('change', { value: val });
};
// 渲染实际的React组件
return React.createElement(MyReactComponent, {
ref: compRef,
title,
config,
value: contextValue,
onChange: handleChange,
className: 'my-widget', // 必须匹配CSS作用域前缀
});
}
// 注册为amis Renderer
amisLib.Renderer({
type: 'my-widget', // 在amis JSON schema中使用的类型名称
autoVar: true,
})(MyWidget);
console.log('[my-widget] amis Renderer已注册。');
}
register();amis Props Reference | amis 传入的 Props
amis传入的Props参考
| Prop | Description |
|---|---|
| Full JSON schema node — includes all custom properties you defined |
| amis current data scope (context variables) |
| Dispatch events to amis ( |
| Batch-write values back to amis data scope |
| amis environment config (fetcher, notify, etc.) |
| Prop | 描述 |
|---|---|
| 完整的JSON schema节点——包含所有你定义的自定义属性 |
| amis当前的数据作用域(上下文变量) |
| 向amis分发事件( |
| 批量将值写回amis数据作用域 |
| amis环境配置(fetcher、notify等) |
Using in Amis Schema | 在 amis Schema 中使用
在amis Schema中使用
After registration, reference via in amis JSON schema:
typejson
{
"type": "my-widget",
"id": "widget1",
"title": "Hello World",
"config": { "theme": "dark" },
"onEvent": {
"change": {
"actions": [
{
"actionType": "setValue",
"args": { "value": "${event.data.value}" }
}
]
}
}
}注册完成后,在amis JSON schema中通过引用:
typejson
{
"type": "my-widget",
"id": "widget1",
"title": "Hello World",
"config": { "theme": "dark" },
"onEvent": {
"change": {
"actions": [
{
"actionType": "setValue",
"args": { "value": "${event.data.value}" }
}
]
}
}
}API v6 Response Structures | API v6 响应数据结构
API v6响应数据结构
When calling Steedos API v6 endpoints from webapp code (fetch/axios), use the correct response format:
| Endpoint | Response Format |
|---|---|
| |
| |
| |
| |
| |
| Whatever the function returns — NO wrapping, raw return value |
typescript
// List records — response has { data, totalCount }
const res = await fetch('/api/v6/data/orders?skip=0&top=20');
const { data: orders, totalCount } = await res.json();
// Single record — response IS the record
const res = await fetch(`/api/v6/data/orders/${id}`);
const order = await res.json(); // { _id, name, status, ... }
// Create record — response IS the created record
const res = await fetch('/api/v6/data/orders', { method: 'POST', body: JSON.stringify(record) });
const created = await res.json(); // { _id, name, created, ... }
// Call function — response IS whatever the function returns
const res = await fetch('/api/v6/functions/orders/approve', {
method: 'POST',
body: JSON.stringify({ id: orderId })
});
const result = await res.json(); // e.g. { message: "Approved", success: true }⚠️ and are REQUIRED for all list endpoints (, , ).
skiptop/api/v6/data//api/v6/tables//api/v6/direct/📖 For complete API v6 documentation (all endpoints, filter operators, complex filters, authentication), load the steedos-server-api skill.📖 如需 API v6 完整文档(所有端点、筛选运算符、复合筛选、认证方式),请加载 steedos-server-api 技能。
从Web应用代码(fetch/axios)调用Steedos API v6端点时,需使用正确的响应格式:
| 端点 | 响应格式 |
|---|---|
| |
| |
| |
| |
| |
| 函数返回的任意内容——无包裹,原始返回值 |
typescript
// 获取列表——响应包含{ data, totalCount }
const res = await fetch('/api/v6/data/orders?skip=0&top=20');
const { data: orders, totalCount } = await res.json();
// 获取单条记录——响应即为记录本身
const res = await fetch(`/api/v6/data/orders/${id}`);
const order = await res.json(); // { _id, name, status, ... }
// 创建记录——响应即为创建的记录
const res = await fetch('/api/v6/data/orders', { method: 'POST', body: JSON.stringify(record) });
const created = await res.json(); // { _id, name, created, ... }
// 调用函数——响应即为函数返回值
const res = await fetch('/api/v6/functions/orders/approve', {
method: 'POST',
body: JSON.stringify({ id: orderId })
});
const result = await res.json(); // 例如:{ message: "Approved", success: true }⚠️ 所有列表端点(、、)必须包含和参数。
/api/v6/data//api/v6/tables//api/v6/direct/skiptop📖 如需API v6完整文档(所有端点、筛选运算符、复合筛选、认证方式),请加载 steedos-server-api 技能。📖 如需 API v6 完整文档(所有端点、筛选运算符、复合筛选、认证方式),请加载 steedos-server-api 技能。
Express Router for SPA Access | 通过 Router 提供 SPA 访问
通过Express路由提供SPA访问
Note: The SPA router is created by default in Step 5 of "Creating a New Webapp". This section provides detailed reference for the router implementation.注意: SPA 路由已在「创建新 webapp」Step 5 中默认创建。本节提供路由实现的详细参考。
Webapps serve as standalone SPA applications via Express routes in :
main/default/routes/javascript
'use strict';
const express = require('express');
const router = express.Router();
const { requireAuthentication } = require("@steedos/auth");
const path = require('path');
const packageRoot = path.dirname(require.resolve('@steedos-labs/my-package/package.json'));
const webappDistPath = path.join(packageRoot, 'webapps', 'my-widget', 'dist');
const amisRendererDistPath = path.join(webappDistPath, 'amis-renderer');
// Main page entry (requires auth)
router.get('/api/my-package/my-widget', requireAuthentication, async (req, res) => {
try {
if (process.env.NODE_ENV === 'development') {
return res.redirect('http://localhost:5173');
}
res.sendFile(path.join(webappDistPath, 'index.html'), { dotfiles: 'allow' });
} catch (e) {
res.status(500).send({ errors: [{ errorMessage: e.message }] });
}
});
// Static assets
router.use('/api/my-package/my-widget', express.static(webappDistPath));
// SPA fallback (frontend routing support)
router.use('/api/my-package/my-widget', (req, res, next) => {
if (req.method !== 'GET') return next();
if (path.extname(req.path)) return next();
if (req.path === '/' || req.path === '') return next();
requireAuthentication(req, res, () => {
try {
if (process.env.NODE_ENV === 'development') {
return res.redirect('http://localhost:5173');
}
res.sendFile(path.join(webappDistPath, 'index.html'), { dotfiles: 'allow' });
} catch (e) {
res.status(500).send({ errors: [{ errorMessage: e.message }] });
}
});
});
// amis renderer static assets
router.use('/api/my-package/my-widget-amis', express.static(amisRendererDistPath));
exports.default = router;Important: The webapp's must match the router path:
vite.config.tsbasetypescript
base: command === 'build' ? '/api/my-package/my-widget/' : '/',注意: SPA路由已在「创建新Web应用」步骤5中默认创建。本节提供路由实现的详细参考。注意: SPA 路由已在「创建新 webapp」Step 5 中默认创建。本节提供路由实现的详细参考。
Web应用通过中的Express路由作为独立SPA应用提供服务:
main/default/routes/javascript
'use strict';
const express = require('express');
const router = express.Router();
const { requireAuthentication } = require("@steedos/auth");
const path = require('path');
const packageRoot = path.dirname(require.resolve('@steedos-labs/my-package/package.json'));
const webappDistPath = path.join(packageRoot, 'webapps', 'my-widget', 'dist');
const amisRendererDistPath = path.join(webappDistPath, 'amis-renderer');
// 主页面入口(需认证)
router.get('/api/my-package/my-widget', requireAuthentication, async (req, res) => {
try {
if (process.env.NODE_ENV === 'development') {
return res.redirect('http://localhost:5173');
}
res.sendFile(path.join(webappDistPath, 'index.html'), { dotfiles: 'allow' });
} catch (e) {
res.status(500).send({ errors: [{ errorMessage: e.message }] });
}
});
// 静态资源
router.use('/api/my-package/my-widget', express.static(webappDistPath));
// SPA fallback(支持前端路由)
router.use('/api/my-package/my-widget', (req, res, next) => {
if (req.method !== 'GET') return next();
if (path.extname(req.path)) return next();
if (req.path === '/' || req.path === '') return next();
requireAuthentication(req, res, () => {
try {
if (process.env.NODE_ENV === 'development') {
return res.redirect('http://localhost:5173');
}
res.sendFile(path.join(webappDistPath, 'index.html'), { dotfiles: 'allow' });
} catch (e) {
res.status(500).send({ errors: [{ errorMessage: e.message }] });
}
});
});
// amis渲染器静态资源
router.use('/api/my-package/my-widget-amis', express.static(amisRendererDistPath));
exports.default = router;重要提示: Web应用的中的必须与路由路径匹配:
vite.config.tsbasetypescript
base: command === 'build' ? '/api/my-package/my-widget/' : '/',Client Loader File | 客户端加载文件
客户端加载文件
⚠️ Required: Each webapp MUST have a client loader file at . This file tells Steedos to load the compiled amis renderer JS and CSS into the frontend. Without it, the amis component will NOT be available in pages.
main/default/client/<webapp-name>.client.js⚠️ 必须:每个 webapp 都必须有 加载文件。没有此文件,amis 自定义组件不会被加载到前端。
main/default/client/<webapp-name>.client.js⚠️ 必须:每个Web应用都必须有加载文件。此文件告知Steedos将编译后的amis渲染器JS和CSS加载到前端。没有此文件,amis自定义组件将无法在页面中使用。
main/default/client/<webapp-name>.client.js⚠️ 必须:每个 webapp 都必须有 加载文件。没有此文件,amis 自定义组件不会被加载到前端。
main/default/client/<webapp-name>.client.jsFile Naming | 命名规则
命名规则
The file name MUST match the webapp/public folder name:
| Webapp Directory | Public Output | Client Loader File |
|---|---|---|
| | |
| | |
| | |
文件名必须与webapp/public文件夹名称匹配:
| Web应用目录 | 公共输出目录 | 客户端加载文件 |
|---|---|---|
| | |
| | |
| | |
File Content | 文件内容
文件内容
javascript
// main/default/client/my-widget.client.js
waitForThing(window, 'antd').then(function(){
loadJs('/my-widget/amis-renderer.js');
loadCss('/my-widget/amis-renderer.css')
})How it works:
- — Waits until
waitForThing(window, 'antd')is available. The IIFE useswindow.antdandamisRequire("react")which depend on antd being loaded first.amisRequire("amis") - — Loads the compiled IIFE script from
loadJs('/my-widget/amis-renderer.js'). The script self-executes and registers the amis Renderer.public/my-widget/ - — Loads the scoped CSS from
loadCss('/my-widget/amis-renderer.css').public/my-widget/
The paths () correspond to the directory, which Steedos serves as static files.
/my-widget/...public/my-widget/waitForThingloadJsloadCssjavascript
// main/default/client/my-widget.client.js
waitForThing(window, 'antd').then(function(){
loadJs('/my-widget/amis-renderer.js');
loadCss('/my-widget/amis-renderer.css')
})工作原理:
- — 等待
waitForThing(window, 'antd')可用。IIFE使用window.antd和amisRequire("react"),它们依赖antd先加载。amisRequire("amis") - — 从
loadJs('/my-widget/amis-renderer.js')加载编译后的IIFE脚本。脚本自执行并注册amis Renderer。public/my-widget/ - — 从
loadCss('/my-widget/amis-renderer.css')加载作用域化的CSS。public/my-widget/
路径()对应目录,Steedos将其作为静态文件提供服务。
/my-widget/...public/my-widget/waitForThingloadJsloadCssCSS Isolation | CSS 隔离
CSS隔离
Scope Prefix | 作用域前缀
作用域前缀
postcss-prefix-selectorcss
/* Before */
.btn { color: red; }
/* After */
.my-widget .btn { color: red; }The component root element MUST have the matching class name (passed via in ).
classNameamis-entry.tspostcss-prefix-selectorcss
/* 之前 */
.btn { color: red; }
/* 之后 */
.my-widget .btn { color: red; }组件根元素必须有匹配的类名(在中通过传递)。
amis-entry.tsclassNameTailwind CSS v4 Workarounds
Tailwind CSS v4兼容方案
Tailwind v4 introduces , , — global CSS features that conflict with the host page. Add these 3 PostCSS plugins after in :
@property@supports@layerprefixSelectorvite.amis.config.tstypescript
// 1. Remove @property — global, pollutes host page CSS property registry
function removeAtProperty() {
return {
postcssPlugin: 'remove-at-property',
AtRule: { property(rule) { rule.remove() } },
}
}
removeAtProperty.postcss = true
// 2. Unwrap @supports fallbacks — @property removed, so variable defaults must apply unconditionally
function unwrapTwSupports() {
return {
postcssPlugin: 'unwrap-tw-supports',
AtRule: {
supports(atRule) {
if (atRule.params.includes('-webkit-hyphens') || atRule.params.includes('-moz-orient')) {
atRule.nodes?.length ? atRule.replaceWith(atRule.nodes) : atRule.remove()
}
},
},
}
}
unwrapTwSupports.postcss = true
// 3. Remove @layer — host page CSS not in layers, layer styles can never override
function removeAtLayer() {
return {
postcssPlugin: 'remove-at-layer',
AtRule: {
layer(atRule) {
atRule.nodes?.length ? atRule.replaceWith(atRule.nodes) : atRule.remove()
},
},
}
}
removeAtLayer.postcss = trueTailwind v4引入了、、——这些全局CSS特性会与宿主页面冲突。在中之后添加这3个PostCSS插件:
@property@supports@layervite.amis.config.tsprefixSelectortypescript
// 1. 移除@property——全局特性,会污染宿主页面CSS属性注册表
function removeAtProperty() {
return {
postcssPlugin: 'remove-at-property',
AtRule: { property(rule) { rule.remove() } },
}
}
removeAtProperty.postcss = true
// 2. 展开@supports降级——@property已移除,变量默认值必须无条件生效
function unwrapTwSupports() {
return {
postcssPlugin: 'unwrap-tw-supports',
AtRule: {
supports(atRule) {
if (atRule.params.includes('-webkit-hyphens') || atRule.params.includes('-moz-orient')) {
atRule.nodes?.length ? atRule.replaceWith(atRule.nodes) : atRule.remove()
}
},
},
}
}
unwrapTwSupports.postcss = true
// 3. 移除@layer——宿主页面CSS不在层中,层样式永远无法覆盖
function removeAtLayer() {
return {
postcssPlugin: 'remove-at-layer',
AtRule: {
layer(atRule) {
atRule.nodes?.length ? atRule.replaceWith(atRule.nodes) : atRule.remove()
},
},
}
}
removeAtLayer.postcss = trueMulti-Webapp Management | 多 webapp 管理
多Web应用管理
Package Root Configuration
软件包根目录配置
json
{
"name": "@steedos-labs/my-package",
"files": [
"main/default/client",
"main/default/routes",
"webapps/designer/dist",
"webapps/dashboard/dist",
"public/designer",
"public/dashboard",
"package.service.js"
],
"scripts": {
"build:designer": "cd webapps/designer && npm run build:all",
"build:dashboard": "cd webapps/dashboard && npm run build:all",
"build:webapps": "npm run build:designer && npm run build:dashboard",
"release": "npm run build:webapps && npm publish"
}
}json
{
"name": "@steedos-labs/my-package",
"files": [
"main/default/client",
"main/default/routes",
"webapps/designer/dist",
"webapps/dashboard/dist",
"public/designer",
"public/dashboard",
"package.service.js"
],
"scripts": {
"build:designer": "cd webapps/designer && npm run build:all",
"build:dashboard": "cd webapps/dashboard && npm run build:all",
"build:webapps": "npm run build:designer && npm run build:dashboard",
"release": "npm run build:webapps && npm publish"
}
}Webapp Independence | webapp 独立性
Web应用独立性
- Each webapp is a fully independent Vite project with its own
node_modules - Different webapps can use different dependency versions
- CSS scope prefixes are unique per webapp — no style conflicts
- amis names must be globally unique (e.g.
type,workflow-form-v2)dashboard-chart - is identical across all webapps — copy directly
amis-jsx-shim.ts
- 每个Web应用都是完全独立的Vite项目,拥有自己的
node_modules - 不同Web应用可使用不同的依赖版本
- CSS作用域前缀每个Web应用唯一——无样式冲突
- amis的名称必须全局唯一(如
type、workflow-form-v2)dashboard-chart - 在所有Web应用中完全相同——直接复制
amis-jsx-shim.ts
Development Workflow | 开发工作流
开发工作流
bash
undefinedbash
undefined---- Development ----
---- 开发阶段 ----
cd webapps/my-widget && npm install
npm run dev # http://localhost:5173
cd webapps/my-widget && npm install
npm run dev # 访问http://localhost:5173
---- Build ----
---- 构建阶段 ----
npm run build:amis # IIFE only → dist/amis-renderer/ → public/my-widget/
npm run build:all # Standard + IIFE
npm run build:amis # 仅构建IIFE → dist/amis-renderer/ → public/my-widget/
npm run build:all # 标准构建 + IIFE构建
---- Test in Steedos ----
---- 在Steedos中测试 ----
Start Steedos — public/my-widget/ auto-served as static files
启动Steedos——public/my-widget/自动作为静态文件提供服务
amis pages load amis-renderer.js and auto-register the component
amis页面加载amis-renderer.js并自动注册组件
---- Publish ----
---- 发布阶段 ----
cd ../.. && npm publish # files field ensures public/my-widget is included
undefinedcd ../.. && npm publish # files字段确保public/my-widget被包含在内
undefinedMinimum Checklist | 最小化清单
最小化清单
| # | File | Description |
|---|---|---|
| 1 | | React business components |
| 2 | | JSX bridge (copy directly) |
| 3 | | amis registration entry (modify component import and type) |
| 4 | | IIFE build config (modify entry, global name, CSS scope) |
| 5 | | Add |
| 6 | Root | Include |
| 7 | | ⚠️ Client loader — triggers loading of amis renderer into frontend |
| 8 | | ⚠️ SPA router — enables standalone SPA access via |
| 序号 | 文件 | 描述 |
|---|---|---|
| 1 | | React业务组件 |
| 2 | | JSX桥接文件(直接复制) |
| 3 | | amis注册入口(修改组件导入和类型) |
| 4 | | IIFE构建配置(修改入口、全局名称、CSS作用域) |
| 5 | | 添加 |
| 6 | 根目录 | 在 |
| 7 | | ⚠️ 客户端加载文件——触发将amis渲染器加载到前端 |
| 8 | | ⚠️ SPA路由——允许通过 |
Post-Development Prompt | 开发完成后提示
开发完成后提示
After completing webapp development and configuration, always inform the user about the available access methods:
webapp 开发和配置完成后,务必告知用户可用的访问方式:
✅ Webapp开发完成!你可以通过以下方式访问:{webapp-name}
- amis 组件方式:在 amis Schema 中使用
嵌入到任意页面"type": "{webapp-name}"- 独立 SPA 方式:通过浏览器访问
{ROOT_URL}/api/{package-name}/{webapp-name}- 开发模式:运行
,访问cd webapps/{webapp-name} && npm run devhttp://localhost:5173
Web应用开发和配置完成后,务必告知用户可用的访问方式:
webapp 开发和配置完成后,务必告知用户可用的访问方式:
✅ Web应用开发完成!你可以通过以下方式访问:{webapp-name}
- amis组件方式:在amis Schema中使用
嵌入到任意页面"type": "{webapp-name}"- 独立SPA方式:通过浏览器访问
{ROOT_URL}/api/{package-name}/{webapp-name}- 开发模式:运行
,访问cd webapps/{webapp-name} && npm run devhttp://localhost:5173
FAQ | 常见问题
常见问题
Q: Why can't React be bundled into the IIFE?
A: amis SDK ships its own React. Bundling two copies causes Hooks errors and broken Context sharing. Must use to reuse amis's React.
amisRequire("react")Q: Third-party lib imports ?
A: delegates / to . Map both and in Vite aliases.
react/jsx-runtimeamis-jsx-shim.tsjsx()jsxs()React.createElement()react/jsx-runtimereact/jsx-dev-runtimeQ: error?
A: Some dependencies reference . Add to Vite :
process is not definedprocess.env.NODE_ENVdefinetypescript
define: {
'process.env.NODE_ENV': JSON.stringify('production'),
'process.env': JSON.stringify({}),
}Q: Style conflicts with host page?
A: + root element class name. Exclude , , , from prefixing.
postcss-prefix-selector:roothtmlbody@keyframesQ: How to expose component methods to amis ?
A: Use in the bridge component, register via , and mount methods like and .
getComponentByIdamisLib.ScopedContextscoped.registerComponent()getValue()validate()Q: How to handle antd?
A: When host page loads antd via CDN, mark in rollup . For dayjs locale (antd DatePicker dependency), handle it in .
antdexternalamis-entry.ts问:为什么不能将React打包到IIFE中?
答:amis SDK自带React。打包两个副本会导致Hooks错误和Context共享失效。必须使用复用amis的React。
amisRequire("react")问:第三方库导入怎么办?
答:将/委托给。在Vite别名中映射和。
react/jsx-runtimeamis-jsx-shim.tsjsx()jsxs()React.createElement()react/jsx-runtimereact/jsx-dev-runtime问:出现错误?
答:某些依赖引用。在Vite的中添加:
process is not definedprocess.env.NODE_ENVdefinetypescript
define: {
'process.env.NODE_ENV': JSON.stringify('production'),
'process.env': JSON.stringify({}),
}问:与宿主页面样式冲突?
答:使用 + 根元素类名。排除、、、添加前缀。
postcss-prefix-selector:roothtmlbody@keyframes问:如何向amis的暴露组件方法?
答:在桥接组件中使用,通过注册,并挂载和等方法。
getComponentByIdamisLib.ScopedContextscoped.registerComponent()getValue()validate()问:如何处理antd?
答:当宿主页面通过CDN加载antd时,在rollup的中标记。对于dayjs本地化(antd DatePicker依赖),在中处理。
externalantdamis-entry.ts