네이티브 플러그인 배포

이 페이지

네이티브 플러그인은 호스트 프로젝트에 설치되고 astro.config.mjs에 등록되는 npm 패키지입니다. 패키지에는 디스크립터와 createPlugin()을 제공하는 빌드된 서버 엔트리가 필요합니다. React 또는 Astro 컴포넌트도 함께 제공한다면, 호스트가 올바른 환경에 맞게 컴파일할 수 있도록 이를 별도의 소스 엔트리포인트로 내보내세요.

패키지 레이아웃

다음 레이아웃은 서버 런타임을 브라우저 및 Astro 소스와 분리합니다.

plugin-activity/
├── src/
│   ├── index.ts
│   ├── admin/
│   │   ├── index.tsx
│   │   └── ActivityPage.tsx
│   └── astro/
│       ├── index.ts
│       └── ActivityBlock.astro
├── dist/
│   ├── index.mjs
│   └── index.d.mts
├── package.json
├── tsconfig.json
└── README.md

dist/는 생성되는 디렉터리입니다. 호스트의 Vite와 Astro 빌드가 이 엔트리포인트를 처리해야 하므로, 게시하는 tarball에는 src/admin/과 src/astro/를 그대로 포함하세요.

패키지 내보내기

다음 package.json은 서버 엔트리를 빌드하고 세 가지 엔트리포인트를 모두 게시합니다.

{
	"name": "@example/plugin-activity",
	"version": "0.1.0",
	"type": "module",
	"main": "./dist/index.mjs",
	"exports": {
		".": {
			"types": "./dist/index.d.mts",
			"import": "./dist/index.mjs"
		},
		"./admin": "./src/admin/index.tsx",
		"./astro": "./src/astro/index.ts"
	},
	"files": ["dist", "src/admin", "src/astro"],
	"scripts": {
		"build": "tsdown src/index.ts --format esm --dts --clean",
		"dev": "tsdown src/index.ts --format esm --dts --watch",
		"typecheck": "tsc --noEmit",
		"prepublishOnly": "pnpm typecheck && pnpm build"
	},
	"peerDependencies": {
		"@cloudflare/kumo": "*",
		"@emdash-cms/admin": "*",
		"@lingui/core": "*",
		"@lingui/react": "*",
		"@tanstack/react-query": "*",
		"astro": ">=6.0.0-beta.0",
		"emdash": "*",
		"react": "^18.0.0 || ^19.0.0"
	},
	"devDependencies": {
		"@types/react": "^19.0.0",
		"tsdown": "^0.20.0",
		"typescript": "^5.9.0"
	},
	"keywords": ["emdash", "emdash-plugin"],
	"license": "MIT"
}

플러그인에 신뢰된 React UI가 없다면 ./admin, src/admin, 그리고 관리자 화면 전용 peer 의존성을 제거하세요. Portable Text 렌더러가 없다면 ./astro, src/astro, 그리고 astro peer를 제거하세요. 내보내는 소스 엔트리포인트가 가져오는 호스트 소유 라이브러리마다 peer 의존성을 추가하세요. 그래야 관리자 번들에 React, Kumo, Lingui, React Query 인스턴스가 두 번째로 섞여 들어가는 것을 막을 수 있습니다.

엔트리포인트마다 사용하는 쪽이 다릅니다.

내보내기필요한 경우사용하는 쪽
.항상Astro 구성이 디스크립터 팩토리를 가져오고, EmDash가 런타임에 이름이 지정된 createPlugin()을 가져옵니다.
./adminadminEntry를 설정한 경우호스트의 브라우저 빌드가 React 컴포넌트 맵을 가져옵니다.
./astrocomponentsEntry를 설정한 경우호스트의 Astro 빌드가 blockComponents를 가져옵니다.

디스크립터와 런타임의 모듈 지정자는 이러한 내보내기와 일치해야 합니다.

export function activityPlugin(): PluginDescriptor {
	return {
		id: "plugin-activity",
		version: "0.1.0",
		format: "native",
		entrypoint: "@example/plugin-activity",
		adminEntry: "@example/plugin-activity/admin",
		componentsEntry: "@example/plugin-activity/astro",
	};
}

export function createPlugin() {
	return definePlugin({
		id: "plugin-activity",
		version: "0.1.0",
		admin: {
			entry: "@example/plugin-activity/admin",
		},
	});
}

npm 패키지 버전, 디스크립터 버전, definePlugin() 버전을 동기화된 상태로 유지하세요. 사이트 관리자에게 표시되는 버전은 package.json에서 자동으로 가져오는 것이 아니라 플러그인 정의에서 가져옵니다.

플러그인 신원과 버전

definePlugin()은 소문자, 숫자, 하이픈으로 이루어진 스코프 없는 ID 또는 @scope/name 형식의 스코프가 있는 ID를 받습니다. 이 ID는 /_emdash/api/plugins/<plugin-id>/<route>에서 경로 세그먼트 하나를 차지하기도 하므로, 사이트 플러그인에는 스코프가 없는 kebab-case ID를 사용하세요.

다음 값은 허용되는 형식과, 플러그인 ID와 npm 패키지 이름을 분리하는 권장 방식을 보여 줍니다.

id: "plugin-activity"; // Recommended: valid in plugin route URLs
id: "@example/plugin-activity"; // Accepted by definePlugin(), but not one URL segment

entrypoint: "@example/plugin-activity"; // The npm package may stay scoped

버전은 시맨틱 major.minor.patch 순서로 시작해야 합니다. 디스크립터와 런타임 모두에 완전한 시맨틱 버전을 사용하세요.

version: "1.0.0"; // Valid
version: "1.2.3-beta.1"; // Valid prerelease
version: "1.0"; // Invalid: missing patch version

TypeScript 구성

다음 tsconfig.json은 React와 Astro 소스가 있는 네이티브 플러그인을 다룹니다.

{
	"compilerOptions": {
		"target": "ES2022",
		"module": "preserve",
		"moduleResolution": "bundler",
		"strict": true,
		"declaration": true,
		"outDir": "./dist",
		"rootDir": "./src",
		"jsx": "react-jsx",
		"types": ["astro/client"]
	},
	"include": ["src/**/*"],
	"exclude": ["node_modules", "dist"]
}

패키징하기 전에 소스 엔트리포인트를 대상으로 pnpm typecheck를 실행하세요. build 스크립트는 src/index.ts만 컴파일합니다. 내보낸 관리자 화면 및 Astro 소스는 호스트가 패키지를 사용할 때 컴파일합니다.

패키지 검사하기

게시하기 전에 tarball의 실제 내용을 테스트하세요. 아래 명령은 플러그인 디렉터리 옆에 my-emdash-site라는 일회용 사이트가 있다고 가정합니다.

  1. 패키지를 빌드하고 타입을 검사합니다.

    pnpm typecheck
    pnpm build
  2. npm tarball을 만들고 npm이 출력하는 파일 목록을 검토합니다.

    npm pack

    예제 패키지의 경우 npm은 example-plugin-activity-0.1.0.tgz를 만듭니다.

  3. 출력에 dist/index.mjs, dist/index.d.mts, 그리고 내보낸 ./admin 및 ./astro 모듈에서 접근할 수 있는 모든 소스 파일이 포함되어 있는지 확인합니다.

  4. 만들어진 tarball을 일회용 EmDash 사이트에 설치합니다.

    cd ../my-emdash-site
    pnpm add ../plugin-activity/example-plugin-activity-0.1.0.tgz
  5. 패키지 생성 및 등록을 따라 일회용 사이트의 astro.config.mjs에서 디스크립터 팩토리를 가져와 등록합니다. 그런 다음 호스트 사이트를 빌드합니다.

    pnpm build

    모든 플러그인 관리자 화면을 열고, 제공되는 모든 Portable Text 블록을 렌더링해 보세요. 빌드 전에 등록하면 Astro가 tarball의 ./admin 및 ./astro 내보내기를 해석합니다. 서버 전용 패키지 테스트로는 브라우저용 소스나 .astro 소스 파일이 누락된 것을 발견할 수 없습니다.

README 내용

운영자가 소스를 읽지 않고도 패키지를 설치하고 평가할 수 있도록 충분한 정보를 제공하세요. 다음을 포함하세요.

  • 한 문장 설명과 지원하는 EmDash 버전
  • 설치 명령과 전체 astro.config.mjs 등록 방법
  • 네이티브 신뢰 경계와 플러그인이 네이티브 실행을 필요로 하는 이유
  • 선언한 모든 capability와 허용된 호스트, 그리고 이를 사용하는 기능
  • 설정과 그 기본값
  • body-end 프래그먼트용 EmDashBodyEnd와 같이 필요한 레이아웃 컴포넌트
  • 운영자의 조치가 필요한 변경 사항에 대한 업그레이드 절차

capability 선언을 격리 경계로 설명하지 마세요. capability 선언은 ctx API를 제한할 뿐이며, 네이티브 코드는 호스트 프로세스에서 사용할 수 있는 import, 환경 변수, 직접적인 네트워크 호출을 여전히 사용할 수 있습니다.

npm에 게시하기

tarball 테스트를 통과한 후에 게시하세요.

npm publish --access public

스코프가 있는 패키지를 처음 공개 릴리스할 때는 --access public이 필요합니다. 이후 릴리스에는 시맨틱 버저닝을 사용하세요. 생성자 옵션, 저장된 데이터, 필요한 호스트 변경, 패키지 내보내기, 플러그인의 신뢰 요구 사항에 대한 변경은 호환성 결정으로 다루세요. 업그레이드에 새 capability나 허용 호스트가 필요하다면, 네이티브 설치에는 capability 동의 프롬프트가 없더라도 릴리스 노트에 명확히 밝히세요.

npm에서 설치하기

운영자는 게시된 패키지를 EmDash 사이트에 설치합니다.

pnpm add @example/plugin-activity

그런 다음 패키지 생성 및 등록에 나온 대로 astro.config.mjs에서 디스크립터 팩토리를 가져와 등록합니다. 의존성을 설치하는 것만으로는 플러그인이 활성화되지 않습니다. Astro 구성을 변경하고 사이트를 배포해야 설치가 완료됩니다.

호스트 사이트에 대해 개발하기

플러그인을 감시 모드로 빌드합니다.

pnpm dev

호스트 사이트에서 로컬 디렉터리를 설치합니다.

pnpm add ../plugin-activity

astro.config.mjs에 플러그인의 디스크립터 팩토리를 등록한 다음 호스트 개발 서버를 시작하세요. 디스크립터 메타데이터나 패키지 내보내기를 변경한 후에는 서버를 다시 시작하세요. 사용 중인 환경에서 패키지 매니저의 파일 의존성이 링크하는 대신 파일을 복사한다면, 다시 빌드한 뒤 재설치하세요. 워크스페이스 의존성이나 pnpm link를 사용하면 개발하는 동안 로컬 패키지가 연결된 상태로 유지됩니다.

레지스트리 경계

네이티브 패키지는 EmDash 레지스트리에 게시할 수 없습니다. 레지스트리 플러그인은 샌드박스 패키지 형식, 서명된 릴리스 워크플로, 설치 동의 흐름을 사용합니다. 플러그인에 React 관리자 코드, Astro 렌더러, 신뢰된 프래그먼트, 그 밖의 인프로세스 의존성이 더 이상 필요하지 않다면, 레지스트리를 통해 게시하기 전에 샌드박스 형식으로 변환하세요.