본문 바로가기
WIKI 기술 지식 베이스

프론트엔드 플러그인

원문 보기 위키 갱신

프론트엔드 플러그인 (Frontend Plugins)

프론트엔드 플러그인은 Backstage와 프론트엔드 시스템의 기초적인 빌딩 블록이에요. 이들은 Backstage 앱에 새 페이지, 탐색 요소, API 같은 기능을 캡슐화하고 제공하는 데 사용되며, Software Catalog용 엔터티 페이지 카드와 콘텐츠, 검색 플러그인용 결과 목록 항목처럼 다른 플러그인의 확장과 기능도 제공해요.

출처: 문서

본문

소개 (Introduction)

프론트엔드 플러그인은 Backstage와 프론트엔드 시스템의 기초적인 빌딩 블록이에요. 이들은 Backstage 앱에 새 페이지, 탐색 요소, API 같은 기능을 캡슐화하고 제공하는 데 사용되며, Software Catalog용 엔터티 페이지 카드와 콘텐츠, 검색 플러그인용 결과 목록 항목처럼 다른 플러그인의 확장과 기능도 제공해요.

각 플러그인은 일반적으로 별도의 NPM 패키지로 제공돼요. 게시된 패키지든 로컬 워크스페이스에만 있는 것이든 말이죠. 플러그인 인스턴스는 항상 패키지의 default export여야 하며, 메인 진입점이나 /alpha 하위 경로 export를 통해 내보내져요. 각 플러그인 패키지는 단일 플러그인 인스턴스만 export하는 것으로 제한돼요. 로컬 워크스페이스에서는 원하는 다른 구조를 사용할 수 있지만, 이는 비표준 레이아웃으로 간주되며 게시된 패키지에서는 피해야 해요.

플러그인 만들기 (Creating a Plugin)

프론트엔드 플러그인 인스턴스는 @backstage/frontend-plugin-api 패키지가 제공하는 createFrontendPlugin 함수로 만들어요. 이 함수는 플러그인에 필요한 모든 구성을 제공하는 단일 옵션 객체를 받아요. 특히 플러그인에 확장을 제공하고 싶을 거예요. 그것이 앱에 새 기능을 제공하는 방법이기 때문이에요.

// This creates a new extension, see "Extension Blueprints" documentation for more detailsconst myPage = PageBlueprint.make({  params: {    path: '/my-page',    loader: () => import('./MyPage').then(m => <m.MyPage />),  },});export default createFrontendPlugin({  pluginId: 'my-plugin',  title: 'My Plugin',  icon: MyPluginIcon,  extensions: [myPage],});

pluginId 옵션

각 플러그인은 전체 Backstage 시스템 내에서 플러그인을 고유하게 식별하는 데 사용되는 ID가 필요해요. ID가 전체 NPM 생태계에서 전역적으로 고유할 필요는 없지만, 일반적으로 그렇게 하기 위해 노력해야 해요. 단일 Backstage 앱에는 같은 ID의 여러 플러그인을 설치할 수 없어요.

플러그인 ID는 일반적으로 패키지 이름의 일부여야 하며 kebab-case를 사용해야 해요. 프론트엔드 명명 패턴 섹션과 패키지 메타데이터 섹션을 모두 참고하세요.

title 옵션

플러그인의 표시 제목으로, 페이지 헤더와 탐색에 사용돼요. 제공되지 않으면 플러그인 ID로 대체돼요.

export default createFrontendPlugin({  pluginId: 'my-plugin',  title: 'My Plugin',  extensions: [...],});

icon 옵션

플러그인의 표시 아이콘으로, 페이지 헤더와 탐색에 사용돼요. 유형은 @backstage/frontend-plugin-api의 IconElement(JSX.Element | null)예요. 아이콘은 정확히 24x24 픽셀이어야 해요.

export default createFrontendPlugin({  pluginId: 'my-plugin',  icon: <MyPluginIcon />,  extensions: [...],});

extensions 옵션

이것들은 플러그인이 앱에 제공하는 확장이에요. 이 확장들은 플러그인 인스턴스의 getExtension 메서드로 확장 ID를 사용해 이미 접근할 수 있으므로, 플러그인 패키지에서 별도로 export해서는 안 된다는 점에 유의하세요.

플러그인에 제공하는 확장은 기본적으로 namespace가 플러그인 ID로 설정돼요. 예를 들어 특정 명명 옵션 없이 PageBlueprint로 확장을 만들고 ID가 my-plugin인 플러그인을 통해 설치하면, 최종 확장 ID는 page:my-plugin이 돼요. 이것이 어떻게 작동하는지에 대한 자세한 내용은 확장 구조 문서에서 읽을 수 있어요.

routes와 externalRoutes 옵션

이것들은 플러그인이 앱에 노출하는 경로예요. routes 옵션은 플러그인이 제공하는 모든 대상 경로, 즉 다른 플러그인이 연결하는 경로를 선언해요. externalRoutes 옵션은 대신 플러그인이 연결하는 모든 발신(outgoing) 경로를 선언하며, 다른 플러그인의 routes에 바인딩할 수 있어요. 크로스 플러그인 탐색을 설정하는 방법에 대한 자세한 내용은 경로 문서를 참고하세요.

featureFlags 옵션

이것은 플러그인이 앱에 제공하는 기능 플래그 선언 목록이에요. 이렇게 하면 기능 플래그가 올바르게 등록되고 앱에서 토글할 수 있어요. 기능 플래그를 읽으려면 featureFlagsApiRef로 접근할 수 있는 기능 플래그 Utility API를 사용할 수 있어요.

if 옵션

if 옵션을 사용하면 플러그인 인스턴스가 제공하는 모든 확장에 공유 조건을 적용할 수 있어요. 전체 플러그인을 기능 플래그나 권한 뒤에 두고 싶을 때 유용하며, 같은 predicate를 모든 개별 확장에 반복할 필요가 없어요.

export default createFrontendPlugin({  pluginId: 'my-plugin',  if: { featureFlags: { $contains: 'my-plugin-enabled' } },  extensions: [...],});

이 predicate는 그 플러그인 인스턴스의 모든 확장에 적용돼요. 확장에 이미 자체 if predicate가 있으면 둘은 논리적 AND로 결합돼요.

info 옵션

이 옵션은 사용자와 관리자에게 유용할 수 있는 플러그인 정보 소스용 로더를 제공하는 데 사용돼요. 사용 가능한 두 로더는 packageJson과 manifest이며, 플러그인은 필요에 따라 둘 중 하나나 둘 다 사용할 수 있어요. 결과 정보는 앱에 설치된 후 플러그인 인스턴스의 info() 메서드로 사용할 수 있지만, 제공된 소스에서 정보를 어떻게 파생할지는 각 앱이 결정할 몫이에요.

info.packageJson 로더는 자체 패키지 내에서 구현된 모든 플러그인이 반드시 사용해야 하며, 플러그인 패키지의 package.json 파일을 로드해야 해요. 일반적인 사용은 다음과 같아요.

export default createFrontendPlugin({  pluginId: 'my-plugin',  info: {    packageJson: () => import('../package.json'),  },  extensions: [...],});

info.manifest 로더는 불투명한 플러그인 매니페스트를 가리키는 데 사용돼요. 이는 단일 조직 내에서 사용하도록 의도된 플러그인만 반드시 사용해야 해요. 공개 패키지 레지스트리에 게시된 플러그인은 이 로더를 사용해서는 안 돼요. 이 로더는 플러그인과 연결된 추가 내부 메타데이터를 추가하는 데 유용하며, 이러한 매니페스트를 어떻게 파싱하고 사용할지는 Backstage 앱이 결정할 몫이에요. @backstage/frontend-defaults의 createApp으로 만든 앱의 기본 매니페스트 파서는 기본 catalog-info.yaml 형식과 metadata.links, spec.owner 같은 내장 필드를 파싱할 수 있어요.

일반적인 사용은 다음과 같아요.

export default createFrontendPlugin({  pluginId: '...',  info: {    manifest: () => import('../catalog-info.yaml'),  },});

앱에 플러그인 설치하기 (Installing a Plugin in an App)

플러그인 인스턴스는 프론트엔드 기능으로 간주되며 어떤 Backstage 프론트엔드 앱에도 직접 설치할 수 있어요. 앱에서 새 기능을 설치할 수 있는 다양한 방법에 대한 자세한 내용은 앱 문서를 참고하세요.

플러그인 재정의하기 (Overriding a Plugin)

플러그인이 항상 원하는 대로 정확히 동작하지 않을 수 있어요. 특정 확장을 제거하거나, 조금 장식하거나, 자체 것으로 교체하거나, 단순히 새 것을 추가하고 싶을 수 있어요. 정확한 사용 사례와 관계없이 plugin.withOverrides 메서드를 사용해 원하는 변경 사항이 포함된 플러그인의 새 사본을 만들 수 있어요. 그렇게 할 때 플러그인이 제공하는 원본 확장에 접근하고, 확장 재정의 API를 사용해 개별 확장을 변경할 수도 있어요.

import plugin from '@backstage/plugin-catalog';import { PageBlueprint } from '@backstage/frontend-plugin-api';import { RiLayoutGridLine } from '@remixicon/react';export default plugin.withOverrides({  // These overrides are merged with the original extensions  extensions: [    // Override the catalog index page with a custom icon and implementation    plugin.getExtension('page:catalog').override({      factory: origFactory =>        origFactory({          icon: <RiLayoutGridLine />,          loader: () =>            import('./CustomCatalogIndexPage').then(m => <m.Page />),        }),    }),  ],});

플러그인 재정의를 앱 패키지에 둘 수 있지만, 특히 재정의가 복잡하거나 재정의에 대한 명확한 소유권을 원한다면 별도 패키지로 분리하는 것이 좋은 생각일 수 있어요. 예를 들어 @backstage/plugin-catalog 플러그인을 재정의한다면, 워크스페이스의 plugins/catalog에 @internal/plugin-catalog이라는 새 패키지를 만들어 재정의된 플러그인 인스턴스를 export할 수 있어요.

더 알아보기 (Learn more)