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

프론트엔드 확장 블루프린트

원문 보기 위키 갱신

프론트엔드 확장 블루프린트 (Frontend Extension Blueprints)

createExtension 함수와 관련 API는 저수준이며 상당히 고급스러운 빌딩 블록으로 간주되며, 플러그인과 기능을 구축할 때 일반적으로 사용하는 것은 아니에요. 대신 핵심 API와 플러그인이 특정 용도에 맞는 확장을 더 쉽게 만들 수 있게 해주는 확장 블루프린트를 제공해요. 이 블루프린트는 각 블루프린트가 정의하기 나름인 여러 매개변수를 받고, 제공된 매개변수를 사용해 새 확장을 만들어요. 새 블루프린트는 createExtensionBlueprint 함수를 사용해 만들며, 관례적으로 <Kind>Blueprint 심볼로 export돼요. 플러그인이나 패키지에서 어떤 블루프린트를 사용할 수 있는지 궁금하다면 패키지의 API에서 *Blueprint export를 찾아보세요. 플러그인의 경우 일반적으로 *-react 패키지에서 찾을 수 있어요.

출처: 문서

본문

createExtension 함수와 관련 API는 저수준이며 상당히 고급스러운 빌딩 블록으로 간주되며, 플러그인과 기능을 구축할 때 일반적으로 사용하는 것은 아니에요. 대신 핵심 API와 플러그인이 특정 용도에 맞는 확장을 더 쉽게 만들 수 있게 해주는 확장 블루프린트를 제공해요. 이 블루프린트는 각 블루프린트가 정의하기 나름인 여러 매개변수를 받고, 제공된 매개변수를 사용해 새 확장을 만들어요. 새 블루프린트는 createExtensionBlueprint 함수를 사용해 만들며, 관례적으로 <Kind>Blueprint 심볼로 export돼요. 플러그인이나 패키지에서 어떤 블루프린트를 사용할 수 있는지 궁금하다면 패키지의 API에서 *Blueprint export를 찾아보세요. 플러그인의 경우 일반적으로 *-react 패키지에서 찾을 수 있어요.

블루프린트에서 확장 만들기

모든 확장 블루프린트는 새 확장을 만드는 데 사용할 수 있는 make 메서드를 제공해요. 기본 블루프린트가 필요한 모든 기능을 제공하는 새 확장을 만드는 간단한 방법이에요. 필요한 블루프린트 매개변수를 제공하기만 하면 되지만, name, attachTo, disabled 또는 확장용 if predicate 같은 추가 옵션을 제공하는 기능도 있어요.

다음은 블루프린트 make 메서드를 사용해 새 확장을 만드는 간단한 예시예요.

const myPageExtension = PageBlueprint.make({  params: {    path: '/my-page',    loader: () => import('./components/MyPage').then(m => <m.MyPage />),  },});

반환된 myPageExtension은 플러그인에서 사용할 준비가 된 확장이에요. 저수준 createExtension 함수가 반환하는 것과 같은 유형의 객체예요.

재정의가 있는 블루프린트에서 확장 만들기

모든 확장 블루프린트는 makeWithOverrides 메서드도 제공해요. 이는 블루프린트로 만든 확장에 추가 통합 지점을 제공하고 싶을 때 유용해요. 예를 들어 추가 입력이나 구성 스키마를 정의하거나, 기존 구성을 사용해 블루프린트에 전달될 매개변수를 동적으로 계산하고 싶을 수 있어요.

다음은 블루프린트 makeWithOverrides 메서드를 사용해 새 확장을 만드는 예시예요.

import { z } from 'zod';const myPageExtension = PageBlueprint.makeWithOverrides({  // This defines additional configuration options for the extension.  configSchema: {    layout: z.enum(['grid', 'rows']).default('grid'),  },  // This defines additional inputs for the extension.  inputs: {    content: createExtensionInput([coreExtensionData.reactElement], {      singleton: true,      optional: true,    }),  },  // The original blueprint factory is provided as the first argument.  // By convention the name is `originalFactory`, but you can also pick a different name.  factory(originalFactory, { config, inputs }) {    // Call and forward the result from the original factory, providing    // the blueprint parameters as the first argument.    return originalFactory({      path: '/my-page',      loader: () =>        import('./components/MyPage').then(m => (          // We can now access values from the factory context when providing          // the blueprint parameters, such as config values and inputs.          <m.MyPage            layout={config.layout}            content={inputs.content?.get(coreExtensionData.reactElement)}          />        )),    });  },});

makeWithOverrides를 사용할 때는 블루프린트 매개변수를 직접 전달하지 않아요. 대신 첫 번째 인자로 원본 블루프린트 factory를 받고 두 번째로 확장 factory 컨텍스트를 받는 factory 함수를 제공해요. 그런 다음 블루프린트 매개변수와 함께 원본 블루프린트 factory를 호출하고 그 결과를 우리 factory의 반환 값으로 전달할 수 있어요. 이 패턴으로 블루프린트 매개변수를 전달할 때 make 메서드를 사용할 때보다 훨씬 더 많은 정보에 접근할 수 있지만, 더 복잡하다는 대가가 있어요.

원본 factory 함수의 첫 번째 인자에 블루프린트 매개변수가 추가된 것 외에 makeWithOverrides 메서드는 확장 재정의(extension overrides)와 동일한 방식으로 작동해요. 추가 입력 정의, 출력 재정의 등 동일한 옵션과 규칙이 모두 적용돼요. 이것이 어떻게 작동하는지에 대한 자세한 내용과 예시는 확장 재정의 문서를 참고하세요. 해당 섹션의 패턴은 makeWithOverrides 메서드로 확장을 만드는 것에도 적용돼요.

고급 매개변수 유형이 있는 블루프린트에서 확장 만들기

일부 블루프린트는 "고급 매개변수 유형(advanced parameter types)"이라고 하는 것으로 정의될 수 있어요. 이는 블루프린트 매개변수의 유형 추론과 변환을 가능하게 하는 기능이며, 매개변수를 전달하는 방식이 조금 다르게 보여요. 매개변수를 직접 전달하는 대신 defineParams => defineParams(<params>) 형태의 콜백 함수로 전달돼요.

고급 매개변수 유형을 사용하는 블루프린트의 예시는 ApiBlueprint 블루프린트예요. 이를 사용해 AlertApi용 간단한 구현을 만들면 다음과 같을 수 있어요.

const alertApiBlueprint = ApiBlueprint.make({  params: defineParams =>    defineParams({      api: alertApiRef,      deps: {},      factory: () => new MyAlertApi(),    }),});

이것은 makeWithOverrides에서도 작동하며, 원본 factory의 첫 번째 인자로 define 콜백이 전달돼요.

const alertApiBlueprint = ApiBlueprint.makeWithOverrides({  factory(originalFactory, { config }) {    return originalFactory(defineParams =>      defineParams({        api: alertApiRef,        deps: {},        factory: () => new MyAlertApi(config),      }),    );  },});

확장 블루프린트 만들기

새 확장 블루프린트를 만들려면 createExtensionBlueprint 함수를 사용해요. 표면적으로는 createExtension과 매우 유사하지만 몇 가지 핵심 차이점이 있어요. 첫째, kind 옵션을 제공해야 하며, 이는 블루프린트로 만든 모든 확장의 종류(kind)가 돼요. 좋은 확장 kind를 고르는 방법에 대한 자세한 내용은 명명 패턴 섹션을 참고하세요. 둘째, factory 함수는 첫 번째 매개변수가 블루프린트 매개변수이고 두 번째가 factory 컨텍스트인 새 시그니처를 가져요. 마지막으로 createExtensionBlueprint는 확장을 반환하는 대신 위에서 설명한 것처럼 make 메서드 등을 가진 블루프린트 객체를 반환해요.

다음은 새 확장 블루프린트를 만드는 예시예요.

export interface MyWidgetBlueprintParams {  title: string;  element: JSX.Element;}export const MyWidgetBlueprint = createExtensionBlueprint({  kind: 'my-widget',  attachTo: { id: 'page:my-plugin', input: 'widgets' },  configSchema: {    title: z.string().optional(),  },  output: [coreExtensionData.reactElement],  factory(params: MyWidgetBlueprintParams, { config }) {    return [      // Note that while this is a valid pattern, you might often want to      // return separate pieces of data instead, more on that below.      coreExtensionData.reactElement(        <MyWidgetContainer title={config.title ?? params.title}>          {params.element}        </MyWidgetContainer>,      ),    ];  },});

물론 이것은 꽤 기본적인 예시 블루프린트이지만 여전히 매우 실제적인 예시예요. 블루프린트는 매우 간단할 수 있어요. 확장 kind, 부착 지점과 출력을 블루프린트에서 캡슐화하는 것만으로도 이미 많은 가치가 있어요.

createExtensionBlueprint에 제공된 대부분의 옵션은 makeWithOverrides를 사용해 블루프린트에서 확장을 만들 때 재정의할 수 있어요. 이 재정의는 확장 재정의와 동일한 방식으로 작동하며, 재정의가 어떻게 작동하는지에 대한 자세한 내용은 해당 문서를 참고하세요.

고급 매개변수 유형으로 확장 블루프린트 만들기

경우에 따라 블루프린트 매개변수 정의에 추론된 유형 매개변수를 사용하고 싶을 수 있어요. 이를 위해 "고급 매개변수 유형"이라고 하는 것을 사용해야 해요. 이는 블루프린트 매개변수의 유형 추론과 변환을 가능하게 하는 기능이며, 매개변수 유형을 정의하는 방식이 조금 달라요. 매개변수 유형을 factory 함수의 일부로 정의하는 대신 별도의 defineParams 옵션을 제공해요. 이는 매개변수를 단일 인자로 받고, createExtensionBlueprintParams 함수로 감싼 매개변수를 반환해야 하는 함수예요.

다음은 매개변수가 추론된 유형을 사용하는 블루프린트를 정의하는 예시예요.

export interface MyWidgetBlueprintParams<T> {  defaultOptions: T;  elementFactory(options: T): JSX.Element;}export const MyWidgetBlueprint = createExtensionBlueprint({  kind: 'my-widget',  attachTo: { id: 'page:my-plugin', input: 'widgets' },  output: [coreExtensionData.reactElement],  defineParams<T>(params: MyWidgetBlueprintParams<T>) {    return createExtensionBlueprintParams(params);  },  // Note that we no longer define the parameters type here, they are inferred from the defineParams function  factory(params) {    return [      coreExtensionData.reactElement(        <MyWidgetRenderer          defaultOptions={params.defaultOptions}          elementFactory={params.elementFactory}        />,      ),    ];  },});

"왜 factory 함수에 유형 매개변수를 그냥 정의할 수 없지?"라고 궁금해한다면, 이는 TypeScript 유형 시스템의 한계예요. 기술적으로는 블루프린트 정의에서 지원할 수 있지만, 그 로직을 블루프린트의 .make와 .makeWithOverrides 메서드로 이어 나갈 방법이 없어요.

블루프린트별 확장 데이터 참조

경우에 따라 블루프린트에 특정한 확장 데이터 참조를 정의하고 제공하고 싶을 수 있어요. 위 예시에서 title을 MyWidgetContainer 컴포넌트에 캡슐화하는 대신 데이터로 전달하고 싶을 수 있죠. 이렇게 하면 부모 확장이 예시 위젯 확장을 렌더링할 때 더 많은 유연성을 갖게 돼요.

그렇게 하려면 위젯 제목용 새 확장 데이터 참조를 만들어요. 이 참조는 블루프린트를 만들 때 dataRefs 옵션으로 제공되며, MyWidgetBlueprint.dataRefs.widgetTitle을 통해 사용할 수 있게 돼요.

export interface MyWidgetBlueprintParams {  title: string;  element: JSX.Element;}const widgetTitleRef = createExtensionDataRef<string>().with({  id: 'my-plugin.widget.title',});export const MyWidgetBlueprint = createExtensionBlueprint({  kind: 'my-widget',  attachTo: { id: 'page:my-plugin', input: 'widgets' },  configSchema: {    title: z.string().optional(),  },  output: [widgetTitleRef, coreExtensionData.reactElement],  factory(params: MyWidgetBlueprintParams, { config }) {    return [      widgetTitleRef(config.title ?? params.title),      coreExtensionData.reactElement(params.element),    ];  },  dataRefs: {    widgetTitle: widgetTitleRef,  },});

라이브러리의 확장 블루프린트

플러그인을 게시한다면, 확장 작성자(extension creators)는 항상 플러그인 패키지가 아닌 프론트엔드 라이브러리 패키지(예: *-react)에서 export되어야 해요.

더 알아보기 (Learn more)