Skip to content

WXT 스토리지

변경 로그

확장 프로그램 스토리지 API를 간소화한 래퍼입니다.

Installation

WXT와 함께

이 모듈은 WXT에 내장되어 있어서 별도로 설치할 필요가 없습니다.

ts
import { storage } from 'wxt/storage';

자동 임포트를 사용한다면 storage가 자동으로 임포트되므로, 직접 임포트할 필요조차 없습니다!

WXT 없이 사용하기

NPM 패키지를 설치하세요:

sh
npm i @wxt-dev/storage
pnpm add @wxt-dev/storage
yarn add @wxt-dev/storage
bun add @wxt-dev/storage
ts
import { storage } from '@wxt-dev/storage';

스토리지 권한

wxt/storage API를 사용하려면 매니페스트에 "storage" 권한을 추가해야 합니다:

ts
// wxt.config.ts
export default defineConfig({
  manifest: {
    permissions: ['storage'],
  },
});

기본 사용법

모든 스토리지 키는 스토리지 영역을 접두사로 붙여야 합니다.

ts
// ❌ 이 코드는 에러를 발생시킵니다
await storage.getItem('installDate');

// ✅ 올바른 사용법
await storage.getItem('local:installDate');

local:, session:, sync:, 또는 managed:를 사용할 수 있습니다.

타입스크립트를 사용한다면, 대부분의 메서드에 타입 매개변수를 추가하여 키 값의 예상 타입을 지정할 수 있습니다:

ts
await storage.getItem<number>('local:installDate');
await storage.watch<number>(
  'local:installDate',
  (newInstallDate, oldInstallDate) => {
    // ...
  },
);
await storage.getMeta<{ v: number }>('local:installDate');

사용 가능한 모든 메서드 목록은 API 참조를 확인하세요.

Watchers

스토리지 변경 사항을 감지하려면 storage.watch 함수를 사용하세요. 이 함수를 통해 특정 키에 대한 리스너를 설정할 수 있습니다:

ts
const unwatch = storage.watch<number>('local:counter', (newCount, oldCount) => {
  console.log('Count changed:', { newCount, oldCount });
});

리스너를 제거하려면 반환된 unwatch 함수를 호출하세요:

ts
const unwatch = storage.watch(...);

// 나중에...
unwatch();

메타데이터

wxt/storage는 키에 대한 메타데이터 설정도 지원하며, 이는 key + "$"에 저장됩니다. 메타데이터는 키와 관련된 속성들의 모음입니다. 버전 번호나 마지막 수정 날짜 등이 될 수 있습니다.

버전 관리 외에도, 여러분은 필드의 메타데이터를 관리할 책임이 있습니다:

ts
await Promise.all([
  storage.setItem('local:preference', true),
  storage.setMeta('local:preference', { lastModified: Date.now() }),
]);

여러 번의 호출로 메타데이터의 서로 다른 속성을 설정할 때, 속성들은 덮어쓰지 않고 결합됩니다:

ts
await storage.setMeta('local:preference', { lastModified: Date.now() });
await storage.setMeta('local:preference', { v: 2 });

await storage.getMeta('local:preference'); // { v: 2, lastModified: 1703690746007 }

키와 관련된 모든 메타데이터를 제거하거나 특정 속성만 제거할 수 있습니다:

ts
// 모든 속성 제거
await storage.removeMeta('local:preference');

// "lastModified" 속성만 제거
await storage.removeMeta('local:preference', 'lastModified');

// 여러 속성 제거
await storage.removeMeta('local:preference', ['lastModified', 'v']);

스토리지 아이템 정의하기

같은 키와 타입 파라미터를 반복해서 작성하는 것은 번거로울 수 있습니다. 이를 대체하기 위해 storage.defineItem을 사용하여 "스토리지 아이템"을 생성할 수 있습니다.

스토리지 아이템은 storage 변수와 동일한 API를 포함하지만, 타입, 기본값 등을 한 곳에서 설정할 수 있습니다:

ts
// utils/storage.ts
const showChangelogOnUpdate = storage.defineItem<boolean>(
  'local:showChangelogOnUpdate',
  {
    fallback: true,
  },
);

이제 storage 변수를 사용하는 대신, 생성한 스토리지 아이템의 헬퍼 함수를 사용할 수 있습니다:

ts
await showChangelogOnUpdate.getValue();
await showChangelogOnUpdate.setValue(false);
await showChangelogOnUpdate.removeValue();
const unwatch = showChangelogOnUpdate.watch((newValue) => {
  // ...
});

사용 가능한 모든 속성과 메서드의 전체 목록은 API 참조를 확인하세요.

버전 관리

저장소 항목이 시간이 지남에 따라 커지거나 변경될 것으로 예상된다면 버전 관리를 추가할 수 있습니다. 항목의 첫 번째 버전을 정의할 때는 버전 1부터 시작합니다.

예를 들어, 확장 프로그램에서 무시하는 웹사이트 목록을 저장하는 저장소 항목을 생각해 보겠습니다.

ts
type IgnoredWebsiteV1 = string;

export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV1[]>(
  'local:ignoredWebsites',
  {
    fallback: [],
    version: 1,
  },
);
ts
import { nanoid } from 'nanoid'; 

type IgnoredWebsiteV1 = string;
interface IgnoredWebsiteV2 { 
  id: string; 
  website: string; 
} 

export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV1[]>( 
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>( 
  'local:ignoredWebsites',
  {
    fallback: [],
    version: 1, 
    version: 2, 
    migrations: { 
      // v1에서 v2로 마이그레이션할 때 실행됨
      2: (websites: IgnoredWebsiteV1[]): IgnoredWebsiteV2[] => { 
        return websites.map((website) => ({ id: nanoid(), website })); 
      }, 
    }, 
  },
);
ts
import { nanoid } from 'nanoid';

type IgnoredWebsiteV1 = string;
interface IgnoredWebsiteV2 {
  id: string;
  website: string;
}
interface IgnoredWebsiteV3 { 
  id: string; 
  website: string; 
  enabled: boolean; 
} 

export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>( 
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV3[]>( 
  'local:ignoredWebsites',
  {
    fallback: [],
    version: 2, 
    version: 3, 
    migrations: {
      // v1에서 v2로 마이그레이션할 때 실행됨
      2: (websites: IgnoredWebsiteV1[]): IgnoredWebsiteV2[] => {
        return websites.map((website) => ({ id: nanoid(), website }));
      },
      // v2에서 v3로 마이그레이션할 때 실행됨
      3: (websites: IgnoredWebsiteV2[]): IgnoredWebsiteV3[] => { 
        return websites.map((website) => ({ ...website, enabled: true })); 
      }, 
    },
  },
);

INFO

내부적으로 이 기능은 값의 현재 버전을 추적하기 위해 v라는 메타데이터 속성을 사용합니다.

이 경우, 무시된 웹사이트 목록이 미래에 변경될 수 있다고 생각했기 때문에 처음부터 버전 관리가 가능한 저장소 항목을 설정할 수 있었습니다.

현실적으로는 스키마를 변경해야 할 때까지 항목에 버전 관리가 필요한지 알 수 없습니다. 다행히도, 버전 관리가 없는 저장소 항목에 버전 관리를 추가하는 것은 간단합니다.

이전 버전을 찾을 수 없으면 WXT는 버전이 1이라고 가정합니다. 즉, version: 2를 설정하고 2에 대한 마이그레이션을 추가하기만 하면 바로 작동합니다!

이전과 동일한 무시된 웹사이트 예제를 살펴보겠습니다. 이번에는 버전 관리가 없는 항목으로 시작합니다:

ts
export const ignoredWebsites = storage.defineItem<string[]>(
  'local:ignoredWebsites',
  {
    fallback: [],
  },
);
ts
import { nanoid } from 'nanoid'; 

// 첫 번째 버전에 대한 타입을 나중에 추가함
type IgnoredWebsiteV1 = string; 
interface IgnoredWebsiteV2 { 
  id: string; 
  website: string; 
} 

export const ignoredWebsites = storage.defineItem<string[]>( 
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>( 
  'local:ignoredWebsites',
  {
    fallback: [],
    version: 2, 
    migrations: { 
      // v1에서 v2로 마이그레이션할 때 실행됨
      2: (websites: IgnoredWebsiteV1[]): IgnoredWebsiteV2[] => { 
        return websites.map((website) => ({ id: nanoid(), website })); 
      }, 
    }, 
  },
);

마이그레이션 실행

storage.defineItem이 호출되면, WXT는 마이그레이션이 필요한지 확인하고 필요한 경우 이를 실행합니다. 스토리지 아이템의 값이나 메타데이터를 가져오거나 업데이트하는 호출(getValue, setValue, removeValue, getMeta 등)은 실제로 값을 읽거나 쓰기 전에 마이그레이션 프로세스가 완료될 때까지 자동으로 대기합니다.

기본값 설정

storage.defineItem을 사용하면 기본값을 정의하는 여러 방법이 있습니다:

  1. fallback - 값이 없을 때 getValue에서 null 대신 이 값을 반환합니다.

    이 옵션은 설정에 기본값을 제공할 때 유용합니다:

    ts
    const theme = storage.defineItem('local:theme', {
      fallback: 'dark',
    });
    const allowEditing = storage.defineItem('local:allow-editing', {
      fallback: true,
    });
  2. init - 저장소에 값이 아직 저장되지 않았다면 초기화하고 저장합니다.

    이 방법은 초기화하거나 한 번 설정해야 하는 값에 적합합니다:

    ts
    const userId = storage.defineItem('local:user-id', {
      init: () => globalThis.crypto.randomUUID(),
    });
    const installDate = storage.defineItem('local:install-date', {
      init: () => new Date().getTime(),
    });

    값은 즉시 저장소에 초기화됩니다.

대량 작업

스토리지에서 여러 값을 가져오거나 설정할 때, 개별 스토리지 호출 횟수를 줄여 성능을 향상시키기 위해 대량 작업을 수행할 수 있습니다. storage API는 대량 작업을 수행하기 위한 여러 메서드를 제공합니다:

  • getItems - 여러 값을 한 번에 가져옵니다.
  • getMetas - 여러 항목의 메타데이터를 한 번에 가져옵니다.
  • setItems - 여러 값을 한 번에 설정합니다.
  • setMetas - 여러 항목의 메타데이터를 한 번에 설정합니다.
  • removeItems - 여러 값(및 선택적으로 메타데이터)을 한 번에 제거합니다.

이 모든 API는 문자열 키와 정의된 스토리지 항목을 모두 지원합니다:

ts
const userId = storage.defineItem('local:userId');

await storage.setItems([
  { key: 'local:installDate', value: Date.now() },
  { item: userId, value: generateUserId() },
]);

모든 대량 API 사용 방법에 대한 타입과 예제는 API 참조를 참조하세요.