1. 확장 기능
VS Code 확장 프로그램은 UI 커스터마이징에 적합하지 않습니다. 예를 들어 Atom의 tool-bar는 VS Code에서 구현하기 어렵습니다.
풍부한 확장 기능 모델을 제공하지만, 확장 프로그램이 하위 UI DOM에 직접 접근하는 것을 허용하지 않습니다 (즉, 확장 프로그램이 IDE의 외형을 바꾸기 어렵고 UI 커스터마이징이 제한적입니다). 이는 하위 수준의 지속적인 최적화를 용이하게 하기 위한 고려입니다.
With VS Code, we’re continually trying to optimize use of the underlying web technologies to deliver an always available, highly responsive editor and we will continue to tune our use of the DOM as these technologies and our product evolve.
UI DOM 계층은 최적화에 따라 빈번하게 변경될 수 있으며, VS Code는 이러한 최적화 항목이 확장 프로그램의 의존성에 의해 제한되는 것을 원하지 않습니다. 그래서 아예 UI 커스터마이징 기능을 제한해 두었습니다.
UI 커스터마이징을 제외한 IDE 관련 기능적 특성은 모두 확장을 지원합니다. 기초적인 구문 강조/API 힌트, 참조 이동(정의로 이동)/파일 검색, 테마 커스터마이징, 고급 디버그 프로토콜 등이 그 예입니다.
P.S. 사실 굳이 UI를 확장하고 싶다면 방법이 있긴 합니다 (확장 프로그램 실행 환경을 벗어나야 해서 꽤 많은 노력이 필요합니다). 자세한 내용은 access electron API from vscode extension을 참고하세요. 이후 노트에서 상세히 다루겠습니다.
2. 실행 환경
성능과 호환성을 위해, 확장 프로그램은 독립된 프로세스(extension host process라고 함)에서 실행됩니다. 또한 DOM에 직접 접근하는 것이 허용되지 않으므로 인텔리센스(IntelliSense)와 같은 내장 UI 컴포넌트 세트를 제공합니다.
따라서 확장 프로그램이 충돌하거나 응답하지 않아도 IDE의 정상적인 작동에는 영향을 미치지 않습니다. 예를 들어:
// ref: my-extension/src/extension.ts
export function activate(context: vscode.ExtensionContext) {
// hang up
while (true);
}
확장 프로그램 하나의 무한 루프가 IDE의 정상적인 사용이나 다른 확장 프로그램의 로드/활성화에 영향을 주지 않습니다. 하지만 프로세스 목록에서 Code Helper의 CPU 점유율이 100%에 육박하는 것을 볼 수 있습니다. 프로세스 수준의 샌드박스가 확장 프로그램 메커니즘의 안정성을 보장합니다.
3. 핵심 이념
안정성: 확장 프로그램 격리
확장 프로그램은 시작 성능과 IDE 자체의 안정성에 영향을 줄 수 있습니다. 그래서 프로세스 격리를 통해 이 문제를 해결했습니다. 확장 프로그램은 독립된 프로세스에서 실행되어 IDE와 그 시작 시간에 영향을 주지 않습니다.
이는 사용자 관점을 고려한 것입니다. 사용자가 IDE에 대해 완전한 통제권을 갖기를 원하며, 확장 프로그램이 무엇을 하든 IDE 기본 기능의 정상적인 사용에 영향을 주지 않기를 바라기 때문입니다.
P.S. extension host process는 VS Code 확장 API에 접근할 수 있는 특수한 Node 프로세스입니다. VS Code는 이 프로세스에 대한 디버그 지원도 제공합니다.
성능: 확장 프로그램 활성화
확장 프로그램은 모두 지연 로딩(Lazy Loading) 방식으로, 가능한 한 늦게(as late as possible) 로드됩니다. 특정 시나리오에서만 로드/활성화되므로 그전까지는 메모리 등의 리소스를 소모하지 않습니다.
구현 측면에서는 확장 프로그램이 특정 활성화 이벤트(activation events)를 등록하고, IDE가 이를 트리거하여 실행합니다. 예를 들어 마크다운 확장 프로그램은 사용자가 md 파일을 열 때만 활성화되면 됩니다.
활성화 방식
확장 프로그램에는 6가지 활성화 방식이 있습니다:
onLanguage:${language} 특정 언어의 문서 열기
onCommand:${command} Command Palette를 통해 특정 명령 실행
onDebug 디버그 모드 진입
workspaceContains:${toplevelfilename} 연 폴더에 특정 파일 포함
onView:${viewId} 지정된 뷰 펼치기
* IDE가 열리자마자 활성화
"activationEvents": ["*"]를 제외하면 모두 조건부 활성화이며, 특정 시나리오나 조건을 만족할 때만 확장 프로그램을 로드/활성화합니다.
확장 프로그램 매니페스트 파일
매니페스트 파일은 확장 프로그램의 메타 정보를 기술하는 데 사용됩니다. package.json을 직접 매니페스트 파일로 사용하며, 활성화 이벤트(activation events), 확장 프로그램이 강화하려는 확장 포인트(contribution points)와 같은 특수 필드를 추가했습니다.
IDE는 시작 과정에서 확장 프로그램 매니페스트 파일들을 훑어보고, UI 관련 사항은 UI를 확장하고 그 외의 사항은 확장 포인트와 확장 프로그램 기능을 연결합니다.
또한 확장 프로그램의 실행 환경이 Node 프로세스이므로 npm 패키지를 모두 사용할 수 있습니다. 의존성 모듈 역시 package.json에 선언합니다. 주의할 점은, 사용자가 확장 프로그램을 설치할 때 자동으로 npm install을 수행하지 않는다는 것입니다. 따라서 확장 프로그램을 배포하기 전에 의존성 모듈을 함께 패키징해야 합니다. 자세한 내용은 Installation and Packaging을 참고하세요.
P.S. 확장 포인트는 AOP의 조인 포인트(Join point)와 유사하며, 즉 "여기서 확장/강화가 가능하다"는 지점을 의미합니다. 예를 들어 새 커스텀 명령을 추가하는 것은 commands 확장 포인트를 강화하는 것입니다.
manifest
// package.json
{
// 插件名称
"name": "my-extension",
// 显示名称
"displayName": "MyExtension",
// 描述信息
"description": "An awesome vscode extension",
// 版本号 semver格式
"version": "0.0.1",
// 在插件市场展示的图标
"icon": "img/icon.png",
// 发布者名字
"publisher": "ayqy",
// vscode版本要求
"engines": {
"vscode": "^1.19.0"
},
// 所属分类,可选Languages, Snippets, Linters, Themes等等
"categories": ["Other"],
// 加载/激活方式
"activationEvents": ["onLanguage:javascript"],
// 入口文件路径
"main": "./out/extension",
// 注册扩展点关联
"contributes": {
"languages": [
{
"id": "javascript",
"aliases": ["JavaScript", "javascript"],
"extensions": [".js"]
}
]
}
}
P.S. 전체 내용은 Extension Manifest File - package.json을 참고하세요.
extension.ts/activate는 package.json에 선언된 activationEvents에 따라 단 한 번만 트리거됩니다. 트리거 조건은 특정 언어의 파일을 열거나 특정 명령을 실행하는 것 등이 될 수 있습니다. 활성화된 후에는 IDE가 닫히거나 충돌하기 전까지 extension.ts/deactivate가 트리거되지 않습니다. 따라서 일반적인 용법은 다음과 같습니다.
-
activate: 확장 프로그램이 활성화될 때 기능 모듈 싱글톤을 초기화합니다 (단 한 번만 실행).
-
deactivate: IDE가 종료되기 직전 현장을 정리합니다. 단, 최대 10초까지만 대기한다고 알려져 있으므로 너무 오래 걸리는 작업은 지양해야 합니다.
확장 포인트
즉 지원되는 확장 유형으로, 모두 package.json/contributes 아래에 선언됩니다. 다음과 같은 항목들이 있습니다.
configuration 확장 프로그램 설정 항목, 사용자가 Settings를 통해 설정 가능
configurationDefaults 확장 프로그램 설정 기본값
commands 명령 추가, 사용자가 Command Palette에 특정 명령을 입력하여 확장 프로그램 기능을 활성화
menus 명령과 연결된 메뉴 항목 추가, 사용자가 메뉴를 클릭할 때 대응하는 명령 실행
keybindings 명령과 연결된 단축키 추가, 사용자가 특정 단축키를 누를 때 대응하는 명령 실행
languages 파일 형식 연결 또는 새 언어 확장, 사용자가 특정 파일 형식을 열 때 대응하는 명령 실행
debuggers 디버거 추가, VS Code 디버그 프로토콜을 통해 IDE와 통신
breakpoints 디버거와 협력하여 디버거가 지원하는 언어 유형 선언
grammars 새 TextMate 문법 추가, 구문 강조
themes 맞춤 테마 추가
snippets 코드 스니펫 추가
jsonValidation JSON 형식 검증 추가
views 왼쪽 파일 탐색기나 디버그 뷰에 섹션 추가
problemMatchers 오류 매칭 추가, 린트 결과에서 에러/경고 파싱
problemPatterns problemMatchers와 협력하여 매칭 패턴 정의
menus는 공식적으로 제공되는 유일한 UI 확장 수단입니다. 확장을 지원하는 메뉴는 다음과 같습니다.
Command Palette 검색창 아래 메뉴 commandPalette
파일 탐색기 우클릭 메뉴 explorer/context
에디터
우클릭 메뉴 editor/context
타이틀 바 메뉴 editor/title
타이틀 바 우클릭 메뉴 editor/title/context
디버그 뷰
콜 스택 우클릭 메뉴 debug/callstack/context
SCM (소스 제어) 뷰
타이틀 바 메뉴 scm/title
파일 그룹 메뉴 scm/resourceGroup/context
파일 상태 메뉴 scm/resource/context
파일 변경 메뉴 scm/change/title
왼쪽 뷰
파일 탐색기 섹션 view/title
디버그 뷰 섹션 view/item/context
P.S. 모두 눈에 띄지 않는 위치이며, 대대적인 UI 커스터마이징은 지원되지 않습니다. 예를 들어 왼쪽 사이드바(Activity Bar)에 아이콘 하나 추가하는 것조차 불가능합니다.
타이틀 바의 메뉴 확장은 커스텀 아이콘을 지원하지만, 정의 방식이 다소 독특합니다. 예를 들어:
"commands": [{
"command": "markdown.showPreviewToSide",
"title": "%markdown.previewSide.title%",
"category": "Markdown",
"icon": {
"light": "./media/PreviewOnRightPane_16x.svg",
"dark": "./media/PreviewOnRightPane_16x_dark.svg"
}
}],
"menus": {
"editor/title": [
{
"command": "markdown.showPreviewToSide",
"when": "editorLangId == markdown",
"alt": "markdown.showPreview",
"group": "navigation"
}
]
}
command에 icon을 정의하고, menu를 해당 command에 연결하면 menu에 해당 아이콘이 표시됩니다.
확장 API
환경 격리 덕분에 확장 프로그램이 사용할 수 있는 API를 엄격히 제한하는 것이 훨씬 쉬워졌습니다. 확장 프로그램은 IDE가 제공하는 확장성 API에만 접근할 수 있으며, 함부로 조작할 수 없습니다 (공식적으로 지원하는 테마 설정 등을 제외하고 UI DOM이나 스타일 수정 등).
API 설계 원칙
확장 프로그램 API는 몇 가지 원칙을 따릅니다.
-
Promise 기반: 비동기 작업은 모두 Promise로 기술합니다.
-
취소 토큰:
CancellationToken을 추가 인자로 전달하여 취소 상태를 확인하거나 알림을 받습니다. -
해제 가능한 리소스 관리: 보유한 리소스는 수동으로 해제해야 합니다 (이벤트 리스너, 명령, UI 상호작용 등).
-
이벤트 API: 구독 메서드(
on[Will|Did]VerbNoun)를 호출할 때 리스너(event 인자 수신)를 전달하면 Disposable을 반환합니다. -
엄격한 Null 체크: TypeScript를 통해
undefined와null을 엄격히 구분합니다.
P.S. "해제 가능(Disposable)"에 대한 자세한 내용은 Dispose pattern을 참고하세요.
API 개요
API는 네임스페이스에 따라 조직되며, 전역 네임스페이스는 다음과 같습니다.
commands 명령 실행/등록. IDE 자체 명령 및 다른 확장 프로그램이 등록한 명령 모두 포함 (예: executeCommand)
debug 디버그 관련 API (예: startDebugging)
env IDE 관련 환경 정보 (예: machineId, sessionId)
extensions 확장 프로그램 간 API 호출, extensionDependency를 통해 의존성 선언
languages 프로그래밍 언어 관련 API (예: createDiagnosticCollection, registerDocumentFormattingEditProvider)
scm 소스 코드 버전 관리 API (예: createSourceControl)
window 에디터 창 관련 API (예: onDidChangeTextEditorSelection, createTerminal, showTextDocument)
workspace 워크스페이스 레벨 API (폴더를 열었을 때 활성화, 예: findFiles, openTextDocument, saveAll)
예를 들어 workspace.findFiles + languages.registerDefinitionProvider를 사용하여 Haste의 전역 모듈 참조 이동 지원을 구현할 수 있습니다.
또한 일부 API는 명령 형태로 제공됩니다 (위에서 언급한 "IDE 자체의" 명령). 예: vscode.previewHtml, vscode.openFolder, editorScroll 등.
프로토콜 기반 확장
확장 프로세스와 IDE 사이는 특정 프로토콜을 통해 통신합니다. 구현상으로는 JSON 형식의 stdin/stdout을 통해 통신이 이루어집니다.
이 모델의 더욱 강력한 점은 약속된 통신 프로토콜만 준수한다면 어떤 언어로든 확장 프로그램을 구현할 수 있다는 것입니다.
4. 언어 관련 확장
설정 파일을 통해 구문 강조, 코드 스니펫, 지능형 괄호 매칭을 지원하며, 더 복잡한 기능은 확장 API나 language server를 통해 구현합니다.
설정형 확장
-
구문 강조: 기초적으로 문자열, 주석, 키워드 등을 구분하며, 고급 단계에서는 변수, 함수 참조 등의 시맨틱 구분도 지원합니다.
-
코드 스니펫: 스니펫 퀵 입력. 기초적인 플레이스홀더부터 고급 중첩 플레이스홀더까지 지원합니다.
-
지능형 괄호 매칭: 괄호, 따옴표, 여러 줄 주석 등 쌍으로 나타나는 요소를 자동으로 완성합니다.
주의할 점은, VS Code의 언어 확장은 표준 Text Mate Grammar(tmLanguage 형식)를 지원한다는 것입니다. Monaco Editor의 비주류 Monarch-style보다 훨씬 친숙합니다. 자세한 내용은 Colorization Clarification을 참고하세요.
프로그래밍형 확장
간단한 설정으로 해결할 수 없는 기능은 확장 API(스크립트 작성)를 통해 구현하며, 두 가지 방식이 있습니다.
-
Language Server Protocol을 구현하여 IDE와 통신 (완전히 독립적).
-
Provider를 등록하여 커스텀 기능 제공 (hook 방식).
첫 번째 방식은 번거롭지만 더 강력하고 유연하며, 두 번째 방식은 간편하고 직접적이지만 유연성은 다소 떨어집니다. 지원되는 확장 기능은 다음과 같습니다.
-
hover 힌트: 기초적으로 타입, 문서 정보 등을 제공하며, 고급 단계에서는 메서드 시그니처 구문 강조를 지원합니다.
-
완성 제안: 제안 항목 옆에 추가 정보를 표시하는 고급 기능을 지원합니다.
-
오류 검사: 기초적으로 파일 저장 시 내용을 검사하고, 고급 단계에서는 열린 폴더 내의 모든 리소스를 대상으로 검사합니다.
-
메서드 시그니처: 시그니처 내에 파라미터 설명 문서를 포함할 수 있습니다.
-
정의로 이동: 정의가 여러 곳일 때 모두 나열해 주는 기능을 지원합니다.
-
참조 찾기: 모든 참조 위치를 반환합니다.
-
선택 항목 강조: 현재 문서 내의 모든 동일 참조를 찾아 강조합니다.
-
메서드/변수 선언 목록: 문서 내의 모든 식별자와 그 정의 위치를 반환합니다.
-
빠른 수정: 경고나 에러에 대한 해결책을 제시하고 빠르게 수정합니다. 단순 교정부터 중복 코드를 함수로 추출하는 등의 고급 기능까지 포함합니다.
-
컨텍스트 작업 옵션: 코드 문맥에 따라 추가 정보와 작업 옵션을 제공합니다. 커스텀 명령 추가가 가능합니다.
-
이름 바꾸기: 워크스페이스 내 여러 파일에 걸친 이름 바꾸기를 지원합니다.
-
코드 포맷팅: 전체 문서, 선택 영역, 입력 중 포맷팅을 지원합니다.
5. 개발 단계
환경 요구 사항
단계
스캐폴딩 도구를 통해 프로젝트 템플릿을 생성합니다:
yo code
대화형 명령으로 확장 프로그램 유형을 선택합니다:
New Extension (TypeScript)
New Extension (JavaScript)
New Color Theme
New Language Support
New Code Snippets
New Extension Pack
TypeScript 사용을 권장합니다. Extension Pack(확장 프로그램 팩)은 React Native의 Nuclide와 같이 여러 확장 프로그램을 묶은 형태입니다.
확장 프로그램 이름 등 메타 정보를 입력하여 프로젝트를 생성하고, VS Code에서 해당 프로젝트만 단독으로 열어(워크스페이스에 다른 디렉터리가 없어야 함) F5 키를 눌러 디버깅을 시작합니다.
확장 프로그램의 엔트리 파일은 my-extension/src/extension.ts이며, 프로젝트 구조는 VS Code 내장 확장 프로그램을 참고하면 좋습니다.
// ref: https://github.com/Microsoft/vscode/tree/master/extensions/markdown
markdown/
media/
*.svg
*.css
snippets/
markdown.json
syntaxes/
*.tmLanguage
src/
features/
*Provider.ts
typings/
*.d.ts
commandManager.ts
commands.ts
logger.ts
markdownEngine.ts
security.ts
telemetryReporter.ts
6. 패키징 및 배포
CLI 도구인 vsce를 제공합니다.
npm install -g vsce
패키징
확장 프로그램 디렉터리에서 .vsix 파일로 패키징합니다:
cd my-extension
vsce package
my-extension.vsix 로컬 패키지(node_modules 의존성 포함)를 얻게 됩니다. 공개하고 싶지 않다면 직접 패키지 파일을 전달하여 설치해야 합니다. npm registry와 달리 자체 레지스트리를 구축하여 비공개 확장 프로그램을 관리하는 방식을 Visual Studio Marketplace(VS Code 마켓플레이스)는 지원하지 않습니다.
If you want to share your extension with others privately, you can send them your packaged extension .vsix file.
(Sharing Privately with Others 참고)
자체 마켓플레이스를 구축할 방법이 없으므로, 자동 다운로드나 설치 안내 등 확장 프로그램 업데이트 문제를 수동으로 해결할 방법을 찾아야 합니다.
배포
마켓플레이스에 배포하려면 다음 과정이 필요합니다.
-
Visual Studio Team Services 계정 생성.
-
Security 페이지에서 Personal Access Token 생성.
-
vsce create-publisher (이름)명령으로 게시자 추가. -
vsce login (이름)명령으로 로그인. -
vsce publish -p <token>명령으로 배포.
자세한 내용은 Publishing Extensions를 참고하세요.
아직 댓글이 없습니다