"VS Code의 마켓플레이스처럼 사용자가 기능을 추가할 수 있는 앱" — 플러그인 시스템을 만들면 앱의 수명과 가치가 크게 올라갑니다.


플러그인 시스템 설계

플러그인 인터페이스 정의

JAVASCRIPT
// 플러그인이 구현해야 할 인터페이스
// plugin-api.js
class PluginAPI {
  constructor(app) {
    this.app = app;
  }

  // 메뉴에 항목 추가
  addMenuItem(menu, item) {
    this.app.menuManager.addItem(menu, item);
  }

  // 사이드바에 패널 추가
  addSidebarPanel(panel) {
    this.app.sidebarManager.addPanel(panel);
  }

  // 명령어 등록
  registerCommand(id, handler) {
    this.app.commandRegistry.register(id, handler);
  }

  // 설정 스키마 등록
  registerSettings(schema) {
    this.app.settingsManager.registerSchema(schema);
  }

  // 이벤트 구독
  on(event, handler) {
    this.app.events.on(event, handler);
  }
}

플러그인 구조

PLAINTEXT
my-plugin/
├── package.json
├── index.js         # 메인 진입점
└── ui/
    └── panel.html   # UI 컴포넌트
JSON
{
  "name": "my-plugin",
  "version": "1.0.0",
  "main": "index.js",
  "electronPlugin": {
    "displayName": "내 플러그인",
    "description": "기능 설명",
    "activationEvents": ["onStartup"]
  }
}
JAVASCRIPT
// index.js — 플러그인 진입점
module.exports = {
  activate(api) {
    // 플러그인 활성화 시 호출
    api.registerCommand('myPlugin.hello', () => {
      console.log('Hello from plugin!');
    });

    api.addMenuItem('tools', {
      label: '내 플러그인 실행',
      click: () => api.executeCommand('myPlugin.hello'),
    });
  },

  deactivate() {
    // 플러그인 비활성화 시 정리
    console.log('Plugin deactivated');
  },
};

플러그인 로더

JAVASCRIPT
class PluginLoader {
  constructor(pluginsDir) {
    this.pluginsDir = pluginsDir;
    this.plugins = new Map();
  }

  async loadAll() {
    const dirs = await fs.promises.readdir(this.pluginsDir, {
      withFileTypes: true,
    });

    for (const dir of dirs) {
      if (dir.isDirectory()) {
        await this.loadPlugin(path.join(this.pluginsDir, dir.name));
      }
    }
  }

  async loadPlugin(pluginPath) {
    try {
      const pkgPath = path.join(pluginPath, 'package.json');
      const pkg = JSON.parse(await fs.promises.readFile(pkgPath, 'utf-8'));
      const mainFile = path.join(pluginPath, pkg.main || 'index.js');

      // 플러그인 모듈 로드
      const plugin = require(mainFile);
      this.plugins.set(pkg.name, {
        module: plugin,
        metadata: pkg.electronPlugin || {},
        path: pluginPath,
      });

      console.log(`플러그인 로드: ${pkg.name}`);
    } catch (error) {
      console.error(`플러그인 로드 실패: ${pluginPath}`, error);
    }
  }

  activateAll(api) {
    for (const [name, plugin] of this.plugins) {
      try {
        plugin.module.activate(api);
        console.log(`플러그인 활성화: ${name}`);
      } catch (error) {
        console.error(`플러그인 활성화 실패: ${name}`, error);
      }
    }
  }

  deactivateAll() {
    for (const [name, plugin] of this.plugins) {
      try {
        plugin.module.deactivate?.();
      } catch (error) {
        console.error(`플러그인 비활성화 실패: ${name}`, error);
      }
    }
  }
}

보안 고려사항

JAVASCRIPT
// 플러그인 샌드박싱 — vm 모듈 사용
const vm = require('vm');

function loadPluginSandboxed(code, api) {
  const sandbox = {
    api: api,                    // 제한된 API만 전달
    console: console,
    setTimeout: setTimeout,
    // require는 전달하지 않음 — 시스템 접근 차단
  };

  const context = vm.createContext(sandbox);
  const script = new vm.Script(code);
  script.runInContext(context);
}

면접 포인트 정리

  • 플러그인 시스템은 인터페이스 정의 → 로더 → 생명주기 관리로 구성
  • activate/deactivate로 플러그인 생명주기 관리
  • 보안을 위해 플러그인에 제한된 API만 노출
  • VS Code의 Extension API가 좋은 참고 모델

플러그인 시스템을 다뤘으면, 다음은 OS 통합 기능을 알아봅시다.

댓글 로딩 중...