Token导航 LogoToken导航

Agent Skill · 研究检索

plugin-full-frontend-system-migration(插件完整前端系统迁移)

用于辅助前端页面、组件、样式和交互逻辑的开发与维护。它适合让 Agent 生成或审查 React、Next.js、Vue、Tailwind、CSS 等相关代码,整理组件结构,或定位布局和性能问题。使用时需要结合项目现有设计系统、路由和构建方式,避免只生成孤立片段;涉及页面改动时,应配合本地预览和构建检查确认视觉效果。

plugin-full-frontend-system-migration 是研究检索类 Agent Skill,可用宿主为 Local Agent,通过unknown获取,权限为执行命令;许可证信息还不明确,暂无第三方扫描结果,按安装量在研究检索类的 73,675 个 Skill 里排第 37,435。

总安装 1,738周安装 71下载 562核验 2026-05-01
研究检索执行命令unknown未标认证来源可访问许可证需确认审计未展示Local Agent

安装说明

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

安装方式

通过对话安装

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

请帮我安装这个 Agent Skill:plugin-full-frontend-system-migration(插件完整前端系统迁移)
来源仓库:https://backstage.io
仓库路径:plugin-full-frontend-system-migration
安装命令:
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。当前暂无明确安装命令,请以来源页面说明为准。

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

plugin-full-frontend-system-migration 用于辅助前端页面、组件和样式开发,适合 React、Vue 等项目维护场景。它适用于组件结构整理、布局调整和性能问题定位,可帮助 Agent 生成或审查相关代码片段。

使用时需结合现有设计系统和构建方式,避免生成孤立片段;涉及页面改动时应配合本地预览确认效果。该技能适用于 Local Agent 环境,无需额外安装命令,但需确保本地开发环境已配置好对应框架。使用前应检查项目依赖和路由配置,避免破坏现有功能或引入兼容性问题。

来源仓库内路径:plugin-full-frontend-system-migration

上手步骤

4 步
  1. 1

    确认自己的宿主工具

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

  2. 2

    拿到安装命令

    没有现成安装命令,按unknown的说明从来源页面获取,仓库路径是 plugin-full-frontend-system-migration。

  3. 3

    安装后重启宿主

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

  4. 4

    按最小权限试用

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

常见问题

6 条

plugin-full-frontend-system-migration 怎么安装?

公开资料没有给出可以直接复制的命令,需要按来源页面说明手动安装,仓库内路径为 plugin-full-frontend-system-migration。

plugin-full-frontend-system-migration 支持哪些 AI 工具?

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

plugin-full-frontend-system-migration 需要什么权限?

权限级别是执行命令。安装流程涉及命令执行,可能通过 第三方 CLI 联网下载 Skill 或依赖。用户安装前应确认命令来源、仓库内容和执行环境。

plugin-full-frontend-system-migration 是免费的吗?

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

plugin-full-frontend-system-migration 用的人多吗?

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

plugin-full-frontend-system-migration 安全吗?

没有查到第三方扫描结果,安全性无法从公开资料判断,安装前请自行审查源码、依赖和命令行为。

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

值得选的地方

  • 专注 Local Agent 一种宿主,触发方式和使用习惯不用来回切换。

先想清楚的地方

  • 需要执行命令,装到常用环境前要先评估影响面。
  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 没有第三方扫描结果,安全性只能靠自己读源码判断。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Local Agent,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。

不太适合

  • 不能接受 Skill 在你机器上执行命令的人。
  • 需要明确授权才能用到公司项目里的场景。
  • 要求有安全扫描结论才肯安装的团队。

研究检索类 Skill 对比

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

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
plugin-full-frontend-system-migration

插件完整前端系统迁移 · 本页

1,738—执行命令未明确未收录

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

SKILL.md 原文

Full Plugin Migration to the New Frontend System

This skill helps fully migrate an existing Backstage plugin from the old frontend system to the new one. Unlike adding dual support (which keeps the old system working), this is a complete migration that removes all @backstage/core-plugin-api usage and makes the plugin work exclusively with the new frontend system.

This is the preferred approach for internal plugins that are only used in a single app, since there is no need to maintain backward compatibility. It can also be used for published plugins when you're ready to drop old system support entirely.

It is highly recommended to be on Backstage version 1.49.x or above before starting this, although not mandatory, you may face issues with some of the instructions below. This can be verified by looking in the backstage.json file in the root of the repository.

Key Differences from Dual Support

AspectDual SupportFull Migration
Entry pointOld src/plugin.ts + new src/alpha.tsxSingle src/plugin.tsx
Plugin creationBoth createPlugin and createFrontendPluginOnly createFrontendPlugin
Core dependencyKeeps @backstage/core-plugin-apiRemoves it, uses only @backstage/frontend-plugin-api
Route refsReuses @backstage/core-plugin-api refs directlyUses createRouteRef from @backstage/frontend-plugin-api
Page shellOld pages keep Page/Header, NFS pages skip itAll pages rely on framework's PageLayout/PluginHeader
Internal routingMay keep legacy <Route> trees in componentsReplaced with SubPageBlueprint tabbed pages
CompatibilityNot neededNot needed

Step 1: Migrate Route Refs

Replace createRouteRef / createSubRouteRef / createExternalRouteRef imports:

// OLD (src/routes.ts)
import {
  createRouteRef,
  createSubRouteRef,
  createExternalRouteRef,
} from '@backstage/core-plugin-api';

export const rootRouteRef = createRouteRef({ id: 'my-plugin' });
export const detailsRouteRef = createSubRouteRef({
  id: 'my-plugin-details',
  parent: rootRouteRef,
  path: '/details/:id',
});
export const externalDocsRouteRef = createExternalRouteRef({ id: 'docs' });

// NEW (src/routes.ts)
import {
  createRouteRef,
  createSubRouteRef,
  createExternalRouteRef,
} from '@backstage/frontend-plugin-api';

export const rootRouteRef = createRouteRef();
export const detailsRouteRef = createSubRouteRef({
  path: '/details/:id',
  parent: rootRouteRef,
});
export const externalDocsRouteRef = createExternalRouteRef({
  defaultTarget: 'techdocs.docRoot',
});

Key differences:

  • createRouteRef() no longer takes an id — the ID is derived from the extension
  • createSubRouteRef path must start with / and must not end with /
  • createExternalRouteRef() no longer takes an id or optional flag

Set Default Targets for External Route Refs

When migrating external route refs, always set defaultTarget to the most common binding target. This removes the need for apps to explicitly bind routes via bindRoutes for standard plugin combinations:

export const createComponentRouteRef = createExternalRouteRef({
  defaultTarget: 'scaffolder.root',
});

export const viewTechDocRouteRef = createExternalRouteRef({
  params: ['namespace', 'kind', 'name'],
  defaultTarget: 'techdocs.docRoot',
});

export const catalogEntityRouteRef = createExternalRouteRef({
  params: ['namespace', 'kind', 'name'],
  defaultTarget: 'catalog.catalogEntity',
});

The defaultTarget string uses the <pluginId>.<routeName> format, where routeName matches a key in the target plugin's routes map. The default is only activated when the target plugin is installed — otherwise the route stays unbound and useRouteRef returns undefined.

This is especially important for a full migration because in the old system, apps typically had explicit bindRoutes calls. With default targets, most of those bindings become unnecessary, improving the plug-and-play experience.

Step 2: Migrate the Plugin Definition

Replace src/plugin.ts with a createFrontendPlugin-based definition:

// NEW (src/plugin.tsx)
import { createFrontendPlugin } from '@backstage/frontend-plugin-api';
import { RiToolsLine } from '@remixicon/react';
import { rootRouteRef, externalDocsRouteRef } from './routes';
import { myPage } from './extensions';
import { myPluginApi } from './apis';

export default createFrontendPlugin({
  pluginId: 'my-plugin',
  title: 'My Plugin',
  icon: <RiToolsLine />,
  info: {
    packageJson: () => import('../package.json'),
  },
  routes: {
    root: rootRouteRef,
  },
  externalRoutes: {
    docs: externalDocsRouteRef,
  },
  extensions: [myPluginApi, myPage],
});

For the plugin icon, prefer using Remix Icons from @remixicon/react. If the plugin already has an existing MUI icon, it can be kept with fontSize="inherit" (e.g. <CategoryIcon fontSize="inherit" />), but for new icons Remix is the recommended choice.

Since this is the only entry point now, export it as default from src/index.ts or update package.json exports accordingly. If the plugin was previously consumed via its main entry point, you can make the main entry point export the new plugin:

{
  "exports": {
    ".": "./src/index.ts",
    "./package.json": "./package.json"
  }
}
// src/index.ts
export { default } from './plugin';
export { rootRouteRef } from './routes';

Step 3: Migrate API Factories to ApiBlueprint

// OLD
import {
  createApiFactory,
  discoveryApiRef,
  fetchApiRef,
} from '@backstage/core-plugin-api';

export const myApiFactory = createApiFactory({
  api: myPluginApiRef,
  deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
  factory: ({ discoveryApi, fetchApi }) =>
    new MyPluginClient({ discoveryApi, fetchApi }),
});

// NEW (src/apis.ts)
import {
  ApiBlueprint,
  discoveryApiRef,
  fetchApiRef,
} from '@backstage/frontend-plugin-api';
import { myPluginApiRef } from './api';

export const myPluginApi = ApiBlueprint.make({
  params: defineParams =>
    defineParams({
      api: myPluginApiRef,
      deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
      factory: ({ discoveryApi, fetchApi }) =>
        new MyPluginClient({ discoveryApi, fetchApi }),
    }),
});

Also update the API ref creation to the new builder pattern with explicit pluginId:

// OLD
import { createApiRef } from '@backstage/core-plugin-api';

export const myPluginApiRef = createApiRef<MyPluginApi>({
  id: 'plugin.my-plugin.client',
});

// NEW (recommended builder pattern with explicit pluginId)
import { createApiRef } from '@backstage/frontend-plugin-api';

export const myPluginApiRef = createApiRef<MyPluginApi>().with({
  id: 'plugin.my-plugin.client',
  pluginId: 'my-plugin',
});

The builder form (createApiRef<T>().with(...)) is preferred because ownership is explicit via pluginId rather than parsed from the ID string. The id must still be globally unique across the app — the pluginId is ownership metadata, not a namespace prefix.

API Ownership and Override Rules

The new system enforces API ownership — only the owning plugin (or a module targeting it) can provide or override a given API. Ownership is determined by:

  1. The explicit pluginId on the ApiRef (if set via the builder pattern)
  2. Falling back to inference from the ApiRef ID string:

- plugin.<pluginId>.* → owned by that plugin - core.* → owned by the app plugin

If app adopters want to replace your plugin's default API implementation, they must use a createFrontendModule with pluginId matching your plugin — they cannot override it from a different plugin or from a generic app module. This is a stricter model than the old system where any API could be overridden from the app's apis array.

Step 4: Migrate Pages to PageBlueprint

Simple Page (No Sub-Routes)

// src/extensions.tsx
import { PageBlueprint } from '@backstage/frontend-plugin-api';
import { rootRouteRef } from './routes';

export const myPage = PageBlueprint.make({
  params: {
    path: '/my-plugin',
    routeRef: rootRouteRef,
    loader: () => import('./components/MyPage').then(m => <m.MyPage />),
  },
});

The MyPage component should not include Page, Header, or PageWithHeader from @backstage/core-components. The framework's PageLayout renders PluginHeader automatically.

The title and icon params on PageBlueprint are only needed if they should differ from the plugin's own title and icon (set in createFrontendPlugin). If omitted, the plugin-level values are used.

Page with Header for Custom Actions

If your page needs a subtitle or action buttons below the framework header, use Header from @backstage/ui:

// src/components/MyPage/MyPage.tsx
import { Header } from '@backstage/ui';
import { Content } from '@backstage/core-components';

export function MyPage() {
  return (
    <>
      <Header
        title="Subtitle or description"
        customActions={
          <>
            <CreateButton title="Create" to="/my-plugin/create" />
            <SupportButton>Help text</SupportButton>
          </>
        }
      />
      <Content>
        <MyPageContent />
      </Content>
    </>
  );
}

Page Without Header

For pages that manage their own layout entirely (e.g. home page, dashboards), set noHeader: true:

export const myPage = PageBlueprint.make({
  params: {
    path: '/my-plugin',
    routeRef: rootRouteRef,
    noHeader: true,
    loader: () => import('./components/MyPage').then(m => <m.MyPage />),
  },
});

Step 5: Replace Internal Routing with Sub-Pages

Old frontend plugins often use React Router <Route> trees inside a router component to handle internal navigation. Before migrating, determine which routing pattern fits the plugin.

Decide Which Routing Pattern to Use

Not all internal routing maps to tabs. Read the plugin's existing router component and ask the user:

"Does your plugin use top-level tabs that users navigate between via a header (e.g. Overview / Settings)? Or does it use detail/drill-down routes (e.g. /my-plugin/items/:id)?"

Use SubPageBlueprint when:

  • The sub-routes represent top-level tabs/sections of the plugin
  • Users navigate between them via the header

Keep internal routing within a PageBlueprint loader when:

  • Routes are detail/drill-down pages (e.g. /my-plugin/items/:id)
  • The routing is deeply nested or dynamic

If the plugin uses drill-down routing only, use a PageBlueprint with a loader that handles its own <Routes> and skip the rest of this step:

export const myPage = PageBlueprint.make({
  params: {
    path: '/my-plugin',
    routeRef: rootRouteRef,
    loader: () => import('./components/Router').then(m => <m.MyPluginRouter />),
  },
});

If the plugin uses top-level tabs, continue with the SubPageBlueprint migration below.

Old Pattern: Internal Router

// OLD — plugin owns its own routing
import { Route, Routes } from 'react-router-dom';

export function MyPluginRouter() {
  return (
    <Page themeId="tool">
      <Header title="My Plugin" />
      <HeaderTabs
        tabs={[
          { id: 'overview', label: 'Overview' },
          { id: 'settings', label: 'Settings' },
        ]}
      />
      <Content>
        <Routes>
          <Route path="/" element={<OverviewPage />} />
          <Route path="/settings" element={<SettingsPage />} />
        </Routes>
      </Content>
    </Page>
  );
}

New Pattern: PageBlueprint + SubPageBlueprint

// src/extensions.tsx
import {
  PageBlueprint,
  SubPageBlueprint,
} from '@backstage/frontend-plugin-api';

// Parent page WITHOUT a loader — uses built-in tabbed rendering
export const myPluginPage = PageBlueprint.make({
  params: {
    path: '/my-plugin',
    routeRef: rootRouteRef,
  },
});

export const overviewSubPage = SubPageBlueprint.make({
  name: 'overview',
  params: {
    path: 'overview',
    title: 'Overview',
    loader: () =>
      import('./components/OverviewPage').then(m => <m.OverviewPageContent />),
  },
});

export const settingsSubPage = SubPageBlueprint.make({
  name: 'settings',
  params: {
    path: 'settings',
    title: 'Settings',
    loader: () =>
      import('./components/SettingsPage').then(m => <m.SettingsPageContent />),
  },
});

How this works:

  • PageBlueprint without a loader automatically renders its sub-pages as tabs
  • The first sub-page becomes the default (index redirect)
  • Each SubPageBlueprint gets a tab in the header with its title
  • Sub-page path values are relative (no leading /)
  • Sub-page components render content only — no Page, Header, or HeaderTabs

If the sub-page content needs padding, use Container from @backstage/ui as a wrapper inside the component.

Step 6: Update Hooks and Imports

Replace all @backstage/core-plugin-api imports with @backstage/frontend-plugin-api:

// OLD
import { useApi, useRouteRef, configApiRef } from '@backstage/core-plugin-api';

// NEW
import {
  useApi,
  useRouteRef,
  configApiRef,
} from '@backstage/frontend-plugin-api';

useRouteRef Behavior Change

In the new system, useRouteRef may return undefined for external route refs that aren't bound. Handle this:

// OLD — throws if not bound
const docsLink = useRouteRef(externalDocsRouteRef);
// Always a function

// NEW — returns undefined if not bound
const docsLink = useRouteRef(externalDocsRouteRef);
if (docsLink) {
  // render link
}

Common Import Mappings

Old Import (@backstage/core-plugin-api)New Import (@backstage/frontend-plugin-api)
createPlugincreateFrontendPlugin
createRouteRefcreateRouteRef
createSubRouteRefcreateSubRouteRef
createExternalRouteRefcreateExternalRouteRef
createApiRefcreateApiRef
createApiFactoryApiBlueprint.make
useApiuseApi
useRouteRefuseRouteRef
configApiRefconfigApiRef
discoveryApiRefdiscoveryApiRef
fetchApiReffetchApiRef
identityApiRefidentityApiRef
storageApiRefstorageApiRef
analyticsApiRefanalyticsApiRef
createRoutableExtensionPageBlueprint.make
createComponentExtensionDepends on context — blueprint or createExtension

Step 7: Remove Old System Code

  1. Delete src/plugin.ts (old createPlugin)
  2. Delete any createRoutableExtension / createComponentExtension usage
  3. Remove Page, Header, PageWithHeader wrapping from page components
  4. Remove HeaderTabs if replaced by SubPageBlueprint tabs
  5. Remove internal <Routes>/<Route> trees if replaced by sub-pages
  6. Remove @backstage/core-plugin-api from package.json dependencies
  7. Remove @backstage/core-compat-api from package.json dependencies if present

Step 8: Update Page Components for BUI

With the full migration, page components should use @backstage/ui components and patterns. See the mui-to-bui-migration skill for detailed component migration guidance.

Key page-level changes:

  • Replace PageWithHeader / Page + Header with framework-provided PluginHeader (automatic via PageLayout)
  • Use Header from @backstage/ui for optional subtitle/custom actions
  • Use Content from @backstage/core-components for page body padding (this is still used even in NFS pages)
  • Replace ContentHeader with Header's customActions prop
  • Replace HeaderTabs with SubPageBlueprint (tabs are rendered by the framework)

Real Example: Auth Plugin (Fully Migrated)

The @backstage/plugin-auth plugin is a fully migrated example with no @backstage/core-plugin-api dependency:

// plugins/auth/src/routes.ts
import { createRouteRef } from '@backstage/frontend-plugin-api';

export const rootRouteRef = createRouteRef();

// plugins/auth/src/plugin.tsx
import {
  createFrontendPlugin,
  PageBlueprint,
} from '@backstage/frontend-plugin-api';
import { rootRouteRef } from './routes';

export const AuthPage = PageBlueprint.make({
  params: {
    path: '/oauth2',
    routeRef: rootRouteRef,
    loader: () => import('./components/Router').then(m => <m.Router />),
  },
});

export default createFrontendPlugin({
  pluginId: 'auth',
  extensions: [AuthPage],
  routes: {
    root: rootRouteRef,
  },
});

Real Example: Scaffolder Sub-Pages

The scaffolder plugin demonstrates the sub-page pattern (though it still has dual support — the pattern itself is what a full migration targets):

// PageBlueprint WITHOUT loader — framework renders tabs
export const scaffolderPage = PageBlueprint.make({
  params: {
    path: '/create',
    routeRef: rootRouteRef,
  },
});

// Sub-pages with content only
export const templatesSubPage = SubPageBlueprint.make({
  name: 'templates',
  params: {
    path: 'templates',
    title: 'Templates',
    loader: () => import('./TemplatesPage').then(m => <m.TemplatesSubPage />),
  },
});

export const tasksSubPage = SubPageBlueprint.make({
  name: 'tasks',
  params: {
    path: 'tasks',
    title: 'Tasks',
    loader: () => import('./TasksPage').then(m => <m.TasksSubPage />),
  },
});

Migration Checklist

  1. Migrate route refs to @backstage/frontend-plugin-api (createRouteRef, createSubRouteRef, createExternalRouteRef)
  2. Replace createPlugin with createFrontendPlugin
  3. Convert all API factories to ApiBlueprint extensions
  4. Convert pages to PageBlueprint
  5. Replace internal tab routing with SubPageBlueprint where appropriate
  6. Remove Page/Header/PageWithHeader from page components
  7. Add Header from @backstage/ui where subtitle/custom actions are needed
  8. Replace HeaderTabs with SubPageBlueprint tabs
  9. Update all @backstage/core-plugin-api imports to @backstage/frontend-plugin-api
  10. Handle useRouteRef possibly returning undefined
  11. Remove src/plugin.ts (old system entry point)
  12. Remove src/alpha.tsx if it existed (merge into main entry)
  13. Remove @backstage/core-plugin-api from package.json dependencies
  14. Remove @backstage/core-compat-api from package.json dependencies
  15. Update package.json exports (remove ./alpha if merged into main)
  16. Run yarn tsc to check for type errors
  17. Run yarn lint to check for missing dependencies
  18. Run yarn build:api-reports to update API reports (if the project uses API reports)
  19. Test in a new-system app (packages/app)

Reference

平台分布

Local Agent

77.09%
按下载量换算433

安全审计

暂无安全审计结果可展示。

权限和风险

执行命令

安装流程涉及命令执行,可能通过 第三方 CLI 联网下载 Skill 或依赖。用户安装前应确认命令来源、仓库内容和执行环境。

安装前确认

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

继续浏览同类 Skills