Vue

Addfox는 Vue 3을 완전히 지원하며, .vue 단일 파일 컴포넌트를 사용하여 확장을 개발할 수 있습니다.

설치

프로젝트 생성 시 Vue 템플릿 선택:

pnpm create addfox-app --framework vue

또는 기존 프로젝트에 설치:

pnpm
npm
yarn
bun
pnpm add @addfox/rsbuild-plugin-vue

구성

// addfox.config.ts
import { defineConfig } from "addfox";
import { pluginVue } from "@addfox/rsbuild-plugin-vue";

export default defineConfig({
  plugins: [pluginVue()],
});

프로젝트 구조

app/
├── background/
│   └── index.ts
├── content/
│   └── index.ts
├── popup/
│   ├── App.vue
│   ├── index.html
│   └── index.ts
├── options/
│   ├── App.vue
│   ├── index.html
│   └── index.ts
└── manifest.json

예시 코드

<!-- app/popup/App.vue -->
<template>
  <div class="popup">
    <h1>{{ title }}</h1>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const title = ref('Hello Vue!');
const count = ref(0);

const increment = () => {
  count.value++;
};
</script>

<style scoped>
.popup {
  padding: 16px;
  min-width: 200px;
}

h1 {
  font-size: 18px;
  margin-bottom: 12px;
}

button {
  padding: 8px 16px;
  background: #42b883;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>
// app/popup/index.ts
import { createApp } from 'vue';
import App from './App.vue';

createApp(App).mount('#root');

아래의 index.html생략하고 index.ts만 유지하면 빌드가 자동으로 HTML을 생성하고 id="root", manifest.name / manifest.icons과 동기화된 titlefavicon을 포함합니다 (자세한 내용은 파일 기반 Entry 참조). 아래 예시는 선택적 사용자 지정 템플릿입니다:

<!-- app/popup/index.html (선택적; 사용자 지정 시 title / 아이콘을 직접 작성해야 함) -->
<!DOCTYPE html>
<html lang="ko-KR">
  <head>
    <meta charset="UTF-8" />
    <title>Popup</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./index.ts" data-addfox-entry></script>
  </body>
</html>

Content Script

// app/content/index.ts
import { createApp } from 'vue';
import ContentApp from './ContentApp.vue';

// 마운트 포인트 생성
const container = document.createElement('div');
container.id = 'my-extension-root';
document.body.appendChild(container);

createApp(ContentApp).mount(container);
<!-- app/content/ContentApp.vue -->
<template>
  <div class="content-widget">
    <p>Hello from Vue Content Script!</p>
  </div>
</template>

<style scoped>
.content-widget {
  position: fixed;
  bottom: 20px;
  right: 20px;
  background: white;
  padding: 16px;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  z-index: 9999;
}
</style>

상태 관리

Pinia

pnpm add pinia
// app/stores/counter.ts
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
  },
});
// app/popup/index.ts
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';

const app = createApp(App);
app.use(createPinia());
app.mount('#root');

VueUse

pnpm add @vueuse/core
<script setup>
import { useStorage } from '@vueuse/core';

// chrome.storage에 자동 동기화
const count = useStorage('count', 0);
</script>

Chrome API와 상호 작용

<script setup lang="ts">
import { ref, onMounted } from 'vue';

const tabs = ref<chrome.tabs.Tab[]>([]);

onMounted(async () => {
  tabs.value = await chrome.tabs.query({});
});

const activateTab = (tabId: number) => {
  chrome.tabs.update(tabId, { active: true });
};
</script>

<template>
  <ul>
    <li 
      v-for="tab in tabs" 
      :key="tab.id"
      @click="activateTab(tab.id!)"
    >
      {{ tab.title }}
    </li>
  </ul>
</template>

CSS 솔루션

Tailwind CSS

pnpm add -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
/* app/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
<template>
  <div class="p-4 bg-gray-100">
    <h1 class="text-xl font-bold text-blue-600">Hello Tailwind!</h1>
  </div>
</template>

Scoped CSS

Vue 단일 파일 컴포넌트는 기본적으로 scoped CSS를 지원합니다:

<style scoped>
.button {
  background: #42b883;
}

/* 깊은 선택자 */
:deep(.child) {
  color: red;
}
</style>

관련 링크