회고

[회고] VSC 익스텐션 직접 만들어서 배포까지 - 개발 효율성 UP

닝닝깅 2024. 3. 14. 23:28

컴포넌트 하나를 만들기 위해서 폴더 하나에 컴포넌트 파일 하나와 스타일링 파일 하나를 각각 담는 경우가 많다. 파일마다 보일러 플레이트도 존재하기 때문에 컴포넌트의 개수가 많아질수록 매번 수동으로 다 만들기에 번거로움을 느껴 효율적인 작업을 하기 위해 익스텐션을 만들어봐야겠다는 생각을 하게 되었다.

 

💪🏻 만들고 싶은 익스텐션

여러 스타일링 도구에 적합한 익스텐션을 만들고 싶었기 때문에 구분이 필요했다. 일단은 내가 사용해본 styled componenet와 scss 대상으로 만들었다. 추후 더 추가할 예정!

 

생각한 동작은 아래와 같다.

1. 원하는 컴포넌트 위치의 상위 폴더에 우클릭을 한다.

2. "{컴포넌트명} {스타일링도구}" 을 입력받는다.

3. 컴포넌트명의 폴더가 생기고 하위에 컴포넌트 파일과 스타일링 파일이 자동 생성된다.

 

📌 기본 개념

우선 익스텐션에 대한 기본 개념부터 알고가야한다. 공식문서를 기반으로 작성하였다.

✅ Activation Events 

어떤 상황이 발생할 때 이벤트를 활성화할 지 정의한다. 

 

🚩 예시 ( 전체 이벤트 목록에 대한 공식문서는 여기에 )

onLanguage : 특정 언어로 해석되는 파일이 열렸을 때

"activationEvents": [
    "onLanguage:python" //파이썬 파일이 열렸을 때 이벤트 발생
]

 

onCommand : 명령이 호출될 때

"activationEvents": [
    "onCommand:extension.sayHello"
]

 

 

Contribution Points

다양한 VSCode 구성요소와 기능 중 어떤 것들을 확장할 것인가에 대한 내용을 package.json에 JSON형태로 정의한다.

 

🚩 예시 ( 전체 contrubution points에 대한 공식문서는 여기에 )

views

: 좌측 사이드 바의 explorer(폴더 탐색기), scm(소스 관리), debug(실행 및 디버깅) 등의 뷰가 해당된다. 뷰의 식별자, 이름, 발생시점 등을 정의하여 뷰 내부에 다른 뷰를 추가할 수 있다.

아래의 코드는 explorer 뷰 내부에 node 의존성 라이브러리 목록을 볼 수 있는 트리 뷰를 추가하는 예시 내용이다.

{
  "contributes": {
    "views": {
      "explorer": [
        {
          "id": "nodeDependencies",
          "name": "Node Dependencies",
          "when": "workspaceHasPackageJSON",
          "icon": "media/dep.svg",
          "contextualTitle": "Package Explorer"
        }
      ]
    }
  }
}

 

commands: 명령에 대한 UI요소를 정의할 수 있다.

// 명령이 화면에 "Hello World"라고 보여진다.
{
  "contributes": {
    "commands": [
      {
        "command": "extension.sayHello",
        "title": "Hello World"
      }
    ]
  }
}

 

✅ VSCodeAPI

익스텐션 개발 코드에서 호출할 수 있는 JavaScript API 모음이다.

command, env, workspace 등 VS Code의 UI 구성요소와 환경 변수에 접근할 수 있는 네임스페이스와 인터페이스, 이벤트 핸들러 메소드 등 거의 필요한 모든 정보들이 정의되어 있다.

 

🚩 예시

command

: 명령을 처리하기 위한 네임스페이스로 고유식별자를 가진 함수를 다룬다. 이 네임스페이스를 통해 함수를 등록하면 앞서 정의한 contribution points에 의해 해당 함수를 실행시킬 수 있다. 

//command API를 사용하여 extension.sayHello 명령에 함수를 등록한다.
commands.registerCommand('extension.sayHello', () => {
  window.showInformationMessage('Hello World!');
});

 

이제 이 요소들을 사용하여 원하는 익스텐션을 만들 수 있다. 해보잣

 

🏃🏻‍♂️ 개발 과정

✨ 기본 세팅

공식문서를 기반으로 만들기 시작하였다.

$ npx --package yo --package generator-code -- yo code

 

기본 세팅은 이렇게 하였다. webpack을 사용해보고 싶었기에 웹팩을 통한 번들링에 동의했다.

# ? What type of extension do you want to create? New Extension (TypeScript)
# ? What's the name of your extension? ComPoMaker
### Press <Enter> to choose default for all options below ###

# ? What's the identifier of your extension? compomaker
# ? What's the description of your extension?
# ? Initialize a git repository? Yes
# ? Bundle the source code with webpack? Yes
# ? Which package manager to use? npm

# ? Do you want to open the new folder with Visual Studio Code? Open with `code`

 

✨ webpack 설정

webpack으로 번들링 작업을 거치기 위해 설정이 필요하다.

이 내용에 대해서는 별도의 블로그 포스팅으로 담아두었다!

 

✨ package.json 수정

이제 package.json을 수정해보자. package.json의 contributes 내부에서 명령에 대한 정보와 어떻게 명령에 대한 이벤트를 실행할 수 있는 지 설정 가능하다.

"contributes": {
  "commands": [
    {
      "command": "makeComponent",
      "title": "Make Component"
    }
  ],
  "menus": {
    "explorer/context": [
      {
        "command": "makeComponent",
        "group": "navigation",               // 메뉴 최상단에 위치
        "when": "explorerResourceIsFolder"   // 폴더일 경우에만 명령이 활성화됨
      }
    ]
   }
 }

 

commands에서 명령의 titile을 "Make Component"라고 설정하였고, menus에서 어디에 명령이 위치할 지를 설정하였다.

 

한마디로 설명하자면 explorer뷰에서 폴더일 경우에만 "Make Component"라는 명령이 context 메뉴에서 navigation에 위치하게 하였다.

 

✨ extension.ts 수정

명령을 실행하면 어떤 이벤트가 발생하는 지를 수정할 수 있다.

 

간단한 구분을 위해 styled component -> st / scss -> sc라고 입력받을 수 있게 하고, 스타일링 도구에 따라 파일과 내부 보일러 플레이트를 달리 하였다.

st -> {컴포넌트명}.styled.ts

sc -> {컴포넌트명}.module.scss

import * as vscode from "vscode";
import * as fs from "fs";
import * as path from "path";
import { mainTemplate } from "./template/main";
import { styleTemplate } from "./template/style";

export function activate(context: vscode.ExtensionContext) {
  const commandFunc = async (uri: any) => {
    const fp = uri.fsPath;
    const component = await vscode.window.showInputBox({
      placeHolder: "입력형식->컴포넌트명 스타일링도구(st/sc)",
      validateInput: (str: string) => {
        if (!str) {
          return "컴포넌트명이 입력되지 않았습니다";
        }
        return undefined;
      },
    });
    const getStylingFile = (name: string, type: string) => {
      if (type == "st") return `${name}.styled.ts`;
      if (type == "sc") return `${name}.module.scss`;
      return "";
    };

    if (component) {
      const [componentName, componentStyle] = component?.split(" ");
      const componentPath = path.join(fp, componentName);
      const stylingFile = getStylingFile(componentName, componentStyle);

      if (fs.existsSync(componentPath)) {
        vscode.window.showInformationMessage(
          "컴포넌트 폴더 생성에 실패하였습니다."
        );
      } else if (componentStyle != "st" && componentStyle != "sc") {
        vscode.window.showInformationMessage(
          "올바르지 않은 스타일링 도구입니다."
        );
      } else {
        fs.mkdirSync(componentPath);

        const indexFilePath = path.join(componentPath, `${componentName}.tsx`);
        const styleFilePath = path.join(componentPath, `${stylingFile}`);

        fs.writeFileSync(
          indexFilePath,
          mainTemplate(componentName, stylingFile)
        );
        fs.writeFileSync(styleFilePath, styleTemplate(componentStyle));

        vscode.window.showInformationMessage("컴포넌트 폴더가 생성되었습니다.");
      }
    }
  };

  let disposable = vscode.commands.registerCommand("makeComponent", (uri) => {
    commandFunc(uri);
  });

  context.subscriptions.push(disposable);
}

export function deactivate() {}

파일의 정확한 경로와 파일 처리를 위해 path모듈과 fs모듈을 가져와 사용하고, if 문으로 나름의 에러 처리(?)를 하였다.

 

✨ 디버깅

이제 다 만든 익스텐션을 실행시켜보자! VSCode의 디버그 모드를 통해 익스텐션을 실행시켜볼 수 있다.

 

이것은 좀 TMI이지만..

매번 vscode에서 코드를 실행할 때 전체 실행(run)만 해봤지 디버그(debug)는 처음 시도해보았다. 뭐가 다른가 싶었는데 디버그 모드로 실행할 경우에는 중단점을 설정할 수 있다고 한다.

실제로 중단점을 설정하여 실행 테스트를 해보니 중단점 코드의 실행 전과 실행 후의 상태를 바로 확인할 수 있어서 console로 로그를 찍는 것보다 어디서 에러가 발생했는 지 명확하게 알 수 있어서 좋았다.!

 

1. 폴더의 좌클릭을 하여 메뉴에서 명령을 누르면

2. "{컴포넌트명} {스타일링도구}"을 입력하고

3. 폴더와 하위 파일들이 생성된다!

요로코롬 실행 확인 완료!!

 

🎁 패키징과 배포

배포를 해보자!

일단 익스텐션을 패키징하여 하나의 파일로 압축하기 위해 VSCode extension manager 를 설치한다.

npm install -g vsce

 

그리고 리드미를 수정한다. 왜인지는 모르겠지만 리드미 첫 수정을 하지 않으면 파일 업로드시 에러가 난다.

그리고 마켓플레이스 사이트에서 Publish Extension을 눌러 publiserId를 생성한다. 이렇게 생성한 publiserId와 리드미에 관한 설정을 패키징 전에 package.json에 추가해주어야 한다.

 //package.json
 "readme": "./README.md",
 "publisher": {생성한 publiser id}

이제 패키징 한다.

vsce package

 

패키징을 완료하면 .vsix 확장자 파일이 하나 만들어진다.

이제 이 파일을 마켓플레이스에서 생성한 publisher의 파일로 등록하면 배포가 완료된다! ( 10~15분의 시간 소요 )

배포 완료

🎆 후기

떴다!! 나의 첫 익스텐션 > <

VSCode의 익스텐션을 만들어보다니 신기했다.ㅎㅎ 간단하지만 개발의 효율성을 높일 방법을 만들었다는 것에 대하여 꽤 뿌듯함을 느꼈다. extension에 다양한 설정 기능이 있던데, 개발 규모가 큰 프로젝트에 참여하게 될 경우 이번 경험을 토대로 더 좋은 익스텐션을 만들어 협업 과정의 도움을 줄 수 있을 것 같단 생각을 하게 되었다.

 

 


참고

https://code.visualstudio.com/api/get-started/your-first-extension

https://velog.io/@93minki/VSCode-Extension-%EB%A7%8C%EB%93%A4%EA%B8%B0#%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0

https://medium.com/frontend-developers/vs-code-extension-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0-ae933343d2b5

https://www.freecodecamp.org/news/making-vscode-extension/