소스 정보
- 저장소
- gitkraken/vscode-gitlens
- 최근 소스 활동
- 2026년 9월 9일 01:41
- 감지된 SKILL.md 언어
- 영어
- 스타
- 9,928
- 포크
- 1,801
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
SKILL.md 표시 중
SKILL.md
소스 지침 · 읽기 전용 미리보기- name
- add-webview
- description
- Create new webviews with RPC services, Lit app, and registration
# /add-webview - Create New Webview
Scaffold a new webview with all required boilerplate.
## Usage
```
/add-webview [name]
```
## Information Needed
1. **Webview name** — camelCase (e.g., `myFeature`)
2. **Display title** — e.g., "My Feature"
3. **Type** — `view` (sidebar) or `panel` (editor panel)
4. **Pro feature** — Yes/No (affects file location)
Webviews talk to the host over a single stack: Supertalk RPC (`src/webviews/rpc/`) over the
namespaced binary postMessage pipe. There is no legacy message protocol. Read `docs/webview-architecture.md`
first; use `src/webviews/allowedSigners/` + `src/webviews/apps/allowedSigners/` as the smallest
end-to-end reference, and Timeline for the signals/persistence patterns.
## Files to Create
### 1. Protocol: `src/webviews/{name}/protocol.ts` — pure types
```typescript
import type { WebviewState } from '../protocol.js';
export interface State extends WebviewState<'gitlens.{name}'> {
loading: boolean;
}
// Params/result types for your service methods live here too
export interface DoSomethingParams {
id: string;
}
export interface DoSomethingResult {
ok: boolean;
}
```
No message declarations and no `scope` const — methods/events are declared on the RPC service.
### 2. RPC service: `src/webviews/rpc/{name}Service.ts`
```typescript
import type { Container } from '../../container.js';
import type { DoSomethingParams, DoSomethingResult } from '../{name}/protocol.js';
import type { EventVisibilityBuffer, SubscriptionTracker } from './eventVisibilityBuffer.js';
import { createRpcEvent } from './eventVisibilityBuffer.js';
import type { RpcEventSubscription } from './services/types.js';
import type { SharedWebviewServices } from './services/common.js';
/** Fired when host-owned data changes — payload must be a complete snapshot (save-last buffered). */
export interface DidChangeDataEvent {
items: string[];
}
/** The RPC-facing surface of {@link {Name}Service}. */
export interface {Name}ViewService {
readonly onDataChange: RpcEventSubscription<DidChangeDataEvent>;
doSomething(params: DoSomethingParams): Promise<DoSomethingResult>;
}
/** RPC services for the {Name} webview. */
export interface {Name}Services extends SharedWebviewServices {
readonly {name}: {Name}ViewService;
}
export class {Name}Service implements {Name}ViewService {
readonly onDataChanged: RpcEventSubscription<DidChangeDataEvent>;
readonly #didDataChange = createRpcEvent<DidChangeDataEvent>('dataChanged', 'save-last');
constructor(container: Container, buffer: EventVisibilityBuffer | undefined, tracker?: SubscriptionTracker) {
this.onDataChanged = this.#didDataChange.subscribe(buffer, tracker);
}
fireDataChanged(event: DidChangeDataEvent): void {
this.#didDataChange.fire(event);
}
async doSomething(params: DoSomethingParams): Promise<DoSomethingResult> {
return { ok: true };
}
}
```
Queries take an optional trailing `AbortSignal`; events are `save-last` so a hidden webview gets
the latest snapshot on show.
### 3. Provider: `src/webviews/{name}/{name}Webview.ts`
```typescript
import type { Container } from '../../container.js';
import type { WebviewHost, WebviewProvider } from '../webviewProvider.js';
import type { EventVisibilityBuffer, SubscriptionTracker } from '../rpc/eventVisibilityBuffer.js';
import { createSharedServices } from '../rpc/services/common.js';
import { proxyServices } from '../rpc/services/proxy.js';
import type { State } from './protocol.js';
import type { {Name}Services } from '../rpc/{name}Service.js';
import { {Name}Service } from '../rpc/{name}Service.js';
export class {Name}WebviewProvider implements WebviewProvider<State, State> {
constructor(
private readonly container: Container,
private readonly host: WebviewHost<'gitlens.{name}'>,
) {}
dispose(): void {}
getRpcServices(buffer?: EventVisibilityBuffer, tracker?: SubscriptionTracker): {Name}Services {
const shared = createSharedServices(
this.container,
this.host,
context => {
this._telemetryContext = context;
},
buffer,
tracker,
);
this._service ??= new {Name}Service(this.container, buffer, tracker);
return proxyServices({
...shared,
{name}: this._service,
} satisfies {Name}Services);
}
includeBootstrap(): State {
return {
webviewId: this.host.id,
webviewInstanceId: this.host.instanceId,
timestamp: Date.now(),
loading: false,
};
}
}
```
Prefer resource-shaped queries over a monolithic bootstrap; keep `includeBootstrap()` minimal.
### 4. Registration: `src/webviews/{name}/registration.ts`
```typescript
import { ViewColumn } from 'vscode';
import { loadChunk } from '../../system/-webview/loadChunk.js';
import type { WebviewPanelsProxy, WebviewsController } from '../webviewsController.js';
import type { State } from './protocol.js';
export type {Name}WebviewShowingArgs = [];
export function register{Name}WebviewPanel(
controller: WebviewsController,
): WebviewPanelsProxy<'gitlens.{name}', {Name}WebviewShowingArgs, State> {
return controller.registerWebviewPanel<'gitlens.{name}', State, State, {Name}WebviewShowingArgs>(
{ id: 'gitlens.{name}' },
{
id: 'gitlens.{name}',
fileName: '{name}.html',
title: '{Title}',
contextKeyPrefix: 'gitlens:webview:{name}',
trackingFeature: '{name}Webview',
type: '{name}',
plusFeature: false,
column: ViewColumn.Active,
webviewHostOptions: { retainContextWhenHidden: false },
},
async (container, host) => {
const { {Name}WebviewProvider } = await loadChunk(
() => import(/* webpackChunkName: "webview-{name}" */ './{name}Webview.js'),
);
return new {Name}WebviewProvider(container, host);
},
);
}
```
For a sidebar view use `registerWebviewView` / `WebviewViewsProxy` instead (see any view's
`registration.ts`), then register it in `src/container.ts`.
### 5. App: `src/webviews/apps/{name}/{name}.ts`
```typescript
import type { Remote, Subscription } from '@eamodio/supertalk';
import { subscribe } from '@eamodio/supertalk';
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { fromBase64ToString } from '@gitlens/utils/base64.js';
import type { State } from '../../{name}/protocol.js';
import type { {Name}Services } from '../../rpc/{name}Service.js';
import { SignalWatcherWebviewApp } from '../shared/appBase.js';
import { getHost } from '../shared/host/context.js';
import { RpcController } from '../shared/rpc/rpcController.js';
import { create{Name}State } from './state.js';
import { styles } from './{name}.css.js';
@customElement('gl-{name}-app')
export class Gl{Name}App extends SignalWatcherWebviewApp {
static override styles = styles;
@property({ type: String, noAccessor: true })
private context!: string;
private _host = getHost();
private _state = create{Name}State();
private _eventsSubscription?: Subscription;
private _service?: Awaited<Remote<{Name}Services>['{name}']>;
protected override readonly _rpc = new RpcController<{Name}Services>(this, {
rpcOptions: {
webviewId: () => this._webview?.webviewId,
webviewInstanceId: () => this._webview?.webviewInstanceId,
endpoint: () => this._host.createEndpoint(),
},
onReady: services => this._onRpcReady(services),
});
override connectedCallback(): void {
super.connectedCallback?.();
// One-shot bootstrap attribute: cache-then-clear, safe across startup remounts
const context = this.consumeOneShotAttribute(this.context);
this.context = undefined!;
this.initWebviewContext(context);
const metadata = JSON.parse(fromBase64ToString(context)) as State;
this._state.loading.set(metadata.loading);
}
override disconnectedCallback(): void {
this._eventsSubscription?.unsubscribe();
this._eventsSubscription = undefined;
this._service = undefined;
this._state.resetAll();
super.disconnectedCallback?.();
}
private async _onRpcReady(services: Remote<{Name}Services>): Promise<void> {
const service = await services.{name};
this._service = service;
// Subscribe FIRST, before fetching — subscriptions are re-armed per handshake and
// save-last events re-emit the latest snapshot on connect.
this._eventsSubscription?.unsubscribe();
this._eventsSubscription = subscribe<{Name}Services>(this._rpc.connection!, async remoteServices => {
(await remoteServices.{name}).onDataChanged(() => {
/* update signals */
});
});
}
}
```
Readiness needs no message — each mount's session announces itself over RPC and `_onRpcReady`
runs against the fresh connection. Focus/visibility arrive via window CustomEvents dispatched by
`RpcController`; override `onWebviewFocusChanged`/`onWebviewVisibilityChanged` if needed.
### 6. State: `src/webviews/apps/{name}/state.ts`
```typescript
import { createSignalGroup } from '../shared/state/signals.js';
export function create{Name}State() {
const { signal, resetAll } = createSignalGroup();
const loading = signal(false);
return {
loading: loading,
resetAll: resetAll,
};
}
```
Use `createStateGroup()` + `persisted()` instead of plain signals for navigation/UI state that
must survive hide/show (see `docs/webview-architecture.md`, "State groups and persistence").
### 7. Styles: `src/webviews/apps/{name}/{name}.css.ts`
```typescript
import { css } from 'lit';
export const styles = css`
:host { display: block; height: 100%; }
.{name} { padding: 1rem; }
`;
```
## Accessibility
For accessibility requirements when creating or modifying webview components, see `docs/accessibility.md`.
## Additional Steps
8. **Webpack entry** — Add to `getWebviewsConfigs()` in `webpack.config.mjs`
9. **Register** in `src/container.ts` (call your `register{Name}Webview*` function)
10. **View ID** — Add to `src/constants.views.ts`
11. **Build** — `pnpm run build:webviews`
## File Locations
| Component | Community | Pro |
| ----------------- | --------------------------- | -------------------------------- |
| Protocol/Provider | `src/webviews/{name}/` | `src/webviews/plus/{name}/` |
| RPC service | `src/webviews/rpc/` | |
| App | `src/webviews/apps/{name}/` | `src/webviews/apps/plus/{name}/` |
## Localization
Every user-facing string in the new code (titles, notifications, quick pick items, placeholders, webview text, ARIA labels) must be a literal `l10n.t()` message — `{ l10n }` from `vscode` in host code, `* as l10n` from `@vscode/l10n` in webviews and packages. Run `pnpm run generate:l10n` afterward so the catalog check passes. Manifest text (command titles, view names) goes through `contributions.json` → `package.nls.json` as before. See `docs/localization.md`.
GitHub에서 보기