feat: whyrating - initial project from turbostarter boilerplate
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: Background service worker
|
||||
description: Configure your extension's background service worker.
|
||||
url: /docs/extension/structure/background
|
||||
---
|
||||
|
||||
# Background service worker
|
||||
|
||||
An extension's service worker is a powerful script that runs in the background, separate from other parts of the extension. It's loaded when it is needed, and unloaded when it goes dormant.
|
||||
|
||||
Once loaded, an extension service worker generally runs as long as it is actively receiving events, though it [can shut down](https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle#idle-shutdown). Like its web counterpart, an extension service worker cannot access the DOM, though you can use it if needed with [offscreen documents](https://developer.chrome.com/docs/extensions/reference/api/offscreen).
|
||||
|
||||
Extension service workers are more than network proxies (as web service workers are often described), they run in a separate [service worker context](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers). For example, when in this context, you no longer need to worry about CORS and can fetch resources from any origin.
|
||||
|
||||
In addition to the [standard service worker events](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope#events), they also respond to extension events such as navigating to a new page, clicking a notification, or closing a tab. They're also registered and updated differently from web service workers.
|
||||
|
||||
**It's common to offload heavy computation to the background service worker**, so you should always try to do resouce-expensive operations there and send results using [Messages API](/docs/extension/structure/messaging) to other parts of the extension.
|
||||
|
||||
Code for the background service worker is located at `src/app/background` directory - you need to use `defineBackground` within `index.ts` file inside to allow WXT to include your script in the build.
|
||||
|
||||
```ts title="src/app/background/index.ts"
|
||||
import { defineBackground } from "wxt/sandbox";
|
||||
|
||||
const main = () => {
|
||||
console.log(
|
||||
"Background service worker is running! Edit `src/app/background` and save to reload.",
|
||||
);
|
||||
};
|
||||
|
||||
export default defineBackground(main);
|
||||
```
|
||||
|
||||
To see the service worker in action, reload the extension, then open its "Service Worker inspector":
|
||||
|
||||

|
||||
|
||||
You should see what we've logged in the console:
|
||||
|
||||

|
||||
|
||||
To communicate with the service worker from other parts of the extension, you can use the [Messaging API](/docs/extension/structure/messaging).
|
||||
|
||||
## Persisting state
|
||||
|
||||
<Callout>
|
||||
Service workers in `dev` mode always remain in `active` state.
|
||||
</Callout>
|
||||
|
||||
The worker becomes idle after a few seconds of inactivity, and the browser will kill its process entirely after 5 minutes. This means all state (variables, etc.) is lost unless you use a storage engine.
|
||||
|
||||
The simplest way to persist your background service worker's state is to use the [storage API](/docs/extension/structure/storage).
|
||||
|
||||
The more advanced way is to send the state to a remote database via our [backend API](/docs/extension/api/overview).
|
||||
|
||||
<Cards>
|
||||
<Card title="Using service workers" href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers" description="developer.mozilla.org" />
|
||||
|
||||
<Card title="Migrate to a service worker" href="https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers" description="developer.chrome.com" />
|
||||
|
||||
<Card title="Extension service worker basics" href="https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/basics" description="developer.chrome.com" />
|
||||
|
||||
<Card title="The extension service worker lifecycle" href="https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle" description="developer.chrome.com" />
|
||||
</Cards>
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
title: Content scripts
|
||||
description: Learn more about content scripts.
|
||||
url: /docs/extension/structure/content-scripts
|
||||
---
|
||||
|
||||
# Content scripts
|
||||
|
||||
Content scripts run in the context of web pages in an isolated world. This allows multiple content scripts from various extensions to coexist without conflicting with each other's execution and to stay isolated from the page's JavaScript.
|
||||
|
||||
A script that ends with `.ts` will not have front-end runtime (e.g. react) bundled with it and won't be treated as a ui script, while a script that ends in `.tsx` will be.
|
||||
|
||||
There are many use cases for content scripts:
|
||||
|
||||
* Injecting a custom stylesheet into the page
|
||||
* Scraping data from the current web page
|
||||
* Selecting, finding, and styling elements from the current web page
|
||||
* Injecting UI elements into current web page
|
||||
|
||||
Code for the content scripts is located in `src/app/content` directory - you need to define `.ts` or `.tsx` file inside and use `defineContentScript` to allow WXT to include your script in the build.
|
||||
|
||||
```ts title="src/app/content/index.ts"
|
||||
export default defineContentScript({
|
||||
matches: ["<all_urls>"],
|
||||
async main(ctx) {
|
||||
console.log(
|
||||
"Content script is running! Edit `app/content` and save to reload.",
|
||||
);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Reload your extension, open a web page, then open its inspector:
|
||||
|
||||

|
||||
To learn more about content scripts, e.g. how to configure only specific pages to load content scripts, how to inject them into `window` object or how to fetch data inside, please check [the official documentation](https://wxt.dev/guide/essentials/content-scripts.html).
|
||||
|
||||
## UI scripts
|
||||
|
||||
WXT has first-class support for mounting React components into the current webpage. This feature is called content scripts UI (CSUI).
|
||||
|
||||

|
||||
|
||||
An extension can have as many CSUI as needed, with each CSUI targeting a group of webpages or a specific webpage.
|
||||
|
||||
To get started with CSUI, create a `.tsx` file in `src/app/content` directory and use `defineContentScript` allow WXT to include your script in the build and mount your component into the current webpage:
|
||||
|
||||
```tsx title="src/app/content/index.tsx"
|
||||
const ContentScriptUI = () => {
|
||||
return (
|
||||
<Button onClick={() => alert("This is injected UI!")}>
|
||||
Content script UI
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ["<all_urls>"],
|
||||
cssInjectionMode: "ui",
|
||||
async main(ctx) {
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
name: "turbostarter-extension",
|
||||
position: "overlay",
|
||||
anchor: "body",
|
||||
onMount: (container) => {
|
||||
const app = document.createElement("div");
|
||||
container.append(app);
|
||||
|
||||
const root = ReactDOM.createRoot(app);
|
||||
root.render(<ContentScriptUI />);
|
||||
return root;
|
||||
},
|
||||
onRemove: (root) => {
|
||||
root?.unmount();
|
||||
},
|
||||
});
|
||||
|
||||
ui.mount();
|
||||
},
|
||||
});
|
||||
export default ContentScriptUI;
|
||||
```
|
||||
|
||||
<Callout title="File extensions matters!" type="warn">
|
||||
The `.tsx` extension is essential to differentiate between Content Scripts UI and regular Content Scripts. Make sure to check if you're using appropriate type of content script for your use case.
|
||||
</Callout>
|
||||
|
||||
To learn more about content scripts UI, e.g. how to inject custom styles, fonts or the whole lifecycle of a component, please check [the official documentation](https://wxt.dev/guide/essentials/content-scripts.html#ui).
|
||||
|
||||
<Callout title="How does it work?">
|
||||
Under the hood, the component is wrapped inside the component that implements the Shadow DOM technique, together with many helpful features. This isolation technique prevents the web page's style from affecting your component's styling and vice-versa.
|
||||
|
||||
[Read more about the lifecycle of CSUI](https://docs.plasmo.com/framework/content-scripts-ui/life-cycle)
|
||||
</Callout>
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: Messaging
|
||||
description: Communicate between your extension's components.
|
||||
url: /docs/extension/structure/messaging
|
||||
---
|
||||
|
||||
# Messaging
|
||||
|
||||
Messaging API makes communication between different parts of your extension easy. To make it simple and scalable, we're leveraging `@webext-core/messaging` library.
|
||||
|
||||
It provides a declarative, type-safe, functional, promise-based API for sending, relaying, and receiving messages between your extension components.
|
||||
|
||||
## Handling messages
|
||||
|
||||
Based on our convention, we implemented a little abstraction on top of `@webext-core/messaging` to make it easier to use. That's why all types and keys are stored inside `lib/messaging` directory:
|
||||
|
||||
```ts title="lib/messaging/index.ts"
|
||||
import { defineExtensionMessaging } from "@webext-core/messaging";
|
||||
|
||||
export const Message = {
|
||||
HELLO: "hello",
|
||||
} as const;
|
||||
|
||||
export type Message = (typeof Message)[keyof typeof Message];
|
||||
|
||||
interface Messages {
|
||||
[Message.HELLO]: (message: string) => string;
|
||||
}
|
||||
|
||||
export const { onMessage, sendMessage } = defineExtensionMessaging<Messages>();
|
||||
```
|
||||
|
||||
There you need to define what will be handled under each key. To make it more secure, only `Message` enum and `onMessage` and `sendMessage` functions are exported from the module.
|
||||
|
||||
All message handlers are located in `src/app/background/messaging` directory under respective subdirectories.
|
||||
|
||||
To create a message handler, create a TypeScript module in the `background/messaging` directory. Then, include your handlers for all keys related to the message:
|
||||
|
||||
```ts title="app/background/messaging/hello.ts"
|
||||
import { onMessage, Message } from "~/lib/messaging";
|
||||
|
||||
onMessage(Message.HELLO, (req) => {
|
||||
const result = await querySomeApi(req.body.id);
|
||||
|
||||
return result;
|
||||
});
|
||||
```
|
||||
|
||||
<Callout title="Don't forget to import!" type="warn">
|
||||
To make your handlers available across your extension, you need to import them
|
||||
in the `background/index.ts` file. That way they could be interpreted by the
|
||||
build process facilitated by WXT.
|
||||
</Callout>
|
||||
|
||||
## Sending messages
|
||||
|
||||
Extension pages, content scripts, or tab pages can send messages to the handlers using the `sendMessage` function. Since we orchestrate your handlers behind the scenes, the message names are typed and will enable autocompletion in your editor:
|
||||
|
||||
```tsx title="app/popup/index.tsx"
|
||||
import { sendMessage, Message } from "~/lib/messaging";
|
||||
|
||||
...
|
||||
|
||||
const response = await sendMessage(Message.HELLO, "Hello, world!");
|
||||
|
||||
console.log(response);
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
As it's an asynchronous operation, it's advisable to use [@tanstack/react-query](https://tanstack.com/query/latest/docs/framework/react/overview) integration to handle the response on the client side.
|
||||
|
||||
We're already doing it that way when fetching auth session in the `User` component:
|
||||
|
||||
```tsx title="hello.tsx"
|
||||
export const Hello = () => {
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: [Message.HELLO],
|
||||
queryFn: () => sendMessage(Message.HELLO, "Hello, world!"),
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
/* do something with the data... */
|
||||
return <p>{data?.message}</p>;
|
||||
};
|
||||
```
|
||||
|
||||
<Cards>
|
||||
<Card href="https://webext-core.aklinker1.io/messaging/installation/" title="Messaging API" description="webext-core.aklinker1.io" />
|
||||
|
||||
<Card title="Message passing" description="developer.chrome.com" href="https://developer.chrome.com/docs/extensions/develop/concepts/messaging" />
|
||||
</Cards>
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Learn about the structure of the extension app.
|
||||
url: /docs/extension/structure/overview
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
Every browser extension is different and can include different parts, removing the ones that are not needed.
|
||||
|
||||
TurboStarter ships with all the things you need to start developing your own extension including:
|
||||
|
||||
* **Popup window** - a small window that appears when the user clicks the extension icon.
|
||||
* **Options page** - a page that appears when user enters extension settings.
|
||||
* **Side panel** - a panel that appears when the user clicks sidepanel.
|
||||
* **New tab page** - a page that appears when the user opens a new tab.
|
||||
* **Devtools page** - a page that appears when the user opens the browser's devtools.
|
||||
* **Tab pages** - custom pages shipped with the extension.
|
||||
* **Content scripts** - injected scripts that run in the browser page.
|
||||
* **Background scripts** - scripts that run in the background.
|
||||
* **Message passing** - a way to communicate between different parts of the extension.
|
||||
* **Storage** - a way to store data in the extension.
|
||||
|
||||
All the entrypoints are defined in `apps/extension/src/app` directory (it's similar to file-based routing in Next.js and Expo).
|
||||
|
||||
This directory acts as a source for WXT framework which is used to build the extension. It has the following structure:
|
||||
|
||||
<Files>
|
||||
<Folder name="app" defaultOpen>
|
||||
<Folder name="background - Background service worker" />
|
||||
|
||||
<Folder name="content - Content scripts" />
|
||||
|
||||
<Folder name="devtools - Devtools page with custom panels" />
|
||||
|
||||
<Folder name="newtab - New tab page" />
|
||||
|
||||
<Folder name="options - Options page" />
|
||||
|
||||
<Folder name="popup - Popup window" />
|
||||
|
||||
<Folder name="sidepanel - Side panel" />
|
||||
|
||||
<Folder name="tabs - Custom pages shipped with the extension" />
|
||||
</Folder>
|
||||
</Files>
|
||||
|
||||
By structurizing it this way, we can easily add new entrypoints in the future and extend rest of the extension independently from each other.
|
||||
|
||||
We'll go through each part and explain the purpose of it, check following sections for more details:
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: Pages
|
||||
description: Get started with your extension's pages.
|
||||
url: /docs/extension/structure/pages
|
||||
---
|
||||
|
||||
# Pages
|
||||
|
||||
Extension pages are built-in pages recognized by the browser. They include the extension's popup, options, sidepanel and newtab pages.
|
||||
|
||||
<Callout>
|
||||
As WXT is based on Vite, it has very powerful [HMR support](https://vite.dev/guide/features#hot-module-replacement). This means that you don't need to refresh the extension manually when you make changes to the code.
|
||||
</Callout>
|
||||
|
||||
## Popup
|
||||
|
||||
The popup page is a small dialog window that opens when a user clicks on the extension's icon in the browser toolbar. It is the most common type of extension page.
|
||||
|
||||

|
||||
|
||||
<Cards>
|
||||
<Card title="Add a popup" href="https://developer.chrome.com/docs/extensions/develop/ui/add-popup" description="developer.chrome.com" />
|
||||
|
||||
<Card title="Entrypoints" href="https://wxt.dev/guide/essentials/entrypoints.html" description="wxt.dev" />
|
||||
</Cards>
|
||||
|
||||
## Options
|
||||
|
||||
The options page is meant to be a dedicated place for the extension's settings and configuration.
|
||||
|
||||

|
||||
|
||||
<Card title="Give users options" href="https://developer.chrome.com/docs/extensions/develop/ui/options-page" description="developer.chrome.com" />
|
||||
|
||||
## Devtools
|
||||
|
||||
The devtools page is a custom page (including panels) that opens when a user opens the extension's devtools panel.
|
||||
|
||||

|
||||
|
||||
<Card title="Extend devtools" href="https://developer.chrome.com/docs/extensions/how-to/devtools/extend-devtools" description="developer.chrome.com" />
|
||||
|
||||
## New tab
|
||||
|
||||
The new tab page is a custom page that opens when a user opens a new tab in the browser.
|
||||
|
||||

|
||||
|
||||
<Card title="Override Chrome pages" href="https://developer.chrome.com/docs/extensions/develop/ui/override-chrome-pages" description="developer.chrome.com" />
|
||||
|
||||
## Side panel
|
||||
|
||||
The side panel is a custom page that opens when a user clicks on the extension's icon in the browser toolbar.
|
||||
|
||||

|
||||
|
||||
<Card title="Side panel" href="https://developer.chrome.com/docs/extensions/reference/api/sidePanel" description="developer.chrome.com" />
|
||||
|
||||
## Tabs
|
||||
|
||||
Unlike traditional extension pages, tab (unlisted) pages are just regular web pages shipped with your extension bundle. Extensions generally redirect to or open these pages programmatically, but you can link to them as well.
|
||||
|
||||
They could be useful for following cases:
|
||||
|
||||
* when you want to show a some page when user first installs your extension
|
||||
* when you want to have dedicated pages for authentication
|
||||
* when you need more advanced routing setup
|
||||
|
||||

|
||||
|
||||
Your tab page will be available under the `/tabs` path in the extension bundle. It will be accessible from the browser under the URL:
|
||||
|
||||
```
|
||||
chrome-extension://<your-extension-id>/tabs/your-tab-page.html
|
||||
```
|
||||
|
||||
<Card title="Unlisted pages" href="https://wxt.dev/guide/essentials/entrypoints.html#unlisted-pages" description="wxt.dev" />
|
||||
@@ -0,0 +1,109 @@
|
||||
---
|
||||
title: Storage
|
||||
description: Learn how to store data in your extension.
|
||||
url: /docs/extension/structure/storage
|
||||
---
|
||||
|
||||
# Storage
|
||||
|
||||
TurboStarter leverages `wxt/storage` library to handle persistent storage for your extension. It's a utility library from that abstracts the persistent storage API available to browser extensions.
|
||||
|
||||
It falls back to localStorage when the extension storage API is unavailable, allowing for state sync between extension pages, content scripts, background service workers and web pages.
|
||||
|
||||
<Callout>
|
||||
To use the `wxt/storage` API, the "storage" permission **must** be added to the manifest:
|
||||
|
||||
```ts title="wxt.config.ts"
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ["storage"],
|
||||
},
|
||||
});
|
||||
```
|
||||
</Callout>
|
||||
|
||||
## Storing data
|
||||
|
||||
The base Storage API is designed to be easy to use. It is usable in every extension runtime such as background service workers, content scripts and extension pages.
|
||||
|
||||
TurboStarter ships with predefined storage used to handle [theming](/docs/extension/customization/styling) in your extension, but you can create your own storage as well.
|
||||
|
||||
All storage-related methods and types are located in `lib/storage` directory.
|
||||
|
||||
```ts title="lib/storage/index.ts"
|
||||
export const StorageKey = {
|
||||
THEME: "local:theme",
|
||||
} as const;
|
||||
|
||||
export type StorageKey = (typeof StorageKey)[keyof typeof StorageKey];
|
||||
```
|
||||
|
||||
Then, to make it available around your extension, we're setting it up and providing default values:
|
||||
|
||||
```ts title="lib/storage/index.ts"
|
||||
import { storage as browserStorage } from "wxt/storage";
|
||||
|
||||
import { appConfig } from "~/config/app";
|
||||
|
||||
import type { ThemeConfig } from "@turbostarter/ui";
|
||||
|
||||
const storage = {
|
||||
[StorageKey.THEME]: browserStorage.defineItem<ThemeConfig>(StorageKey.THEME, {
|
||||
fallback: appConfig.theme,
|
||||
}),
|
||||
} as const;
|
||||
```
|
||||
|
||||
To learn more about customizing your storage, syncing state or setup automatic backups please refer to the [official documentation](https://wxt.dev/storage.html).
|
||||
|
||||
## Consuming storage
|
||||
|
||||
To consume storage in your extension, you can use the `useStorage` React hook that is automatically provided to every part of the extension. The hook API is designed to streamline the state-syncing workflow between the different pieces of an extension.
|
||||
|
||||
Here is an example on how to consume our theme storage in `Layout` component:
|
||||
|
||||
```tsx title="modules/common/layout/layout.tsx"
|
||||
import { StorageKey, useStorage } from "~/lib/storage";
|
||||
|
||||
export const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||
const { data } = useStorage(StorageKey.THEME);
|
||||
|
||||
return (
|
||||
<div id="root" data-theme={data.color}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
Congrats! You've just learned how to persist and consume global data in your extension 🎉
|
||||
|
||||
For more advanced use cases, please refer to the [official documentation](https://wxt.dev/storage.html).
|
||||
|
||||
### Usage with Firefox
|
||||
|
||||
To use the storage API on Firefox during development you need to add an addon ID to your manifest, otherwise, you will get this error:
|
||||
|
||||
> Error: The storage API will not work with a temporary addon ID. Please add an explicit addon ID to your manifest. For more information see [https://mzl.la/3lPk1aE](https://mzl.la/3lPk1aE)
|
||||
|
||||
To add an addon ID to your manifest, add this to your package.json:
|
||||
|
||||
```ts title="wxt.config.ts"
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
browser_specific_settings: {
|
||||
gecko: {
|
||||
id: "your-id@example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
During development, you may use any ID. If you have published your extension, you need to use the ID assigned by [Firefox Add-ons](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons).
|
||||
|
||||
<Cards>
|
||||
<Card title="Storage API" href="https://wxt.dev/storage.html" description="wxt.dev" />
|
||||
|
||||
<Card title="chrome.storage" href="https://developer.chrome.com/docs/extensions/reference/api/storage" description="developer.chrome.com" />
|
||||
</Cards>
|
||||
Reference in New Issue
Block a user