Token导航 LogoToken导航

Agent Skill · 前端设计

steedos-webapps(Steedos Web 应用)

steedos-webapps 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

steedos-webapps 是前端设计类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,第三方扫描已通过,按安装量在前端设计类的 10,216 个 Skill 里排第 7,824。

总安装 339周安装 14GitHub Stars 1,589下载 111核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:steedos-webapps(Steedos Web 应用)
来源仓库:https://github.com/steedos/steedos-platform
仓库路径:skills/steedos-webapps
安装命令:
npx skills add https://github.com/steedos/steedos-platform --skill steedos-webapps
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/steedos/steedos-platform --skill steedos-webapps

这个 Skill 是做什么的

前端设计类 Skill 关注页面结构、视觉规范和组件写法,输出通常是可运行的界面代码。

用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中围绕仓库状态、代码变更或协作事项进行整理。可结合来源仓库、安装命令和原始 README 继续核验具体用法。

通过 npx skills add 命令从指定 GitHub 仓库安装。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。steedos-webapps 属于前端设计类 Skill,可作为该场景下的辅助能力补充。

来源仓库内路径:skills/steedos-webapps

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/steedos/steedos-platform --skill steedos-webap…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

steedos-webapps 怎么安装?

在终端执行 npx skills add https://github.com/steedos/steedos-platform --skill steedos-webapps。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

steedos-webapps 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

steedos-webapps 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

steedos-webapps 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

steedos-webapps 用的人多吗?

公开数据是累计安装 339 次,近一周安装 14 次,下载 111 次,GitHub 1,589 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

steedos-webapps 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。
  • 来源仓库有 1,589 星,维护活跃度相对可查。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理前端设计类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

前端设计类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
frontend-design

前端设计 · Codex、Claude

8,823,360126,072只读MIT通过
vercel-composition-patterns

Vercel 组合模式 · Codex、Claude

3,858,81625,893需要联网MIT通过
vercel-react-native-skills

Vercel React native skills 前端 · Codex、Claude

2,468,66425,878只读MIT通过
adapt

adapt 搜索 · Codex、Claude

2,027,04023,125操作浏览器未明确通过
bolder

bolder 分析 · Codex、Claude

2,004,28823,130只读未明确通过
steedos-webapps

Steedos Web 应用 · 本页

3391,589需要联网未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Steedos Webapps | Steedos 软件包自定义 React 应用

Overview | 概述

Steedos packages can contain React + Vite sub-projects in the webapps/ 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.

Steedos 软件包通过 webapps/ 目录管理 React 子项目。每个子项目是独立的 Vite 应用,可独立开发调试,也可编译为 amis 自注册组件。

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 definition

Key Concepts:

  • Each webapps/ subdirectory is an independent React + Vite project
  • Two build modes: standard Vite build (dev) + IIFE build (amis component)
  • IIFE output is copied to public/<webapp-name>/, served as static files by Steedos
  • main/default/client/<webapp-name>.client.js triggers loading of the IIFE into the Steedos frontend
  • Each webapp is isolated — can use different dependencies and versions

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 库,以下是两个推荐选项。

ScaffoldDescriptionWhen to Use
antd (default)Ant Design component library. Reuses host page's antd via external, no extra bundle size.Default choice — enterprise forms, tables, standard UI. Recommended when unsure.
shadcn/uiTailwind CSS + Radix UI primitives. Components are copied into project (not a dependency).Custom/modern UI, full design control, lightweight output.
OtherAny React UI library (MUI, Chakra, Mantine, headless, etc.). Follow the same IIFE build pattern.Specific design system requirements or team preference.

Key Differences | 关键区别

antdshadcn/ui
StylingCSS-in-JS (host page antd)Tailwind CSS utility classes
Bundleantd marked as external — zero bundle costComponents copied into src/, bundled into IIFE
TailwindOptionalRequired
waitForThing targetwindow.antdwindow.antd (still needed — amis SDK depends on antd)
PostCSS pluginspostcss-prefix-selectorpostcss-prefix-selector + Tailwind v4 workarounds (removeAtProperty, unwrapTwSupports, removeAtLayer)

antd Scaffold Setup | antd 脚手架

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

In vite.amis.config.ts, mark antd as external:

rollupOptions: {
  external: ['react', 'react-dom', 'antd'],
  output: {
    globals: {
      react: 'amisRequire("react")',
      'react-dom': 'amisRequire("react-dom")',
      'antd': 'antd',
    },
  },
},

shadcn/ui Scaffold Setup | shadcn/ui 脚手架

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

In vite.amis.config.ts, do NOT externalize antd (shadcn/ui doesn't use it):

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).

Other Scaffolds | 其他脚手架

Any React-compatible UI library works. The core requirements are the same:

  1. Mark react and react-dom as external in rollup (use amisRequire)
  2. If the library is already on the host page (like antd), mark it as external too
  3. Use postcss-prefix-selector for CSS isolation
  4. If using Tailwind v4, add the 3 PostCSS workaround plugins

Creating a New Webapp | 创建新 webapp

Step 1: Initialize Vite Project | 初始化

cd my-package/webapps
npm create vite@latest my-widget -- --template react-ts
cd my-widget
npm install

Step 2: Add Build Dependencies | 添加构建依赖

# Common dependencies (both scaffolds)
npm install -D @tailwindcss/postcss autoprefixer postcss-prefix-selector terser

# antd scaffold
npm install antd

# shadcn/ui scaffold
npx shadcn@latest init
npm install -D tailwindcss

Step 3: Create amis Integration Files | 创建 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)

Step 4: Configure Build Scripts | 配置构建脚本

In the webapp's package.json:

{
  "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 路由

Create main/default/routes/<webapp-name>.router.js to serve the webapp as a standalone SPA:

创建 main/default/routes/<webapp-name>.router.js,让 webapp 可以作为独立 SPA 访问:

// 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 vite.config.ts to set base matching the router path:

同时更新 webapp 的 vite.config.ts,设置 base 与路由路径一致:

// webapps/my-widget/vite.config.ts
export default defineConfig(({ command }) => ({
  base: command === 'build' ? '/api/my-package/my-widget/' : '/',
  // ... other config
}))

Step 6: Register in Package Root | 在软件包根目录注册

{
  "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

Compiles React components into self-executing IIFE scripts loadable by amis.

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:

ConfigPurpose
formats: ['iife']Self-executing script, registers on load
external: ['react', 'react-dom']React NOT bundled — reuse amis SDK's React
react/jsx-runtime aliasPrevent 3rd-party libs from bundling their own jsx-runtime
postcss-prefix-selectorScope all CSS under prefix to prevent style leaks
jsxRuntime: 'classic'Use React.createElement instead of new JSX Transform

amis-jsx-shim.ts — JSX Runtime Bridge

Rollup external can only externalize react, not the react/jsx-runtime sub-path. Third-party dependencies (e.g. @tiptap/react) import from react/jsx-runtime, which would bundle an incompatible React copy. This shim delegates jsx()/jsxs() to the externalized React.createElement().

This file is identical across all webapps — copy directly without modification.

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

IIFE build entry point. Imports styles, defines a bridge component, registers via amisLib.Renderer().

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();

amis Props Reference | amis 传入的 Props

PropDescription
$schemaFull JSON schema node — includes all custom properties you defined
dataamis current data scope (context variables)
dispatchEventDispatch events to amis (change, submit, etc.)
onBulkChangeBatch-write values back to amis data scope
envamis environment config (fetcher, notify, etc.)

Using in Amis Schema | 在 amis Schema 中使用

After registration, reference via type in amis JSON schema:

{
  "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 响应数据结构

When calling Steedos API v6 endpoints from webapp code (fetch/axios), use the correct response format:

EndpointResponse Format
GET /api/v6/data/:obj?skip=0&top=20 (list){"data": [...], "totalCount": 42}
GET /api/v6/data/:obj/:id (single){"_id": "...", "name": "...",...} — Raw document, NOT wrapped
POST /api/v6/data/:obj (create){"_id": "...",...} — Raw created document, NOT wrapped
PATCH /api/v6/data/:obj/:id (update){"_id": "...",...} — Raw updated document, NOT wrapped
DELETE /api/v6/data/:obj/:id (delete){"deleted": true, "_id": "..."}
POST /api/v6/functions/:obj/:fn (function)Whatever the function returns — NO wrapping, raw return value
// 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 }

⚠️ skip and top are REQUIRED for all list endpoints (/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 技能。

Express Router for SPA Access | 通过 Router 提供 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/:

'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 vite.config.ts base must match the router path:

base: command === 'build' ? '/api/my-package/my-widget/' : '/',

Client Loader File | 客户端加载文件

⚠️ Required: Each webapp MUST have a client loader file at main/default/client/<webapp-name>.client.js. 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.

⚠️ 必须:每个 webapp 都必须有 main/default/client/<webapp-name>.client.js 加载文件。没有此文件,amis 自定义组件不会被加载到前端。

File Naming | 命名规则

The file name MUST match the webapp/public folder name:

Webapp DirectoryPublic OutputClient Loader File
webapps/designer/public/designer/main/default/client/designer.client.js
webapps/dashboard/public/dashboard/main/default/client/dashboard.client.js
webapps/workstation/public/workstation/main/default/client/workstation.client.js

File Content | 文件内容

// 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:

  1. waitForThing(window, 'antd') — Waits until window.antd is available. The IIFE uses amisRequire("react") and amisRequire("amis") which depend on antd being loaded first.
  2. loadJs('/my-widget/amis-renderer.js') — Loads the compiled IIFE script from public/my-widget/. The script self-executes and registers the amis Renderer.
  3. loadCss('/my-widget/amis-renderer.css') — Loads the scoped CSS from public/my-widget/.

The paths (/my-widget/...) correspond to the public/my-widget/ directory, which Steedos serves as static files.

waitForThing、loadJs、loadCss 是 Steedos 前端内置的全局工具函数,无需额外引入。

CSS Isolation | CSS 隔离

Scope Prefix | 作用域前缀

postcss-prefix-selector adds a scope class to all CSS rules:

/* Before */
.btn { color: red; }

/* After */
.my-widget .btn { color: red; }

The component root element MUST have the matching class name (passed via className in amis-entry.ts).

Tailwind CSS v4 Workarounds

Tailwind v4 introduces @property, @supports, @layer — global CSS features that conflict with the host page. Add these 3 PostCSS plugins after prefixSelector in vite.amis.config.ts:

// 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 = true

Multi-Webapp Management | 多 webapp 管理

Package Root Configuration

{
  "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 独立性

  • 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 type names must be globally unique (e.g. workflow-form-v2, dashboard-chart)
  • amis-jsx-shim.ts is identical across all webapps — copy directly

Development Workflow | 开发工作流

# ---- Development ----
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

# ---- Test in Steedos ----
# Start Steedos — public/my-widget/ auto-served as static files
# amis pages load amis-renderer.js and auto-register the component

# ---- Publish ----
cd ../.. && npm publish        # files field ensures public/my-widget is included

Minimum Checklist | 最小化清单

#FileDescription
1webapps/xxx/src/components/React business components
2webapps/xxx/src/amis-jsx-shim.tsJSX bridge (copy directly)
3webapps/xxx/src/amis-entry.tsamis registration entry (modify component import and type)
4webapps/xxx/vite.amis.config.tsIIFE build config (modify entry, global name, CSS scope)
5webapps/xxx/package.jsonAdd build:amis script
6Root package.jsonInclude public/xxx, main/default/client, main/default/routes in files, add build command
7main/default/client/xxx.client.js⚠️ Client loader — triggers loading of amis renderer into frontend
8main/default/routes/xxx.router.js⚠️ SPA router — enables standalone SPA access via /api/<pkg>/xxx

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 dev,访问 http://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 amisRequire("react") to reuse amis's React.

Q: Third-party lib imports react/jsx-runtime? A: amis-jsx-shim.ts delegates jsx()/jsxs() to React.createElement(). Map both react/jsx-runtime and react/jsx-dev-runtime in Vite aliases.

Q: process is not defined error? A: Some dependencies reference process.env.NODE_ENV. Add to Vite define:

define: {
  'process.env.NODE_ENV': JSON.stringify('production'),
  'process.env': JSON.stringify({}),
}

Q: Style conflicts with host page? A: postcss-prefix-selector + root element class name. Exclude :root, html, body, @keyframes from prefixing.

Q: How to expose component methods to amis getComponentById? A: Use amisLib.ScopedContext in the bridge component, register via scoped.registerComponent(), and mount methods like getValue() and validate().

Q: How to handle antd? A: When host page loads antd via CDN, mark antd in rollup external. For dayjs locale (antd DatePicker dependency), handle it in amis-entry.ts.

平台分布

Codex

36.24%
按下载量换算40

Claude

28.97%
按下载量换算32

Cursor

20.46%
按下载量换算23

Gemini CLI

9.82%
按下载量换算11

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills