3. 插件开发基础:Scaffolder模板、插件目录结构、package.json配置
在Backstage中,插件是功能扩展的基本单元。本章聚焦于插件开发的核心基础:Scaffolder模板(快速生成插件骨架)、插件目录结构(约定优于配置)以及package.json配置(元数据与依赖管理)。掌握这三部分,是构建隔离良好、可维护的插件的第一步。
3.1 使用Scaffolder模板创建插件
Backstage内置了强大的Scaffolder(脚手架)系统,用于快速生成标准化的插件项目结构。推荐使用npx @backstage/create-app或yarn create backstage-app后,通过以下命令创建插件:
# 在Backstage根目录下执行
yarn new --select plugin
# 或使用backstage-cli
yarn backstage-cli new plugin my-plugin-name
执行后,Scaffolder会交互式询问插件名称、是否包含后端、是否包含React组件等选项。生成的插件骨架遵循官方最佳实践,自动配置了TypeScript、ESLint、Jest等工具链。
最佳实践:始终使用Scaffolder创建插件,而非手动复制。这能确保目录结构、配置文件版本与当前Backstage版本兼容,减少因结构差异导致的隔离问题。
3.2 插件目录结构详解
一个标准的Backstage插件(以my-plugin为例)通常包含以下目录与文件:
| 路径 | 用途 | 是否必需 |
|---|---|---|
plugins/my-plugin/ |
插件根目录 | 是 |
plugins/my-plugin/src/ |
前端React源码(组件、路由、API) | 是 |
plugins/my-plugin/src/index.ts |
插件入口,导出插件实例与组件 | 是 |
plugins/my-plugin/src/plugin.ts |
定义createPlugin实例,注册路由、API、外部路由 |
是 |
plugins/my-plugin/src/components/ |
React组件目录 | 推荐 |
plugins/my-plugin/src/routes.ts |
定义插件内部路由路径常量 | 推荐 |
plugins/my-plugin/src/types.ts |
TypeScript类型定义 | 按需 |
plugins/my-plugin/dev/ |
开发环境入口,用于独立运行插件 | 推荐 |
plugins/my-plugin/dev/index.ts |
开发模式下的App实例,包含Mock数据 | 推荐 |
plugins/my-plugin/package.json |
插件元数据、依赖、脚本 | 是 |
plugins/my-plugin/tsconfig.json |
TypeScript编译配置 | 是 |
plugins/my-plugin/README.md |
插件说明文档 | 推荐 |
隔离视角:每个插件拥有独立的
src/、package.json和tsconfig.json,这从物理上实现了代码隔离。插件之间通过Backstage的API系统和外部路由进行通信,而非直接引用对方源码,这是故障域隔离的基础。
3.3 package.json配置要点
插件的package.json是Backstage识别和加载插件的核心配置文件。除了标准的npm字段外,还包含Backstage特有的元数据。
3.3.1 核心字段说明
| 字段 | 说明 | 示例值 |
|---|---|---|
name |
插件包名,遵循@backstage/plugin-*或@internal/plugin-*命名规范 |
@backstage/plugin-scaffolder |
version |
语义化版本号 | 1.0.0 |
main |
前端入口文件,指向编译后的JS文件 | dist/index.esm.js |
types |
TypeScript类型声明入口 | dist/index.d.ts |
backstage.role |
Backstage特有,声明插件角色(frontend-plugin, backend-plugin, common-library等) | frontend-plugin |
backstage.pluginId |
Backstage特有,插件唯一标识符,用于路由和API注册 | my-plugin |
keywords |
推荐包含backstage和plugin,便于搜索 |
["backstage", "plugin"] |
3.3.2 典型package.json示例
{
"name": "@internal/plugin-capacity-planner",
"version": "0.1.0",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts",
"license": "Apache-2.0",
"backstage": {
"role": "frontend-plugin",
"pluginId": "capacity-planner",
"pluginPackages": [
"@internal/plugin-capacity-planner"
]
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean"
},
"dependencies": {
"@backstage/core-plugin-api": "^1.9.0",
"@backstage/plugin-catalog-react": "^1.12.0",
"@material-ui/core": "^4.12.2",
"react": "^17.0.2",
"react-router-dom": "^6.3.0"
},
"devDependencies": {
"@backstage/cli": "^0.26.0",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^12.1.0"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0"
}
}
隔离注意事项:
- 依赖隔离:避免在
dependencies中引入与Backstage核心库版本冲突的包。使用peerDependencies声明对React等共享库的版本要求。 - 角色声明:
backstage.role字段决定了插件在构建和运行时如何被处理。前端插件(frontend-plugin)和后端插件(backend-plugin)的构建目标不同,错误声明会导致加载失败。 - 插件ID唯一性:
backstage.pluginId在整个Backstage实例中必须唯一,否则会导致路由冲突和API覆盖,破坏故障域隔离。
3.4 插件入口与导出规范
每个插件必须通过src/index.ts导出核心内容,Backstage的App Builder通过扫描这些导出加载插件。
3.4.1 标准导出结构
// plugins/my-plugin/src/index.ts
export { myPluginPlugin } from './plugin';
export { MyPluginPage } from './components/MyPluginPage';
export { MyPluginCard } from './components/MyPluginCard';
export type { MyPluginApi } from './types';
3.4.2 plugin.ts 核心定义
// plugins/my-plugin/src/plugin.ts
import { createPlugin, createRoutableExtension } from '@backstage/core-plugin-api';
import { rootRouteRef } from './routes';
export const myPluginPlugin = createPlugin({
id: 'my-plugin',
routes: {
root: rootRouteRef,
},
});
export const MyPluginPage = myPluginPlugin.provide(
createRoutableExtension({
name: 'MyPluginPage',
component: () => import('./components/MyPluginPage').then(m => m.MyPluginPage),
mountPoint: rootRouteRef,
}),
);
故障域隔离设计:插件通过
createPlugin定义独立的路由命名空间和API注册表。即使两个插件定义了相同的路由路径(如/dashboard),由于pluginId不同,Backstage会自动添加前缀(如/my-plugin/dashboard),避免路由冲突。这种机制确保了每个插件的故障域在路由层面是隔离的。
3.5 开发环境与调试
插件开发时,建议使用dev/index.ts创建独立的开发入口,避免影响主应用:
// plugins/my-plugin/dev/index.ts
import { createDevApp } from '@backstage/dev-utils';
import { myPluginPlugin, MyPluginPage } from '../src';
createDevApp()
.registerPlugin(myPluginPlugin)
.addPage({
path: '/my-plugin',
title: 'My Plugin',
element: <MyPluginPage />,
})
.render();
运行yarn start后,可在独立端口(通常为:3000)预览插件,与主应用完全隔离,便于快速迭代和故障排查。
开发效率提示:利用Backstage的模块热替换(HMR)机制,修改插件源码后无需重启整个Backstage实例。这在大规模插件开发中显著提升效率,同时保持故障域的独立性。