Compare commits

...

18 Commits

Author SHA1 Message Date
GitHub Actions 6aa827438c chore(release): v0.3.2 2023-08-13 17:58:07 +00:00
Aaron 6ee8a43677 docs: Generate markdown for config reference (#74) 2023-08-13 12:51:19 -05:00
Aaron Klinker d54d6111e6 feat: Don't allow auto-importing from subdirectories
This reverts commit 547fee0e0e.

BREAKING CHANGE: 547fee previously added auto imports from subdirectories, but Nuxt does not do this, so to be consistent with Nuxt's DX, I'm also removing these auto-imports.
2023-08-13 10:13:42 -05:00
Aaron aefc8d3167 feat: Discover icons from the public directory (#72) 2023-08-13 10:05:27 -05:00
Aaron 7ccff533ff chore: Improve file list output in CI (#73) 2023-08-13 09:51:22 -05:00
Aaron Klinker edfa075030 ci: Validate templates using tarball to avoid version conflicts within the wxt/node_modules directory 2023-08-13 08:33:26 -05:00
Aaron Klinker ef140dc7f8 ci: List vite version when validating project templates 2023-08-13 08:33:26 -05:00
Aaron Klinker 39467d10f3 docs: Fix capitalization 2023-08-13 08:33:26 -05:00
Aaron Klinker 97f0938c99 docs: Fix typos 2023-08-12 09:21:40 -05:00
Aaron Klinker 18741eae8e Add remote code page to sidebar 2023-07-30 00:06:09 -05:00
Aaron Klinker 3f3ef37c96 Cleanup nuxt similarities 2023-07-30 00:05:14 -05:00
Aaron Klinker 323045a7c3 docs: Document the url: import prefix for remote code 2023-07-29 23:33:28 -05:00
Aaron 9b34180467 ci: Validate templates against main (#66) 2023-07-29 23:21:13 -05:00
Aaron Klinker 7f15305037 chore: Update templates to work with CSS entrypoints 2023-07-29 23:08:42 -05:00
Aaron Klinker 35cc1c94bf Update contributor links in changelog 2023-07-29 22:57:45 -05:00
Aaron Klinker 547c1850ac chore: Upgrade vite (v4.3 → v4.4) 2023-07-29 22:50:08 -05:00
Aaron Klinker 798f02f626 chore: Upgrade dependencies 2023-07-29 22:49:00 -05:00
Aaron Klinker d5948abfe5 Update changelog 2023-07-29 22:39:42 -05:00
36 changed files with 883 additions and 263 deletions
+1
View File
@@ -1,2 +1,3 @@
* text=auto eol=lf
pnpm-lock.yaml linguist-generated
docs/config.md linguist-generated
+13
View File
@@ -72,10 +72,15 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: Build Local Tarball
run: pnpm pack
- name: Validate Vanilla
working-directory: templates/vanilla
run: |
npm i
npm i -D ../../wxt-*.tgz
npm ls vite
npm run build
npm run compile
@@ -83,6 +88,8 @@ jobs:
working-directory: templates/vue
run: |
npm i
npm i -D ../../wxt-*.tgz
npm ls vite
npm run build
npm run compile
@@ -90,6 +97,8 @@ jobs:
working-directory: templates/react
run: |
npm i
npm i -D ../../wxt-*.tgz
npm ls vite
npm run build
npm run compile
@@ -97,6 +106,8 @@ jobs:
working-directory: templates/svelte
run: |
npm i
npm i -D ../../wxt-*.tgz
npm ls vite
npm run build
npm run check
@@ -104,5 +115,7 @@ jobs:
working-directory: templates/solid
run: |
npm i
npm i -D ../../wxt-*.tgz
npm ls vite
npm run build
npm run compile
+52 -20
View File
@@ -1,5 +1,39 @@
# Changelog
## v0.3.2
[compare changes](https://github.com/aklinker1/wxt/compare/v0.3.1...v0.3.2)
### 🚀 Enhancements
- Discover icons from the public directory ([#72](https://github.com/aklinker1/wxt/pull/72))
- Don't allow auto-importing from subdirectories ([d54d611](https://github.com/aklinker1/wxt/commit/d54d611))
### 📖 Documentation
- Document the `url:` import prefix for remote code ([323045a](https://github.com/aklinker1/wxt/commit/323045a))
- Fix typos ([97f0938](https://github.com/aklinker1/wxt/commit/97f0938))
- Fix capitalization ([39467d1](https://github.com/aklinker1/wxt/commit/39467d1))
- Generate markdown for config reference ([#74](https://github.com/aklinker1/wxt/pull/74))
### 🏡 Chore
- Upgrade dependencies ([798f02f](https://github.com/aklinker1/wxt/commit/798f02f))
- Upgrade vite (`v4.3` → `v4.4`) ([547c185](https://github.com/aklinker1/wxt/commit/547c185))
- Update templates to work with CSS entrypoints ([7f15305](https://github.com/aklinker1/wxt/commit/7f15305))
- Improve file list output in CI ([#73](https://github.com/aklinker1/wxt/pull/73))
### 🤖 CI
- Validate templates against `main` ([#66](https://github.com/aklinker1/wxt/pull/66))
- List vite version when validating project templates ([ef140dc](https://github.com/aklinker1/wxt/commit/ef140dc))
- Validate templates using tarball to avoid version conflicts within the `wxt/node_modules` directory ([edfa075](https://github.com/aklinker1/wxt/commit/edfa075))
### ❤️ Contributors
- Aaron <aaronklinker1@gmail.com>
- Aaron Klinker <aaronklinker1@gmail.com>
## v0.3.1
[compare changes](https://github.com/aklinker1/wxt/compare/v0.3.0...v0.3.1)
@@ -33,9 +67,8 @@
### ❤️ Contributors
- Aaron Klinker <aaronklinker1@gmail.com>
- Aaron
- BeanWei <bean0wei@dingtalk.com>
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
- BeanWei ([@BeanWei](https://github.com/BeanWei))
## v0.3.0
@@ -70,7 +103,7 @@
### ❤️ Contributors
- Aaron Klinker
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.2.5
@@ -96,7 +129,7 @@
### ❤️ Contributors
- Aaron Klinker
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.2.4
@@ -108,7 +141,7 @@
### ❤️ Contributors
- Aaron
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.2.3
@@ -122,7 +155,7 @@
### ❤️ Contributors
- Aaron Klinker
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.2.2
@@ -134,7 +167,7 @@
### ❤️ Contributors
- Aaron Klinker
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.2.1
@@ -150,7 +183,7 @@
### ❤️ Contributors
- Aaron Klinker
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.2.0
@@ -178,7 +211,7 @@
### ❤️ Contributors
- Aaron Klinker
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.1.6
@@ -196,8 +229,7 @@
### ❤️ Contributors
- Aaron Klinker
- Aaron
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.1.5
@@ -209,7 +241,7 @@
### ❤️ Contributors
- Aaron Klinker
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.1.4
@@ -230,7 +262,7 @@
### ❤️ Contributors
- Aaron Klinker
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.1.3
@@ -255,7 +287,7 @@
### ❤️ Contributors
- Aaron Klinker
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.1.2
@@ -277,7 +309,7 @@
### ❤️ Contributors
- Aaron Klinker
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.1.1
@@ -290,7 +322,7 @@
### ❤️ Contributors
- Aaron Klinker
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.1.0
@@ -317,7 +349,7 @@ Initial release of WXT. Full support for production builds and initial toolkit f
### ❤️ Contributors
- Aaron Klinker
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.0.2
@@ -351,7 +383,7 @@ Initial release of WXT. Full support for production builds and initial toolkit f
### ❤️ Contributors
- Aaron Klinker
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
## v0.0.1
@@ -425,4 +457,4 @@ Initial release of WXT. Full support for production builds and initial toolkit f
### ❤️ Contributors
- Aaron Klinker
- Aaron Klinker ([@aklinker1](https://github.com/aklinker1))
+6
View File
@@ -1,8 +1,13 @@
import { defineConfig } from 'vitepress';
import { generateConfigDocs } from './plugins/generate-config-docs';
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: 'WXT',
vite: {
clearScreen: false,
plugins: [generateConfigDocs()],
},
description: 'Next gen framework for developing web extensions',
lastUpdated: true,
@@ -47,6 +52,7 @@ export default defineConfig({
{ text: 'Auto-imports', link: '/guide/auto-imports.md' },
{ text: 'Manifest.json', link: '/guide/manifest.md' },
{ text: 'Extension APIs', link: '/guide/extension-apis.md' },
{ text: 'Remote Code', link: '/guide/remote-code.md' },
],
},
{
@@ -0,0 +1,148 @@
import { resolve } from 'node:path';
import { Project, ts, Type, Node, JSDocableNode } from 'ts-morph';
import Ora from 'ora';
import { readFileSync, writeFileSync } from 'node:fs';
const externalTypesPath = resolve('src/core/types/external.ts');
const configTemplatePath = resolve('docs/config.tpl.md');
const configPath = resolve('docs/config.md');
const PREFACE = `<!--
DO NOT EDIT
Generated by \`wxt/docs/.vitepress/plugins/generate-config-docs.ts\`
To make changes to the config reference, update the JSDoc in \`src/core/types/external.ts\`.
-->`;
/**
* Custom property paths that should not be recursively inspected. Usually 3rd party types.
*/
const LEAF_PATHS = ['imports', 'vite', 'server'];
/**
* Override any types that resolve to `import(...)` instead of their type names when calling
* `type.getText()`
*/
const CUSTOM_TYPES = {
manifest:
'Manifest | Promise<Manifest> | () => Manifest | () => Promise<Manifest>',
};
export function generateConfigDocs() {
const generateDocs = () => {
const spinner = Ora('Generating /config.md').start();
try {
const project = new Project({
tsConfigFilePath: resolve('tsconfig.json'),
});
// Load file containing "UserConfig"
const externalTypesFile = project.addSourceFileAtPath(externalTypesPath);
project.resolveSourceFileDependencies();
const typeChecker = project.getProgram().getTypeChecker();
const inlineConfigInterface =
externalTypesFile.getInterfaceOrThrow('InlineConfig');
const getDocsFor = (
path: string[],
node: Node<ts.Node>,
depth = 0,
): string[] => {
if (depth > 3) throw Error('Recursion to deep for ' + path.join('.'));
const pathStr = path.join('.');
let type: Type<ts.Type>;
if (node.isKind(ts.SyntaxKind.InterfaceDeclaration)) {
type = node.getType();
} else if (node.isKind(ts.SyntaxKind.PropertySignature)) {
type = node.getTypeNodeOrThrow()?.getType();
} else if (node.isKind(ts.SyntaxKind.MethodSignature)) {
type = node.getType();
} else {
throw Error('Unsupported type node: ' + node.getKindName());
}
if (type.isObject() && !type.isArray()) {
return (
type
.getProperties()
// .sort((l, r) => l.getName().localeCompare(r.getName()))
.flatMap((property) => {
const childPath = [...path, property.getName()];
if (LEAF_PATHS.includes(childPath.join('.'))) return [];
return getDocsFor(
childPath,
property.getDeclarations()[0],
depth + 1,
);
})
);
}
if ('getJsDocs' in node) {
const lines: string[] = [];
const docs = (node as unknown as JSDocableNode).getJsDocs();
let typeText: string;
if (CUSTOM_TYPES[pathStr]) {
typeText = CUSTOM_TYPES[pathStr];
} else if (type.isUnion() && !type.isBoolean()) {
typeText = type
.getUnionTypes()
.map((type) => type.getText())
.join(' | ');
} else {
typeText = type.getText();
}
const defaultValue = docs
.flatMap((doc) => doc.getTags())
.find((tag) => tag.getTagName() === 'default')
?.getCommentText();
lines.push(
'',
`## ${pathStr}`,
'',
`- **Type**: \`${typeText}\``,
`- **Default**: \`${defaultValue}\``,
...docs.flatMap((doc) => doc.getDescription()),
);
return lines;
}
return [];
};
const lines = getDocsFor([], inlineConfigInterface);
const text =
PREFACE +
'\n\n' +
readFileSync(configTemplatePath, 'utf-8').replace(
'{{ DOCS }}',
lines.join('\n'),
);
writeFileSync(configPath, text);
spinner.succeed('Generated /config.md');
} catch (err) {
spinner.fail('Failed to generate /config.md');
console.error(err.message);
}
};
return {
name: 'docs:generate-config-docs',
buildStart() {
generateDocs();
},
configureServer(server: any) {
server.watcher.add(externalTypesPath);
},
handleHotUpdate(ctx: { file: string }) {
if ([externalTypesPath, configTemplatePath].includes(ctx.file)) {
generateDocs();
}
},
};
}
+199 -4
View File
@@ -1,5 +1,200 @@
# Config
<!--
DO NOT EDIT
Generated by `wxt/docs/.vitepress/plugins/generate-config-docs.ts`
To make changes to the config reference, update the JSDoc in `src/core/types/external.ts`.
-->
:::warning 🚧&ensp;Under construction
This documentation does not exist yet.
:::
# Config Reference
Discover all the options you can use in your `wxt.config.ts` file.
## root
- **Type**: `string`
- **Default**: `process.cwd()`
Your project's root directory containing the `package.json` used to fill out the
`manifest.json`.
## srcDir
- **Type**: `string`
- **Default**: `config.root`
Directory containing all source code. Set to `"src"` to move all source code to a `src/`
directory.
## publicDir
- **Type**: `string`
- **Default**: `"${config.root}/public"`
Directory containing files that will be copied to the output directory as-is.
## entrypointsDir
- **Type**: `string`
- **Default**: `"${config.srcDir}/entrypoints"`
## configFile
- **Type**: `string | false`
- **Default**: `"wxt.config.ts"`
Path to `"wxt.config.ts"` file or false to disable config file discovery.
## storeIds.chrome
- **Type**: `string`
- **Default**: `undefined`
## storeIds.firefox
- **Type**: `string`
- **Default**: `undefined`
## storeIds.edge
- **Type**: `string`
- **Default**: `undefined`
## mode
- **Type**: `string`
- **Default**: `undefined`
Explicitly set a mode to run in. This will override the default mode for each command, and can
be overridden by the command line `--mode` option.
## browser
- **Type**: `"chrome" | "firefox" | "safari" | "edge" | "opera"`
- **Default**: `"chrome"`
Explicitly set a browser to build for. This will override the default browser for each command,
and can be overridden by the command line `--browser` option.
## manifestVersion
- **Type**: `2 | 3`
- **Default**: `undefined`
Explicitly set a manifest version to target. This will override the default manifest version
for each command, and can be overridden by the command line `--mv2` or `--mv3` option.
## manifest
- **Type**: `Manifest | Promise<Manifest> | () => Manifest | () => Promise<Manifest>`
- **Default**: `undefined`
Customize the `manifest.json` output. Can be an object, promise, or function that returns an
object or promise.
## runner.openConsole
- **Type**: `boolean`
- **Default**: `undefined`
## runner.openDevtools
- **Type**: `boolean`
- **Default**: `undefined`
## runner.binaries.chrome
- **Type**: `string`
- **Default**: `undefined`
## runner.binaries.edge
- **Type**: `string`
- **Default**: `undefined`
## runner.binaries.opera
- **Type**: `string`
- **Default**: `undefined`
## runner.binaries.firefox
- **Type**: `string`
- **Default**: `undefined`
## runner.firefoxProfile
- **Type**: `string`
- **Default**: `undefined`
## runner.chromiumProfile
- **Type**: `string`
- **Default**: `undefined`
## runner.firefoxArgs
- **Type**: `string[]`
- **Default**: `undefined`
## runner.chromiumArgs
- **Type**: `string[]`
- **Default**: `undefined`
## runner.startUrls
- **Type**: `string[]`
- **Default**: `undefined`
## zip.artifactTemplate
- **Type**: `string`
- **Default**: `"{name}-{version}-{browser}.zip"`
Configure the filename output when zipping files.
Available template variables:
- `{name}` - The project's name converted to kebab-case
- `{version}` - The version_name or version from the manifest
- `{browser}` - The target browser from the `--browser` CLI flag
- `{manifestVersion}` - Either "2" or "3"
## zip.sourcesTemplate
- **Type**: `string`
- **Default**: `"{name}-{version}-sources.zip"`
Configure the filename output when zipping files.
Available template variables:
- `{name}` - The project's name converted to kebab-case
- `{version}` - The version_name or version from the manifest
- `{browser}` - The target browser from the `--browser` CLI flag
- `{manifestVersion}` - Either "2" or "3"
## zip.name
- **Type**: `string`
- **Default**: `undefined`
Override the artifactTemplate's `{name}` template variable. Defaults to the `package.json`'s
name, or if that doesn't exist, the current working directories name.
## zip.sourcesRoot
- **Type**: `string`
- **Default**: `config.root`
Root directory to ZIP when generating the sources ZIP.
## zip.ignoredSources
- **Type**: `string[]`
- **Default**: `undefined`
[Minimatch](https://www.npmjs.com/package/minimatch) patterns of files to exclude when
creating a ZIP of all your source code for Firfox. Patterns are relative to your
`config.zip.sourcesRoot`.
Hidden files, node_modules, and tests are ignored by default.
+5
View File
@@ -0,0 +1,5 @@
# Config Reference
Discover all the options you can use in your `wxt.config.ts` file.
{{ DOCS }}
+1 -1
View File
@@ -2,7 +2,7 @@
WXT has two directories for storing assets like CSS, images, or fonts.
- `<srcDir>/public`: Store files that will be copied into the output directory as-is
- `<rootDir>/public`: Store files that will be copied into the output directory as-is
- `<srcDir>/assets`: Store files that will be processed by Vite during the build process
## `/public` Directory
+1 -1
View File
@@ -24,7 +24,7 @@ Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework),
| Opens browser and install extension | ✅ | ❌ |
| HMR for UIs | ✅ | 🟡 React only |
| Reload HTML Files on Change | ✅ | 🟡 Reloads entire extension |
| Reload Content Scripta on Change | ✅ | 🟡 Reloads entire extension |
| Reload Content Scripts on Change | ✅ | 🟡 Reloads entire extension |
| Reload Background on Change | 🟡 Reloads entire extension | 🟡 Reloads entire extension |
| <strong style="opacity: 50%">Built-in Utils</strong> | | |
| Storage | ❌ | ✅ |
+1 -1
View File
@@ -25,7 +25,7 @@ WXT allows you to edit several directories to your liking:
- `root` (default: `process.cwd()`) - Root of the WXT project
- `srcDir` (default: `<rootDir>`) - Location of all your source code
- `entrypointsDir` (default: `<srcDir>/entrypoints`) - Folder containing all the entrypoints.
- `publicDir` (default: `<srcDir>/public`) - Folder containing [public assets](/get-started/assets.md)
- `publicDir` (default: `<rootDir>/public`) - Folder containing [public assets](/get-started/assets.md)
### Example
+2 -2
View File
@@ -1,8 +1,8 @@
# Defining Entrypoints
An entrypoints is any HTML, JS, or CSS file that needs to be bundled and included with the extension.
An "entrypoint" is any HTML, JS, or CSS file that needs to be bundled and included with the extension.
They may or may not be listed in the extension's `manifest.json`.
Entrypoints may or may not be listed in the extension's `manifest.json`.
## `/entrypoints` Directory
+1 -1
View File
@@ -2,7 +2,7 @@
WXT is a free and open source framework for building web extensions in an conventional, intuative, and safe way **_for all browsers_**.
WXT comes with full TypeScript support and auto-imports. Sounds familiar? That's right, **_WXT was based off of Nuxt_** and aims to provide the same greate DX and features.
WXT is based of [Nuxt](https://nuxt.com), and aims to provide the same great DX with TypeScript, auto-imports, and an opinionated project structure.
![Example build output](../assets/cli-output.png)
+13 -4
View File
@@ -20,10 +20,19 @@ And more. All [`wxt/client`](/config.md#wxtclient) APIs can be used without impo
In addition WXT APIs, default and named exports from inside the following directories can be used without listing them in imports.
- `<srcDir>/components/**/*`
- `<srcDir>/composables/**/*`
- `<srcDir>/hooks/**/*`
- `<srcDir>/utils/**/*`
- `<srcDir>/components/*`
- `<srcDir>/composables/*`
- `<srcDir>/hooks/*`
- `<srcDir>/utils/*`
To add auto-imports from subdirectories, like `utils/api/some-file.ts`, re-export them from the base directory:
```ts
// utils/index.ts
export * from './api/some-file.ts';
```
Alternatively, you could add the directory to the list of auto-import directories in your config file.
## TypeScript
+5 -5
View File
@@ -2,16 +2,16 @@
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/content_scripts/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts)
When creating content script entrypoints, they are automatically included in the `manifest.json` along with any CSS they import.
When creating content script entrypoints, they are automatically included in the `manifest.json` along with any CSS files they import.
## Filenames
<EntrypointPatterns
:patterns="[
['content.tsx?', 'content-scripts/content.js'],
['content/index.tsx?', 'content-scripts/content.js'],
['<name>.content.tsx?', 'content-scripts/<name>.js'],
['<name>.content/index.tsx?', 'content-scripts/<name>.js'],
['content.(ts|tsx)', 'content-scripts/content.js'],
['content/index.(ts|tsx)', 'content-scripts/content.js'],
['<name>.content.(ts|tsx)', 'content-scripts/<name>.js'],
['<name>.content/index.(ts|tsx)', 'content-scripts/<name>.js'],
]"
/>
+10 -8
View File
@@ -14,9 +14,11 @@ And that's it! Your extension now supports Chrome, Firefox, Safari, Edge, and ot
The `browser` variable is available globally via [auto-imports](/guide/auto-imports.md), or it can be imported manually.
```ts
import browser from 'webextension-polyfill';
import browser from 'wxt/browser';
```
The `wxt/browser` module exports a customized version of `webextension-polyfill`'s browser with improved typing.
### Example
Let's save the date the extension was installed. Just like `chrome`, some APIs require the permission is added to your manifest before the API is defined. Here, we need to add the `storage` permission to your manifest.
@@ -51,6 +53,13 @@ Follow [Chrome's message passing guide](https://developer.chrome.com/docs/extens
Here's a basic request/response example:
```ts
// popup/main.ts
const res = await browser.runtime.sendMessage('ping');
console.log('res'); // "pong"
```
```ts
// background.ts
export default defineBackground(() => {
@@ -66,13 +75,6 @@ export default defineBackground(() => {
});
```
```ts
// popup/main.ts
const res = await browser.runtime.sendMessage('ping');
console.log('res'); // "pong"
```
There are a number of message passing libraries you can use to improve the message passing experience.
Here are some that are compatible with WXT (because they are based off `webextension-polyfill` as well):
+16 -10
View File
@@ -2,7 +2,7 @@
The manifest.json is generated at build-time based on files in your `entrypoints` directory and your `wxt.config.ts`.
## Customization
## Confiuration
While entrypoints are generated and added to the manifest at build-time, you can customize or add to your `manifest.json` in the config file.
@@ -47,9 +47,9 @@ The [manifest's `version` and `version_name`](https://developer.chrome.com/docs/
}
```
### `icons`
## `icons`
The [manifest's `icons`](https://developer.chrome.com/docs/extensions/mv3/manifest/icons/) property needs to be set in the config file. The files should be added to WXT's [`public` directory](/get-started/assets#public-directory).
By default, WXT will discover icons in your [`public` directory](/get-started/assets#public-directory) and use them for the [manifest's `icons`](https://developer.chrome.com/docs/extensions/mv3/manifest/icons/).
```
public/
@@ -60,21 +60,27 @@ public/
└─ icon-128.png
```
Icon files need to match the following regex to be automatically included in the manifest. Most design software can output icons in one of these formats
<<< @/../src/core/utils/manifest.ts#snippet
If you prefer to use filenames in a different format, you can add the icons manually in your `wxt.config.ts` file:
```ts
export default defineConfig({
manifest: {
icons: {
16: '/icon-16.png',
24: '/icon-24.png',
48: '/icon-48.png',
96: '/icon-96.png',
128: '/icon-128.png',
16: '/extension-icon-16.png',
24: '/extension-icon-24.png',
48: '/extension-icon-48.png',
96: '/extension-icon-96.png',
128: '/extension-icon-128.png',
},
},
});
```
### Permissions
## `permissions`
[Permissions](https://developer.chrome.com/docs/extensions/reference/permissions/) must be listed in the manifest config.
@@ -86,7 +92,7 @@ export default defineConfig({
});
```
### Localization
## Localization
Similar to the icon, the [`_locales` directory](https://developer.chrome.com/docs/extensions/reference/i18n/) should be placed inside the the WXT's [`public` directory](/get-started/assets#public-directory).
-2
View File
@@ -13,8 +13,6 @@
## Definition
Plain old HTML file.
```html
<!DOCTYPE html>
<html lang="en">
-2
View File
@@ -13,8 +13,6 @@
## Definition
Plain old HTML file.
```html
<!DOCTYPE html>
<html lang="en">
+31
View File
@@ -0,0 +1,31 @@
# Remote Code
WXT will automatically download and bundle imports with the `url:` prefix so the extension does not depend of remote code, [a requirement from Google for MV3](https://developer.chrome.com/docs/extensions/migrating/improve-security/#remove-remote-code).
## Google Analytics
For example, you can import google analytics:
```ts
// utils/google-analytics.ts
import 'url:https://www.googletagmanager.com/gtag/js?id=G-XXXXXX';
window.dataLayer = window.dataLayer || [];
// NOTE: This line is different from Google's documentation
window.gtag = function () {
dataLayer.push(arguments);
};
gtag('js', new Date());
gtag('config', 'G-XXXXXX');
```
Then you can import this in your HTML files to enable Google Analytics:
```ts
// popup/main.ts
import '~/utils/google-analytics';
gtag('event', 'event_name', {
key: 'value',
});
```
+2 -2
View File
@@ -6,8 +6,8 @@ TypeScript files that are built, but are not included in the manifest.
<EntrypointPatterns
:patterns="[
['<name>.tsx?', '<name>.js'],
['<name>/index.tsx?', '<name>.js'],
['<name>.(ts|tsx)', '<name>.js'],
['<name>/index.(ts|tsx)', '<name>.js'],
]"
/>
+60
View File
@@ -133,6 +133,66 @@ describe('Manifest Content', () => {
});
});
describe('icons', () => {
it('should auto-discover icons with the correct name', async () => {
const project = new TestProject();
project.addFile('public/icon-16.png');
project.addFile('public/icon/32.jpeg');
project.addFile('public/icon@48w.jpg');
project.addFile('public/icon-64x64.gif');
project.addFile('public/icon@96.bmp');
project.addFile('public/icon/128x128.ico');
await project.build();
const manifest = await project.getOutputManifest();
expect(manifest.icons).toEqual({
'16': 'icon-16.png',
'32': 'icon/32.jpeg',
'48': 'icon@48w.jpg',
'64': 'icon-64x64.gif',
'96': 'icon@96.bmp',
'128': 'icon/128x128.ico',
});
});
it('should return undefined when no icons are found', async () => {
const project = new TestProject();
project.addFile('public/logo.png');
project.addFile('public/icon.jpeg');
await project.build();
const manifest = await project.getOutputManifest();
expect(manifest.icons).toBeUndefined();
});
it('should allow icons to be overwritten from the wxt.config.ts file', async () => {
const project = new TestProject();
project.addFile('public/icon-16.png');
project.addFile('public/icon-32.png');
project.addFile('public/logo-16.png');
project.addFile('public/logo-32.png');
project.addFile('public/logo-48.png');
const icons = {
'16': 'logo-16.png',
'32': 'logo-32.png',
'48': 'logo-48.png',
};
project.setConfigFileConfig({
manifest: {
icons,
},
});
await project.build();
const manifest = await project.getOutputManifest();
expect(manifest.icons).toEqual(icons);
});
});
it('should group content scripts and styles together based on their matches and run_at', async () => {
const project = new TestProject();
project.addFile(
+14 -13
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.3.1",
"version": "0.3.2",
"description": "Next gen framework for developing web extensions",
"engines": {
"node": ">=18.16.0",
@@ -63,26 +63,26 @@
"docs:preview": "vitepress preview docs"
},
"dependencies": {
"@types/webextension-polyfill": "^0.10.0",
"@types/webextension-polyfill": "^0.10.1",
"@webext-core/fake-browser": "^1.2.2",
"@webext-core/match-patterns": "^1.0.1",
"async-mutex": "^0.4.0",
"c12": "^1.4.2",
"cac": "^6.7.14",
"consola": "^3.1.0",
"fast-glob": "^3.2.12",
"filesize": "^10.0.7",
"consola": "^3.2.3",
"fast-glob": "^3.3.1",
"filesize": "^10.0.8",
"fs-extra": "^11.1.1",
"get-port": "^7.0.0",
"giget": "^1.1.2",
"jiti": "^1.18.2",
"jiti": "^1.19.1",
"json5": "^2.2.3",
"linkedom": "^0.14.26",
"minimatch": "^9.0.3",
"picocolors": "^1.0.0",
"prompts": "^2.4.2",
"unimport": "^3.0.8",
"vite": "^4.3.9",
"unimport": "^3.1.0",
"vite": "^4.4.7",
"vite-tsconfig-paths": "^4.2.0",
"web-ext": "^7.6.2",
"webextension-polyfill": "^0.10.0",
@@ -92,19 +92,20 @@
"@faker-js/faker": "^8.0.2",
"@types/fs-extra": "^11.0.1",
"@types/lodash.merge": "^4.6.7",
"@types/node": "^20.3.1",
"@types/node": "^20.4.5",
"@types/prompts": "^2.4.4",
"@vitest/coverage-v8": "^0.32.2",
"@vitest/coverage-v8": "^0.32.4",
"execa": "^7.2.0",
"lodash.merge": "^4.6.2",
"npm-run-all": "^4.1.5",
"ora": "^6.3.1",
"prettier": "^2.8.8",
"pretty-quick": "^3.1.3",
"simple-git-hooks": "^2.8.1",
"tsup": "^7.0.0",
"simple-git-hooks": "^2.9.0",
"ts-morph": "^19.0.0",
"tsup": "^7.1.0",
"tsx": "^3.12.7",
"typescript": "^5.1.3",
"typescript": "^5.1.6",
"vitepress": "1.0.0-beta.5",
"vitest": "^0.32.4",
"vitest-mock-extended": "^1.1.4",
+211 -147
View File
File diff suppressed because it is too large Load Diff
+14 -4
View File
@@ -23,12 +23,14 @@ await Promise.all([
sourcemap: true,
dts: true,
silent: true,
external: ['vite'],
}),
tsup.build({
entry: { cli: 'src/cli/index.ts' },
format: ['cjs'],
sourcemap: 'inline',
silent: true,
external: ['vite'],
}),
tsup.build({
entry: { client: 'src/client/index.ts' },
@@ -36,6 +38,7 @@ await Promise.all([
sourcemap: 'inline',
dts: true,
silent: true,
external: ['vite'],
}),
tsup.build({
entry: { browser: 'src/client/browser.ts' },
@@ -43,6 +46,7 @@ await Promise.all([
sourcemap: 'inline',
dts: true,
silent: true,
external: ['vite'],
}),
...virtualEntrypoints.map((entryName) =>
tsup.build({
@@ -52,7 +56,7 @@ await Promise.all([
format: ['esm'],
sourcemap: true,
silent: true,
external: [`virtual:user-${entryName}`],
external: [`virtual:user-${entryName}`, 'vite'],
}),
),
tsup.build({
@@ -62,6 +66,7 @@ await Promise.all([
format: ['esm'],
sourcemap: true,
silent: true,
external: ['vite'],
}),
tsup.build({
entry: {
@@ -69,6 +74,7 @@ await Promise.all([
},
format: ['esm', 'cjs'],
silent: true,
external: ['vite'],
}),
]).catch((err) => {
spinner.fail();
@@ -76,9 +82,13 @@ await Promise.all([
process.exit(1);
});
spinner.succeed();
spinner.clear().stop();
const duration = Date.now() - startTime;
const outFiles = await glob(`${outDir}/**`, { absolute: true });
await printFileList(consola.log, outDir, outFiles);
consola.success(`Finished in ${formatDuration(duration)}`);
await printFileList(
consola.success,
`Built WXT in ${formatDuration(duration)}`,
outDir,
outFiles,
);
+4 -2
View File
@@ -42,10 +42,12 @@ export async function buildInternal(
const { output } = await rebuild(config, groups, undefined);
// Post-build
config.logger.success(
await printBuildSummary(
config.logger.success,
`Built extension in ${formatDuration(Date.now() - startTime)}`,
output,
config,
);
await printBuildSummary(output, config);
return output;
}
+3 -1
View File
@@ -3,6 +3,8 @@ import { BuildOutput, InternalConfig } from '../types';
import { printFileList } from './printFileList';
export async function printBuildSummary(
log: (...args: any[]) => void,
header: string,
output: BuildOutput,
config: InternalConfig,
) {
@@ -18,7 +20,7 @@ export async function printBuildSummary(
});
const files = chunks.map((chunk) => resolve(config.outDir, chunk.fileName));
await printFileList(config.logger.log, config.outDir, files);
await printFileList(log, header, config.outDir, files);
}
const DEFAULT_SORT_WEIGHT = 100;
+3 -2
View File
@@ -6,6 +6,7 @@ import { printTable } from './printTable';
export async function printFileList(
log: (message: string) => void,
header: string,
baseDir: string,
files: string[],
): Promise<void> {
@@ -29,9 +30,9 @@ export async function printFileList(
}),
);
printTable(log, fileRows);
fileRows.push([`${pc.cyan('Σ Total size:')} ${String(filesize(totalSize))}`]);
log(`${pc.cyan('Σ Total size:')} ${String(filesize(totalSize))}`);
printTable(log, header, fileRows);
}
const DEFAULT_COLOR = pc.blue;
+2 -1
View File
@@ -1,5 +1,6 @@
export function printTable(
log: (message: string) => void,
header: string,
rows: string[][],
gap = 2,
): void {
@@ -24,5 +25,5 @@ export function printTable(
if (i !== rows.length - 1) str += '\n';
});
log(str);
log(`${header}\n${str}`);
}
+22 -25
View File
@@ -5,37 +5,33 @@ import { EntrypointGroup } from '.';
export interface InlineConfig {
/**
* Project root directory.
* Your project's root directory containing the `package.json` used to fill out the
* `manifest.json`.
*
* @default
* process.cwd()
* @default process.cwd()
*/
root?: string;
/**
* Directory containing all source code. Set to `"src"` to move all source code to a `src/`
* directory.
*
* @default
* "<rootDir>"
* @default config.root
*/
srcDir?: string;
/**
* Directory containing files that will be copied to the output directory as-is.
*
* @default
* "<rootDir>/publicDir"
* @default "${config.root}/public"
*/
publicDir?: string;
/**
* @default
* "<srcDir>/entrypoints"
* @default "${config.srcDir}/entrypoints"
*/
entrypointsDir?: string;
/**
* Path to `"wxt.config.ts"` file or false to disable config file discovery.
*
* @default
* "wxt.config.ts"
* @default "wxt.config.ts"
*/
configFile?: string | false;
/**
@@ -56,7 +52,7 @@ export interface InlineConfig {
*/
imports?: Partial<UnimportOptions>;
/**
* Explicitly set a browser to target. This will override the default browser for each command,
* Explicitly set a browser to build for. This will override the default browser for each command,
* and can be overridden by the command line `--browser` option.
*
* @default
@@ -98,12 +94,12 @@ export interface InlineConfig {
*
* Available template variables:
*
* - `{{name}}` - The project's name converted to kebab-case
* - `{{version}} - The version_name or version from the manifest
* - `{{browser}} - The target browser from the `--browser` CLI flag
* - `{{manifestVersion}}` - Either "2" or "3"
* - `{name}` - The project's name converted to kebab-case
* - `{version}` - The version_name or version from the manifest
* - `{browser}` - The target browser from the `--browser` CLI flag
* - `{manifestVersion}` - Either "2" or "3"
*
* @default "{{name}}-{{version}}-{{browser}}.zip"
* @default "{name}-{version}-{browser}.zip"
*/
artifactTemplate?: string;
/**
@@ -111,22 +107,23 @@ export interface InlineConfig {
*
* Available template variables:
*
* - `{{name}}` - The project's name converted to kebab-case
* - `{{version}} - The version_name or version from the manifest
* - `{{browser}} - The target browser from the `--browser` CLI flag
* - `{{manifestVersion}}` - Either "2" or "3"
* - `{name}` - The project's name converted to kebab-case
* - `{version}` - The version_name or version from the manifest
* - `{browser}` - The target browser from the `--browser` CLI flag
* - `{manifestVersion}` - Either "2" or "3"
*
* @default "{{name}}-{{version}}-sources.zip"
* @default "{name}-{version}-sources.zip"
*/
sourcesTemplate?: string;
/**
* Override the artifactTemplate's `{{name}}` template variable. Defaults to the package.json's
* Override the artifactTemplate's `{name}` template variable. Defaults to the `package.json`'s
* name, or if that doesn't exist, the current working directories name.
*/
name?: string;
/**
* Root directory to ZIP. The ZIP can be uploaded to the Firefox Addon Store as your source
* code. Defaults to the `config.root` directory.
* Root directory to ZIP when generating the sources ZIP.
*
* @default config.root
*/
sourcesRoot?: string;
/**
+1 -1
View File
@@ -10,7 +10,7 @@ export function getUnimportOptions(
imports: [{ name: 'defineConfig', from: 'wxt' }],
presets: [{ package: 'wxt/client' }, { package: 'wxt/browser' }],
warn: config.logger.warn,
dirs: ['./components/*', './composables/*', './hooks/*', './utils/*'],
dirs: ['components', 'composables', 'hooks', 'utils'],
};
return mergeConfig(
+36
View File
@@ -17,6 +17,7 @@ import {
mapWxtOptionsToContentScript,
} from './content-scripts';
import { getPackageJson } from './package';
import { normalizePath } from './paths';
/**
* Writes the manifest to the output directory and the build output.
@@ -65,6 +66,7 @@ export async function generateMainfest(
? pkg?.version
: undefined,
short_name: pkg?.shortName,
icons: discoverIcons(buildOutput),
},
config.manifest,
);
@@ -317,6 +319,40 @@ function addEntrypoints(
}
}
function discoverIcons(
buildOutput: Omit<BuildOutput, 'manifest'>,
): Manifest.WebExtensionManifest['icons'] {
const icons: [string, string][] = [];
// prettier-ignore
// #region snippet
const iconRegex = [
/^icon-([0-9]+)\.(png|bmp|jpeg|jpg|ico|gif)$/, // icon-16.png
/^icon-([0-9]+)x[0-9]+\.(png|bmp|jpeg|jpg|ico|gif)$/, // icon-16x16.png
/^icon@([0-9]+)w\.(png|bmp|jpeg|jpg|ico|gif)$/, // icon@16w.png
/^icon@([0-9]+)h\.(png|bmp|jpeg|jpg|ico|gif)$/, // icon@16h.png
/^icon@([0-9]+)\.(png|bmp|jpeg|jpg|ico|gif)$/, // icon@16.png
/^icon[\/\\]([0-9]+)\.(png|bmp|jpeg|jpg|ico|gif)$/, // icon/16.png
/^icon[\/\\]([0-9]+)x[0-9]+\.(png|bmp|jpeg|jpg|ico|gif)$/, // icon/16x16.png
];
// #endregion snippet
buildOutput.publicAssets.forEach((asset) => {
let size: string | undefined;
for (const regex of iconRegex) {
const match = asset.fileName.match(regex);
if (match?.[1] != null) {
size = match[1];
break;
}
}
if (size == null) return;
icons.push([size, normalizePath(asset.fileName)]);
});
return icons.length > 0 ? Object.fromEntries(icons) : undefined;
}
function addDevModeCsp(
manifest: Manifest.WebExtensionManifest,
config: InternalConfig,
+4 -2
View File
@@ -66,10 +66,12 @@ export async function zipExtension(
zipFiles.push(sourcesZipPath);
}
config.logger.success(
await printFileList(
config.logger.success,
`Zipped extension in ${formatDuration(Date.now() - start)}`,
config.outBaseDir,
zipFiles,
);
await printFileList(config.logger.log, config.outBaseDir, zipFiles);
return zipFiles;
}
+1 -1
View File
@@ -1,7 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import './style.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
+1 -1
View File
@@ -1,6 +1,6 @@
import { render } from 'solid-js/web';
import './index.css';
import './style.css';
import App from './App';
render(() => <App />, document.getElementById('root')!);