Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 65fc0fadb3 | |||
| 64b686713b | |||
| 9c6d3e37d4 | |||
| 37abe9db39 | |||
| 9ebef4a268 | |||
| e60f7c7e77 | |||
| 36aadd0604 | |||
| e4598a5e37 | |||
| db5c22809c | |||
| c61fa8f858 | |||
| 5fe4681620 | |||
| 80d384361b | |||
| a1e72c0749 | |||
| e3a80b8247 | |||
| c0013acadc | |||
| f472c53da7 | |||
| 032f7931e8 | |||
| 57a12c1711 | |||
| d13ca3983f | |||
| 9769ce4ac9 | |||
| 55dc263dc9 | |||
| e56ae23747 | |||
| bfd4af5d69 | |||
| 9673604878 | |||
| ae55aecb0d | |||
| a14d0b4f2f | |||
| 301e4a17da | |||
| 19cebbf673 | |||
| ef73c07dfe | |||
| ca5ad7e51b | |||
| 5dab944abc | |||
| adaae6e1a4 | |||
| 4524cbdf76 | |||
| 1171cf52f0 | |||
| c5978b1072 | |||
| e5ef9367ed |
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Set default
|
||||
* @aklinker1 @Timeraa
|
||||
* @aklinker1
|
||||
|
||||
# Secure Directories
|
||||
/.github/ @aklinker1
|
||||
|
||||
@@ -11,19 +11,26 @@ permissions:
|
||||
jobs:
|
||||
vhs:
|
||||
name: Create VHS
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: macos-latest
|
||||
if: ${{ github.repository == 'wxt-dev/wxt' }}
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
install: false
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00
|
||||
with:
|
||||
go-version: '1.25.1'
|
||||
|
||||
# This prevents pnpm dlx from downloading WXT in the video
|
||||
- name: Pre-install WXT
|
||||
run: |
|
||||
@@ -31,9 +38,10 @@ jobs:
|
||||
pnpm dlx wxt@latest --version
|
||||
|
||||
- name: Record VHS
|
||||
uses: charmbracelet/vhs-action@59641cdc7fadf3978db65eb8c6937ea2752f4ec3 # v2.1.0
|
||||
with:
|
||||
path: 'docs/tapes/init-demo.tape'
|
||||
run: |
|
||||
brew install ttyd ffmpeg
|
||||
go install github.com/charmbracelet/vhs@517bcda0faf416728bcf6b7fe489eb0e2469d9b5 # v0.10.0
|
||||
vhs docs/tapes/init-demo.tape
|
||||
|
||||
- name: Save recorded GIF
|
||||
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||
@@ -41,5 +49,8 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
commit_message: 'docs: Update `wxt init` GIF'
|
||||
commit_user_name: github-actions[bot]
|
||||
commit_user_email: github-actions[bot]@users.noreply.github.com
|
||||
commit_author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||||
# https://github.com/charmbracelet/vhs#output
|
||||
file_pattern: 'docs/assets/*.gif'
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
singleQuote: true
|
||||
endOfLine: lf
|
||||
plugins:
|
||||
- prettier-plugin-jsdoc
|
||||
|
||||
@@ -119,6 +119,10 @@ const chromeExtensionIds = [
|
||||
'mfjdonmgmgcijagclnkfhmjiblbfjaid', // KeyFloat - Floating multilingual keyboard with native key mappings, drag, dark mode, sounds, and dynamic layouts for macOS & Windows
|
||||
'dhiekgdaipindoapjmcnpompdknjeijf', // Glossy New Tab - Say Goodbye to Boring Tabs with live wallpapers
|
||||
'lapnciffpekdengooeolaienkeoilfeo', // All API Hub – AI Relay & New API Manager - https://github.com/qixing-jk/all-api-hub
|
||||
'bhgobenflkkhfcgkikejaaejenoddcmo', // Scrape Similar - Extract data from websites into spreadsheets - https://github.com/zizzfizzix/scrape-similar
|
||||
'kinlknncggaihnhdcalijdmpbhbflalm', // isTrust - https://github.com/Internet-Society-Belgium/isTrust/
|
||||
'ojpakgiekphppgkcdihbjpafobhnhlkp', // Dymo
|
||||
'pmgehhllikbjmadpenhabejhpemplhmd', // Extension Rank Checker - Extension Ranker
|
||||
];
|
||||
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
|
||||
@@ -77,9 +77,7 @@ export function menuItem(
|
||||
return { text, link };
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up and add badges to typedoc leaf sections
|
||||
*/
|
||||
/** Clean up and add badges to typedoc leaf sections */
|
||||
export function prepareTypedocSidebar(items: SidebarItem[]) {
|
||||
// skip contents file
|
||||
const filtered = items.slice(1);
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 136 KiB |
+11
-12
@@ -16,27 +16,26 @@
|
||||
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.6",
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@aklinker1/check": "^2.2.0",
|
||||
"@commitlint/config-conventional": "^20.4.1",
|
||||
"@commitlint/types": "^20.4.0",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@commitlint/config-conventional": "^20.4.3",
|
||||
"@commitlint/types": "^20.4.3",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"changelogen": "^0.6.2",
|
||||
"consola": "^3.4.2",
|
||||
"fast-glob": "^3.3.3",
|
||||
"feed": "^5.2.0",
|
||||
"fs-extra": "^11.3.3",
|
||||
"lint-staged": "^16.2.7",
|
||||
"markdown-it-footnote": "^4.0.0",
|
||||
"markdownlint-cli": "^0.47.0",
|
||||
"markdownlint-cli": "^0.48.0",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"nano-staged": "^0.8.0",
|
||||
"p-map": "^7.0.4",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-jsdoc": "^1.8.0",
|
||||
"semver": "^7.7.4",
|
||||
"simple-git-hooks": "^2.13.1",
|
||||
"tsdown": "^0.20.3",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"tsdown": "^0.21.0",
|
||||
"tsx": "4.21.0",
|
||||
"typedoc": "^0.25.4",
|
||||
"typedoc-plugin-frontmatter": "^1.3.1",
|
||||
@@ -48,13 +47,13 @@
|
||||
"vitepress-plugin-group-icons": "^1.7.1",
|
||||
"vitepress-plugin-llms": "^1.11.0",
|
||||
"vitest-mock-extended": "^3.1.0",
|
||||
"vue": "^3.5.27",
|
||||
"vue": "^3.5.29",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm lint-staged"
|
||||
"pre-commit": "./node_modules/.bin/nano-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"nano-staged": {
|
||||
"*": "prettier --ignore-unknown --write"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## v0.5.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.3...analytics-v0.5.4)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Continue using `useAppConfig` to support older versions of WXT ([bfd94556](https://github.com/wxt-dev/wxt/commit/bfd94556))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v0.5.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.2...analytics-v0.5.3)
|
||||
|
||||
@@ -62,7 +62,8 @@ export function createAnalytics(config?: AnalyticsConfig): Analytics {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an analytics client in the background responsible for uploading events to the server to avoid CORS errors.
|
||||
* Creates an analytics client in the background responsible for uploading
|
||||
* events to the server to avoid CORS errors.
|
||||
*/
|
||||
function createBackgroundAnalytics(
|
||||
config: AnalyticsConfig | undefined,
|
||||
@@ -219,9 +220,7 @@ function createBackgroundAnalytics(
|
||||
return analytics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an analytics client for non-background contexts.
|
||||
*/
|
||||
/** Creates an analytics client for non-background contexts. */
|
||||
function createFrontendAnalytics(): Analytics {
|
||||
const port = browser.runtime.connect({ name: ANALYTICS_PORT });
|
||||
const sessionId = Date.now();
|
||||
|
||||
@@ -44,9 +44,9 @@ export default defineWxtModule({
|
||||
? clientModuleId
|
||||
: normalizePath(relative(wxtAnalyticsFolder, clientModuleId))
|
||||
}';
|
||||
import { getAppConfig } from '#imports';
|
||||
import { useAppConfig } from '#imports';
|
||||
|
||||
export const analytics = createAnalytics(getAppConfig().analytics);
|
||||
export const analytics = createAnalytics(useAppConfig().analytics);
|
||||
`;
|
||||
addAlias(wxt, '#analytics', wxtAnalyticsIndex);
|
||||
wxt.hook('prepare:types', async (_, entries) => {
|
||||
|
||||
@@ -8,36 +8,39 @@ export interface Analytics {
|
||||
) => void;
|
||||
/** Save information about the user. */
|
||||
identify: (userId: string, userProperties?: Record<string, string>) => void;
|
||||
/** Automatically setup and track user interactions, returning a function to remove any listeners that were setup. */
|
||||
/**
|
||||
* Automatically setup and track user interactions, returning a function to
|
||||
* remove any listeners that were setup.
|
||||
*/
|
||||
autoTrack: (root: Document | ShadowRoot | Element) => () => void;
|
||||
/** Calls `config.enabled.setValue`. */
|
||||
setEnabled: (enabled: boolean) => void;
|
||||
}
|
||||
|
||||
export interface AnalyticsConfig {
|
||||
/**
|
||||
* Array of providers to send analytics to.
|
||||
*/
|
||||
/** Array of providers to send analytics to. */
|
||||
providers: AnalyticsProvider[];
|
||||
/**
|
||||
* Enable debug logs and other provider-specific debugging features.
|
||||
*/
|
||||
/** Enable debug logs and other provider-specific debugging features. */
|
||||
debug?: boolean;
|
||||
/**
|
||||
* Your extension's version, reported alongside events.
|
||||
*
|
||||
* @default browser.runtime.getManifest().version`.
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
* Configure how the enabled flag is persisted. Defaults to using `browser.storage.local`.
|
||||
* Configure how the enabled flag is persisted. Defaults to using
|
||||
* `browser.storage.local`.
|
||||
*/
|
||||
enabled?: AnalyticsStorageItem<boolean>;
|
||||
/**
|
||||
* Configure how the user Id is persisted. Defaults to using `browser.storage.local`.
|
||||
* Configure how the user Id is persisted. Defaults to using
|
||||
* `browser.storage.local`.
|
||||
*/
|
||||
userId?: AnalyticsStorageItem<string | undefined>;
|
||||
/**
|
||||
* Configure how user properties are persisted. Defaults to using `browser.storage.local`.
|
||||
* Configure how user properties are persisted. Defaults to using
|
||||
* `browser.storage.local`.
|
||||
*/
|
||||
userProperties?: AnalyticsStorageItem<Record<string, string>>;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wxt-dev/analytics",
|
||||
"version": "0.5.3",
|
||||
"version": "0.5.4",
|
||||
"description": "Add analytics to your web extension",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -8,7 +8,7 @@
|
||||
"dev:build": "buildc --deps-only -- wxt build",
|
||||
"check": "pnpm build && check",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepack": "pnpm -s build",
|
||||
"prepack": "pnpm build",
|
||||
"prepare": "buildc --deps-only -- wxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"publint": "^0.3.17",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
|
||||
@@ -40,19 +40,19 @@
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "pnpm build && check",
|
||||
"test": "buildc --deps-only -- vitest"
|
||||
"test": "buildc --deps-only -- vitest",
|
||||
"prepack": "pnpm build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"defu": "^6.1.4",
|
||||
"fs-extra": "^11.3.3",
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"oxlint": "^1.43.0",
|
||||
"publint": "^0.3.17",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18",
|
||||
"wxt": "workspace:*"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
|
||||
import { resolve } from 'node:path';
|
||||
import * as fsExtra from 'fs-extra';
|
||||
import * as fsPromises from 'node:fs/promises';
|
||||
import sharp from 'sharp';
|
||||
import type { Wxt, UserManifest } from 'wxt';
|
||||
|
||||
@@ -9,9 +9,9 @@ import autoIconsModule from '../index';
|
||||
import type { AutoIconsOptions } from '../index';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('fs-extra', () => ({
|
||||
ensureDir: vi.fn(),
|
||||
pathExists: vi.fn(),
|
||||
vi.mock('node:fs/promises', () => ({
|
||||
mkdir: vi.fn(),
|
||||
access: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('sharp', () => ({
|
||||
@@ -86,8 +86,8 @@ describe('auto-icons module', () => {
|
||||
vi.mocked(sharp).mockReturnValue(
|
||||
mockSharpInstance as unknown as sharp.Sharp,
|
||||
);
|
||||
vi.mocked(fsExtra.pathExists).mockResolvedValue(true as any);
|
||||
vi.mocked(fsExtra.ensureDir).mockResolvedValue(undefined as any);
|
||||
vi.mocked(fsPromises.access).mockResolvedValue(undefined);
|
||||
vi.mocked(fsPromises.mkdir).mockResolvedValue(undefined as any);
|
||||
});
|
||||
|
||||
describe('module setup', () => {
|
||||
@@ -158,7 +158,7 @@ describe('auto-icons module', () => {
|
||||
});
|
||||
|
||||
it('should warn when base icon not found', async () => {
|
||||
vi.mocked(fsExtra.pathExists).mockResolvedValue(false as any);
|
||||
vi.mocked(fsPromises.access).mockRejectedValue(new Error('ENOENT'));
|
||||
|
||||
const options: AutoIconsOptions = {
|
||||
enabled: true,
|
||||
@@ -293,8 +293,9 @@ describe('auto-icons module', () => {
|
||||
expect(mockSharpInstance.resize).toHaveBeenCalledWith(32);
|
||||
expect(mockSharpInstance.resize).toHaveBeenCalledWith(16);
|
||||
|
||||
expect(fsExtra.ensureDir).toHaveBeenCalledWith(
|
||||
expect(fsPromises.mkdir).toHaveBeenCalledWith(
|
||||
resolve('/mock/dist', 'icons'),
|
||||
{ recursive: true },
|
||||
);
|
||||
|
||||
expect(output.publicAssets).toEqual([
|
||||
@@ -514,7 +515,7 @@ describe('auto-icons module', () => {
|
||||
};
|
||||
|
||||
// Make ensureDir throw an error
|
||||
vi.mocked(fsExtra.ensureDir).mockRejectedValue(
|
||||
vi.mocked(fsPromises.mkdir).mockRejectedValue(
|
||||
new Error('Directory creation failed'),
|
||||
);
|
||||
|
||||
@@ -530,7 +531,7 @@ describe('auto-icons module', () => {
|
||||
if (buildHook) {
|
||||
await buildHook(mockWxt as unknown as Wxt, output);
|
||||
// But ensureDir should have been called
|
||||
expect(fsExtra.ensureDir).toHaveBeenCalled();
|
||||
expect(fsPromises.mkdir).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineWxtModule } from 'wxt/modules';
|
||||
import { resolve, relative } from 'node:path';
|
||||
import defu from 'defu';
|
||||
import sharp from 'sharp';
|
||||
import { ensureDir, pathExists } from 'fs-extra';
|
||||
import { access, mkdir } from 'node:fs/promises';
|
||||
|
||||
export default defineWxtModule<AutoIconsOptions>({
|
||||
name: '@wxt-dev/auto-icons',
|
||||
@@ -37,7 +37,11 @@ export default defineWxtModule<AutoIconsOptions>({
|
||||
if (!parsedOptions.enabled)
|
||||
return wxt.logger.warn(`\`[auto-icons]\` ${this.name} disabled`);
|
||||
|
||||
if (!(await pathExists(resolvedPath))) {
|
||||
const iconExists = await access(resolvedPath).then(
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
if (!iconExists) {
|
||||
return wxt.logger.warn(
|
||||
`\`[auto-icons]\` Skipping icon generation, no base icon found at ${relative(process.cwd(), resolvedPath)}`,
|
||||
);
|
||||
@@ -91,7 +95,7 @@ export default defineWxtModule<AutoIconsOptions>({
|
||||
}
|
||||
}
|
||||
|
||||
ensureDir(resolve(outputFolder, 'icons'));
|
||||
mkdir(resolve(outputFolder, 'icons'), { recursive: true });
|
||||
await resizedImage.toFile(resolve(outputFolder, `icons/${size}.png`));
|
||||
|
||||
output.publicAssets.push({
|
||||
@@ -109,12 +113,11 @@ export default defineWxtModule<AutoIconsOptions>({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Options for the auto-icons module
|
||||
*/
|
||||
/** Options for the auto-icons module */
|
||||
export interface AutoIconsOptions {
|
||||
/**
|
||||
* Enable auto-icons generation
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enabled?: boolean;
|
||||
@@ -122,27 +125,30 @@ export interface AutoIconsOptions {
|
||||
* Path to the image to use.
|
||||
*
|
||||
* Path is relative to the project's src directory.
|
||||
* @default "<srcDir>/assets/icon.png"
|
||||
*
|
||||
* @default '<srcDir>/assets/icon.png'
|
||||
*/
|
||||
baseIconPath?: string;
|
||||
/**
|
||||
* Apply a visual indicator to the icon when running in development mode.
|
||||
*
|
||||
* "grayscale" converts the icon to grayscale.
|
||||
* "overlay" covers the bottom half with a yellow rectangle and writes "DEV" in black text.
|
||||
* Set to `false` to disable any indicator.
|
||||
* "grayscale" converts the icon to grayscale. "overlay" covers the bottom
|
||||
* half with a yellow rectangle and writes "DEV" in black text. Set to `false`
|
||||
* to disable any indicator.
|
||||
*
|
||||
* @default "grayscale"
|
||||
* @default 'grayscale'
|
||||
*/
|
||||
developmentIndicator?: 'grayscale' | 'overlay' | false;
|
||||
/**
|
||||
* Grayscale the image when in development mode to indicate development
|
||||
* @default true
|
||||
*
|
||||
* @deprecated Use `developmentIndicator` instead
|
||||
* @default true
|
||||
*/
|
||||
grayscaleOnDevelopment?: boolean;
|
||||
/**
|
||||
* Sizes to generate icons for
|
||||
*
|
||||
* @default [128, 48, 32, 16]
|
||||
*/
|
||||
sizes?: number[];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wxt-dev/browser",
|
||||
"description": "Provides a cross-browser API for using extension APIs and types based on @types/chrome",
|
||||
"version": "0.1.37",
|
||||
"version": "0.1.38",
|
||||
"type": "module",
|
||||
"main": "src/index.mjs",
|
||||
"types": "src/index.d.ts",
|
||||
@@ -23,8 +23,8 @@
|
||||
"src"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chrome": "0.1.37",
|
||||
"fs-extra": "^11.3.3",
|
||||
"@types/chrome": "0.1.38",
|
||||
"@types/node": "^20.0.0",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import spawn from 'nano-spawn';
|
||||
import fs from 'fs-extra';
|
||||
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join, resolve, sep } from 'node:path';
|
||||
import { sep as posixSep } from 'node:path/posix';
|
||||
@@ -17,8 +17,8 @@ const pkgJsonPath = fileURLToPath(
|
||||
import.meta.resolve('@types/chrome/package.json'),
|
||||
);
|
||||
const pkgDir = dirname(pkgJsonPath);
|
||||
const pkgJson = await fs.readJson(pkgJsonPath);
|
||||
const pkgJsonTemplate = await fs.readFile('templates/package.json', 'utf8');
|
||||
const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8'));
|
||||
const pkgJsonTemplate = await readFile('templates/package.json', 'utf8');
|
||||
const newPkgJson = JSON.parse(
|
||||
pkgJsonTemplate.replaceAll('{{chromeTypesVersion}}', pkgJson.version),
|
||||
);
|
||||
@@ -27,7 +27,7 @@ newPkgJson.peerDependencies = pkgJson.peerDependencies;
|
||||
newPkgJson.peerDependenciesMeta = pkgJson.peerDependenciesMeta;
|
||||
|
||||
const outPkgJsonPath = resolve('package.json');
|
||||
await fs.writeJson(outPkgJsonPath, newPkgJson);
|
||||
await writeFile(outPkgJsonPath, JSON.stringify(newPkgJson, null, 2));
|
||||
await spawn('pnpm', ['-w', 'prettier', '--write', outPkgJsonPath]);
|
||||
|
||||
// Generate declaration files
|
||||
@@ -35,7 +35,7 @@ await spawn('pnpm', ['-w', 'prettier', '--write', outPkgJsonPath]);
|
||||
console.log('Generating declaration files');
|
||||
const outDir = resolve('src/gen');
|
||||
const declarationFileMapping = (
|
||||
await fs.readdir(pkgDir, {
|
||||
await readdir(pkgDir, {
|
||||
recursive: true,
|
||||
encoding: 'utf8',
|
||||
})
|
||||
@@ -50,11 +50,11 @@ const declarationFileMapping = (
|
||||
}));
|
||||
|
||||
for (const { file, srcPath, destPath } of declarationFileMapping) {
|
||||
const content = await fs.readFile(srcPath, 'utf8');
|
||||
const content = await readFile(srcPath, 'utf8');
|
||||
const transformedContent = transformFile(file, content);
|
||||
const destDir = dirname(destPath);
|
||||
await fs.mkdir(destDir, { recursive: true });
|
||||
await fs.writeFile(destPath, transformedContent);
|
||||
await mkdir(destDir, { recursive: true });
|
||||
await writeFile(destPath, transformedContent);
|
||||
console.log(` \x1b[2m-\x1b[0m \x1b[36m${file}\x1b[0m`);
|
||||
}
|
||||
|
||||
|
||||
+11
-8
@@ -4076,14 +4076,17 @@ export namespace Browser {
|
||||
id: string;
|
||||
/**
|
||||
* Implements the WebCrypto's SubtleCrypto interface. The cryptographic operations, including key generation, are hardware-backed.
|
||||
* Only non-extractable keys can be generated. The supported key types are RSASSA-PKCS1-V1_5 and RSA-OAEP (on Chrome versions 134+) with `modulusLength` up to 2048 and ECDSA with `namedCurve` P-256. Each RSASSA-PKCS1-V1_5 and ECDSA key can be used for signing data at most once, unless the extension is allowlisted through the KeyPermissions policy, in which case the key can be used indefinitely. RSA-OAEP keys are supported since Chrome version 134 and can be used by extensions allowlisted through that same policy to unwrap other keys.
|
||||
*
|
||||
* Only non-extractable keys can be generated. The supported key types are RSASSA-PKCS1-V1_5 with `modulusLength` up to 2048 and ECDSA with `namedCurve` P-256. Each key can be used for signing data at most once, unless the extension is allowlisted by the KeyPermissions policy, in which case the key can be used indefinitely.
|
||||
*
|
||||
* Keys generated on a specific `Token` cannot be used with any other Tokens, nor can they be used with `window.crypto.subtle`. Equally, `Key` objects created with `window.crypto.subtle` cannot be used with this interface.
|
||||
*/
|
||||
subtleCrypto: SubtleCrypto;
|
||||
/**
|
||||
* Implements the WebCrypto's SubtleCrypto interface. The cryptographic operations, including key generation, are software-backed.
|
||||
* Protection of the keys, and thus implementation of the non-extractable property, is done in software, so the keys are less protected than hardware-backed keys.
|
||||
* Only non-extractable keys can be generated. The supported key types are RSASSA-PKCS1-V1_5 and RSA-OAEP (on Chrome versions 134+) with `modulusLength` up to 2048. Each RSASSA-PKCS1-V1_5 key can be used for signing data at most once, unless the extension is allowlisted through the KeyPermissions policy, in which case the key can be used indefinitely. RSA-OAEP keys are supported since Chrome version 134 and can be used by extensions allowlisted through that same policy to unwrap other keys.
|
||||
* Implements the WebCrypto's SubtleCrypto interface. The cryptographic operations, including key generation, are software-backed. Protection of the keys, and thus implementation of the non-extractable property, is done in software, so the keys are less protected than hardware-backed keys.
|
||||
*
|
||||
* Only non-extractable keys can be generated. The only supported key type is RSASSA-PKCS1-V1_5 with `modulusLength` up to 2048. up to 2048. Each key can be used for signing data at most once, unless the extension is allowlisted through the KeyPermissions policy, in which case the key can be used indefinitely.
|
||||
*
|
||||
* Keys generated on a specific `Token` cannot be used with any other Tokens, nor can they be used with `window.crypto.subtle`. Equally, `Key` objects created with `window.crypto.subtle` cannot be used with this interface.
|
||||
* @since Chrome 97
|
||||
*/
|
||||
@@ -9692,7 +9695,7 @@ export namespace Browser {
|
||||
/** Sent after onSuspend to indicate that the app won't be unloaded after all. */
|
||||
export const onSuspendCanceled: events.Event<() => void>;
|
||||
|
||||
/** Fired when a message is sent from either an extension process (by {@link runtime.sendMessage}) or a content script (by {@link tabs.sendMessage}). */
|
||||
/** Fired when a message is sent from either {@link runtime.sendMessage} or {@link tabs.sendMessage}. */
|
||||
export const onMessage: events.Event<
|
||||
(message: any, sender: MessageSender, sendResponse: (response?: any) => void) => void
|
||||
>;
|
||||
@@ -11058,7 +11061,7 @@ export namespace Browser {
|
||||
sessionId?: string | undefined;
|
||||
/**
|
||||
* The ID of the Split View that the tab belongs to.
|
||||
* @since Chrome 145
|
||||
* @since Chrome 140
|
||||
*/
|
||||
splitViewId?: number | undefined;
|
||||
/**
|
||||
@@ -11134,7 +11137,7 @@ export namespace Browser {
|
||||
|
||||
/**
|
||||
* An ID that represents the absence of a split tab.
|
||||
* @since Chrome 145
|
||||
* @since Chrome 140
|
||||
*/
|
||||
export const SPLIT_VIEW_ID_NONE: -1;
|
||||
|
||||
@@ -11581,7 +11584,7 @@ export namespace Browser {
|
||||
export function duplicate(tabId: number, callback: (tab?: Tab) => void): void;
|
||||
|
||||
/**
|
||||
* Sends a single message to the content script(s) in the specified tab, with an optional callback to run when a response is sent back. The {@link runtime.onMessage} event is fired in each content script running in the specified tab for the current extension.
|
||||
* Sends a single message to the content script(s) in the specified tab. The {@link runtime.onMessage} event is fired in each content script running in the specified tab for the current extension.
|
||||
*
|
||||
* Can return its result via Promise in Manifest V3 or later since Chrome 99.
|
||||
*/
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chrome": "{{chromeTypesVersion}}",
|
||||
"fs-extra": "^11.3.3",
|
||||
"@types/node": "^20.0.0",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
|
||||
@@ -23,13 +23,14 @@
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "buildc --deps-only -- check",
|
||||
"test": "buildc --deps-only -- vitest"
|
||||
"test": "buildc --deps-only -- vitest",
|
||||
"prepack": "pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wxt-dev/browser": "workspace:^",
|
||||
"chokidar": "^5.0.0",
|
||||
"confbox": "^0.1.8 || ^0.2.2",
|
||||
"fast-glob": "^3.3.3"
|
||||
"confbox": "^0.2.4",
|
||||
"tinyglobby": "^0.2.15"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.7"
|
||||
@@ -41,8 +42,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.17.6",
|
||||
"oxlint": "^1.43.0",
|
||||
"publint": "^0.3.17",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18",
|
||||
"vitest-plugin-random-seed": "^1.1.2",
|
||||
|
||||
@@ -51,7 +51,8 @@ export type MessageFormat = 'JSON5' | 'YAML' | 'TOML';
|
||||
//
|
||||
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/reference/api/i18n#overview-predefined
|
||||
* See
|
||||
* https://developer.chrome.com/docs/extensions/reference/api/i18n#overview-predefined
|
||||
*/
|
||||
const PREDEFINED_MESSAGES: Record<string, ChromeMessage> = {
|
||||
'@@extension_id': {
|
||||
@@ -110,9 +111,7 @@ const ALLOWED_CHROME_MESSAGE_KEYS: Set<string> = new Set<keyof ChromeMessage>([
|
||||
// PARSING
|
||||
//
|
||||
|
||||
/**
|
||||
* Parse a messages file, extract the messages. Supports JSON, JSON5, and YAML.
|
||||
*/
|
||||
/** Parse a messages file, extract the messages. Supports JSON, JSON5, and YAML. */
|
||||
export async function parseMessagesFile(
|
||||
file: string,
|
||||
): Promise<ParsedMessage[]> {
|
||||
@@ -121,9 +120,7 @@ export async function parseMessagesFile(
|
||||
return parseMessagesText(text, EXT_FORMATS_MAP[ext] ?? 'JSON5');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string, extracting the messages. Supports JSON, JSON5, and YAML.
|
||||
*/
|
||||
/** Parse a string, extracting the messages. Supports JSON, JSON5, and YAML. */
|
||||
export function parseMessagesText(
|
||||
text: string,
|
||||
format: 'JSON5' | 'YAML' | 'TOML',
|
||||
@@ -131,9 +128,7 @@ export function parseMessagesText(
|
||||
return parseMessagesObject(PARSERS[format](text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the JS object form of a raw messages file, extract the messages.
|
||||
*/
|
||||
/** Given the JS object form of a raw messages file, extract the messages. */
|
||||
export function parseMessagesObject(object: any): ParsedMessage[] {
|
||||
return _parseMessagesObject(
|
||||
[],
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/**
|
||||
* @module @wxt-dev/i18n
|
||||
*/
|
||||
/** @module @wxt-dev/i18n */
|
||||
import {
|
||||
I18nStructure,
|
||||
DefaultI18nStructure,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* ```ts
|
||||
* export default defineConfig({
|
||||
* modules: ["@wxt-dev/i18n/module"],
|
||||
* modules: ['@wxt-dev/i18n/module'],
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
generateTypeText,
|
||||
SUPPORTED_LOCALES,
|
||||
} from './build';
|
||||
import glob from 'fast-glob';
|
||||
import { glob } from 'tinyglobby';
|
||||
import { basename, extname, join, resolve } from 'node:path';
|
||||
import { watch } from 'chokidar';
|
||||
import { GeneratedPublicFile, WxtDirFileEntry } from 'wxt';
|
||||
@@ -47,6 +47,7 @@ export default defineWxtModule<I18nOptions>({
|
||||
const files = await glob('*.{json,json5,jsonc,yml,yaml,toml}', {
|
||||
cwd: localesDir,
|
||||
absolute: true,
|
||||
expandDirectories: false,
|
||||
});
|
||||
|
||||
const unsupportedLocales: string[] = [];
|
||||
@@ -180,13 +181,12 @@ export { type GeneratedI18nStructure }
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Options for the i18n module
|
||||
*/
|
||||
/** Options for the i18n module */
|
||||
export interface I18nOptions {
|
||||
/**
|
||||
* Directory containing files that define the translations.
|
||||
* @default "${config.srcDir}/locales"
|
||||
*
|
||||
* @default '${config.srcDir}/locales'
|
||||
*/
|
||||
localesDir?: string;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
"@wxt-dev/browser": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"oxlint": "^1.43.0",
|
||||
"publint": "^0.3.17",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
/**
|
||||
* This module uses a lazy getter function so the logic isn't ran until it's needed.
|
||||
* This module uses a lazy getter function so the logic isn't ran until it's
|
||||
* needed.
|
||||
*
|
||||
* This has a few benefits:
|
||||
*
|
||||
* 1. Easier to mock in tests
|
||||
* 2. Safe to import in NodeJS environments (but it should be safe to run just in-case)
|
||||
* 3. Keeps startup fast by waiting to run the slow functions (`instanceof` or `browser.extension.getBackgroundPage`) until needed
|
||||
* 2. Safe to import in NodeJS environments (but it should be safe to run just
|
||||
* in-case)
|
||||
* 3. Keeps startup fast by waiting to run the slow functions (`instanceof` or
|
||||
* `browser.extension.getBackgroundPage`) until needed
|
||||
*
|
||||
* @module @wxt-dev/is-background
|
||||
*/
|
||||
@@ -16,10 +20,10 @@ let cached: boolean | undefined;
|
||||
* Getter that returns if the current context is apart of an extension's
|
||||
* background or not.
|
||||
*
|
||||
* > This function caches the result when called for the first time so it
|
||||
* > doesn't have to recalculate.
|
||||
* > This function caches the result when called for the first time so it doesn't
|
||||
* > have to recalculate.
|
||||
*
|
||||
* @returns true when in a background page or service worker.
|
||||
* @returns True when in a background page or service worker.
|
||||
*/
|
||||
export function isBackground(): boolean {
|
||||
if (cached == null) cached = getIsBackground();
|
||||
|
||||
@@ -1,5 +1,51 @@
|
||||
# Changelog
|
||||
|
||||
## v1.2.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.2.1...module-react-v1.2.2)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Add `@vitejs/plugin-react` v6 support ([c61fa8f8](https://github.com/wxt-dev/wxt/commit/c61fa8f8))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v1.2.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.2.0...module-react-v1.2.1)
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add prepack script to all packages ([032f7931](https://github.com/wxt-dev/wxt/commit/032f7931))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v1.2.0
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.5...module-react-v1.2.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add `vitePluginsBefore` option ([#2170](https://github.com/wxt-dev/wxt/pull/2170))
|
||||
- Vite 8 support ([bfd4af5d](https://github.com/wxt-dev/wxt/commit/bfd4af5d))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
|
||||
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
|
||||
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
|
||||
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
|
||||
- **deps:** Upgrade deps ([#2175](https://github.com/wxt-dev/wxt/pull/2175))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Martin Broder <hello@martinbroder.com>
|
||||
|
||||
## v1.1.5
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.4...module-react-v1.1.5)
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import 'wxt';
|
||||
import { addImportPreset, addViteConfig, defineWxtModule } from 'wxt/modules';
|
||||
import react, { Options as PluginOptions } from '@vitejs/plugin-react';
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
export default defineWxtModule<ReactModuleOptions>({
|
||||
name: '@wxt-dev/module-react',
|
||||
configKey: 'react',
|
||||
setup(wxt, options) {
|
||||
const { vite } = options ?? {};
|
||||
const { vite, vitePluginsBefore } = options ?? {};
|
||||
|
||||
addViteConfig(wxt, () => ({
|
||||
plugins: [react(vite)],
|
||||
plugins: [...(vitePluginsBefore ?? []), react(vite)],
|
||||
}));
|
||||
|
||||
addImportPreset(wxt, 'react');
|
||||
@@ -30,6 +31,12 @@ export default defineWxtModule<ReactModuleOptions>({
|
||||
|
||||
export interface ReactModuleOptions {
|
||||
vite?: PluginOptions;
|
||||
/**
|
||||
* Vite plugins to add before the `react()` plugin. Some plugins like the
|
||||
* `@tanstack/router-plugin` need to be added before `react()` to work
|
||||
* correctly.
|
||||
*/
|
||||
vitePluginsBefore?: PluginOption[];
|
||||
}
|
||||
|
||||
declare module 'wxt' {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"version": "1.1.5",
|
||||
"version": "1.2.2",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -42,21 +42,24 @@
|
||||
"dev": "wxt",
|
||||
"check": "pnpm build && check",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepare": "buildc --deps-only -- wxt prepare"
|
||||
"prepare": "buildc --deps-only -- wxt prepare",
|
||||
"prepack": "pnpm build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^5.4.19 || ^6.3.4 || ^7.0.0 || ^8.0.0-0",
|
||||
"wxt": ">=0.19.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "^4.4.1 || ^5.0.0"
|
||||
"@vitejs/plugin-react": "^4.4.1 || ^5.0.0 || ^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.13",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"publint": "^0.3.17",
|
||||
"publint": "^0.3.18",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.0.0",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
"dev": "wxt",
|
||||
"check": "pnpm build && check",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepare": "buildc --deps-only -- wxt prepare"
|
||||
"prepare": "buildc --deps-only -- wxt prepare",
|
||||
"prepack": "pnpm build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.16"
|
||||
@@ -51,7 +52,7 @@
|
||||
"vite-plugin-solid": "^2.11.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"publint": "^0.3.17",
|
||||
"publint": "^0.3.18",
|
||||
"solid-js": "^1.9.11",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "workspace:*"
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "pnpm build && check"
|
||||
"check": "pnpm build && check",
|
||||
"prepack": "pnpm build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.18.6",
|
||||
@@ -50,7 +51,7 @@
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0 || ^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"publint": "^0.3.17",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "pnpm build && check"
|
||||
"check": "pnpm build && check",
|
||||
"prepack": "pnpm build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.16"
|
||||
@@ -49,7 +50,7 @@
|
||||
"@vitejs/plugin-vue": "^5.2.3 || ^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"publint": "^0.3.17",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
"test": "buildc --deps-only -- vitest",
|
||||
"dev": "tsx --trace-warnings dev.ts",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepublishOnly": "pnpm build"
|
||||
"prepack": "pnpm build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"oxlint": "^1.43.0",
|
||||
"publint": "^0.3.17",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
|
||||
@@ -124,7 +124,8 @@ export const KNOWN_BROWSER_PATHS: Record<
|
||||
};
|
||||
|
||||
/**
|
||||
* When targeting a browser, this map contains the other targets to fall back on when a binary could not be found for the primary target.
|
||||
* When targeting a browser, this map contains the other targets to fall back on
|
||||
* when a binary could not be found for the primary target.
|
||||
*/
|
||||
export const FALLBACK_TARGETS: Partial<Record<KnownTarget, KnownTarget[]>> = {
|
||||
chrome: [
|
||||
|
||||
@@ -4,8 +4,11 @@ import { createCdpConnection } from './cdp';
|
||||
|
||||
/**
|
||||
* Install an extension to an already running instance of Firefox.
|
||||
* @param debuggerUrl The URL of the Firefox BiDi server (ex: `ws://127.0.0.1:45912`).
|
||||
* @param extensionDir Absolute path to the directory containing the extension to be installed.
|
||||
*
|
||||
* @param debuggerUrl The URL of the Firefox BiDi server (ex:
|
||||
* `ws://127.0.0.1:45912`).
|
||||
* @param extensionDir Absolute path to the directory containing the extension
|
||||
* to be installed.
|
||||
*/
|
||||
export async function installFirefox(
|
||||
debuggerUrl: string,
|
||||
@@ -33,7 +36,8 @@ export type BidiWebExtensionInstallResponse = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a child process of Chrome, install an extension. The process must be started with the following flags:
|
||||
* Given a child process of Chrome, install an extension. The process must be
|
||||
* started with the following flags:
|
||||
*
|
||||
* - `--remote-debugging-pipe`
|
||||
* - `--user-data-dir=...`
|
||||
|
||||
@@ -17,21 +17,46 @@ export type Target = KnownTarget | UnknownTarget;
|
||||
export type RunOptions = {
|
||||
/** Paths to binaries to use for each target. */
|
||||
browserBinaries?: Record<string, string>;
|
||||
/** Customize the arguments passed to the chromium binary. Conflicting arguments with required ones to install extensions are ignored. */
|
||||
/**
|
||||
* Customize the arguments passed to the chromium binary. Conflicting
|
||||
* arguments with required ones to install extensions are ignored.
|
||||
*/
|
||||
chromiumArgs?: string[];
|
||||
/** Control how data is persisted between launches. Either save data at a user level, project level, or don't persist data at all. Defaults to `project`. */
|
||||
/**
|
||||
* Control how data is persisted between launches. Either save data at a user
|
||||
* level, project level, or don't persist data at all. Defaults to `project`.
|
||||
*/
|
||||
dataPersistence?: 'user' | 'project' | 'none';
|
||||
/** Customize where your profile's data is stored when using `dataPersistence: 'project'`. Can be absolute or relative to the current working directory. */
|
||||
/**
|
||||
* Customize where your profile's data is stored when using `dataPersistence:
|
||||
* 'project'`. Can be absolute or relative to the current working directory.
|
||||
*/
|
||||
projectDataDir?: string;
|
||||
/** Customize the port Chrome's debugger is listening on. Defaults to a random open port. */
|
||||
/**
|
||||
* Customize the port Chrome's debugger is listening on. Defaults to a random
|
||||
* open port.
|
||||
*/
|
||||
chromiumRemoteDebuggingPort?: number;
|
||||
/** Directory where the extension will be installed from. Should contain a `manifest.json` file. Can be relative to the current working directory. Defaults to the current working directory. */
|
||||
/**
|
||||
* Directory where the extension will be installed from. Should contain a
|
||||
* `manifest.json` file. Can be relative to the current working directory.
|
||||
* Defaults to the current working directory.
|
||||
*/
|
||||
extensionDir?: string;
|
||||
/** Customize the arguments passed to the firefox binary. Conflicting arguments with required ones to install extensions are ignored. */
|
||||
/**
|
||||
* Customize the arguments passed to the firefox binary. Conflicting arguments
|
||||
* with required ones to install extensions are ignored.
|
||||
*/
|
||||
firefoxArgs?: string[];
|
||||
/** Customize the port Firefox's debugger is listening on. Defaults to a random open port. */
|
||||
/**
|
||||
* Customize the port Firefox's debugger is listening on. Defaults to a random
|
||||
* open port.
|
||||
*/
|
||||
firefoxRemoteDebuggingPort?: number;
|
||||
/** Specify the browser to open. Defaults to `"chrome"`, but you can pass any string. */
|
||||
/**
|
||||
* Specify the browser to open. Defaults to `"chrome"`, but you can pass any
|
||||
* string.
|
||||
*/
|
||||
target?: Target;
|
||||
};
|
||||
|
||||
@@ -219,8 +244,9 @@ async function pathExists(path: string): Promise<boolean> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from https://github.com/GoogleChrome/chrome-launcher/blob/main/src/flags.ts
|
||||
* with some flags commented out. Run tests after updating to compare.
|
||||
* Copied from
|
||||
* https://github.com/GoogleChrome/chrome-launcher/blob/main/src/flags.ts with
|
||||
* some flags commented out. Run tests after updating to compare.
|
||||
*/
|
||||
const CHROME_LAUNCHER_DEFAULT_FLAGS = [
|
||||
'--disable-features=' +
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "buildc --deps-only -- check",
|
||||
"test": "buildc --deps-only -- vitest"
|
||||
"test": "buildc --deps-only -- vitest",
|
||||
"prepack": "pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wxt-dev/browser": "workspace:^",
|
||||
@@ -36,8 +37,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@webext-core/fake-browser": "^1.3.4",
|
||||
"oxlint": "^1.43.0",
|
||||
"publint": "^0.3.17",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
|
||||
@@ -4,8 +4,8 @@ import { beforeEach, describe, expect, expectTypeOf, it, vi } from 'vitest';
|
||||
import { MigrationError, type WxtStorageItem, storage } from '../index';
|
||||
|
||||
/**
|
||||
* This works because fakeBrowser is synchronous, and is will finish any number of chained
|
||||
* calls within a single tick of the event loop, ie: a timeout of 0.
|
||||
* This works because fakeBrowser is synchronous, and is will finish any number
|
||||
* of chained calls within a single tick of the event loop, ie: a timeout of 0.
|
||||
*/
|
||||
async function waitForMigrations() {
|
||||
return new Promise((res) => setTimeout(res));
|
||||
|
||||
+64
-100
@@ -1,7 +1,9 @@
|
||||
/**
|
||||
* Simplified storage APIs with support for versioned fields, snapshots, metadata, and item definitions.
|
||||
* Simplified storage APIs with support for versioned fields, snapshots,
|
||||
* metadata, and item definitions.
|
||||
*
|
||||
* See [the guide](https://wxt.dev/storage.html) for more information.
|
||||
*
|
||||
* @module @wxt-dev/storage
|
||||
*/
|
||||
import { browser, type Browser } from '@wxt-dev/browser';
|
||||
@@ -689,7 +691,7 @@ export interface WxtStorage {
|
||||
* Get an item from storage, or return `null` if it doesn't exist.
|
||||
*
|
||||
* @example
|
||||
* await storage.getItem<number>("local:installDate");
|
||||
* await storage.getItem<number>('local:installDate');
|
||||
*/
|
||||
getItem<TValue>(
|
||||
key: StorageItemKey,
|
||||
@@ -702,11 +704,11 @@ export interface WxtStorage {
|
||||
): Promise<TValue | null>;
|
||||
|
||||
/**
|
||||
* Get multiple items from storage. The return order is guaranteed to be the same as the order
|
||||
* requested.
|
||||
* Get multiple items from storage. The return order is guaranteed to be the
|
||||
* same as the order requested.
|
||||
*
|
||||
* @example
|
||||
* await storage.getItems(["local:installDate", "session:someCounter"]);
|
||||
* await storage.getItems(['local:installDate', 'session:someCounter']);
|
||||
*/
|
||||
getItems(
|
||||
keys: Array<
|
||||
@@ -717,11 +719,11 @@ export interface WxtStorage {
|
||||
): Promise<Array<{ key: StorageItemKey; value: any }>>;
|
||||
|
||||
/**
|
||||
* Return an object containing metadata about the key. Object is stored at `key + "$"`. If value
|
||||
* is not an object, it returns an empty object.
|
||||
* Return an object containing metadata about the key. Object is stored at
|
||||
* `key + "$"`. If value is not an object, it returns an empty object.
|
||||
*
|
||||
* @example
|
||||
* await storage.getMeta("local:installDate");
|
||||
* await storage.getMeta('local:installDate');
|
||||
*/
|
||||
getMeta<T extends Record<string, unknown>>(key: StorageItemKey): Promise<T>;
|
||||
|
||||
@@ -736,22 +738,23 @@ export interface WxtStorage {
|
||||
): Promise<Array<{ key: StorageItemKey; meta: any }>>;
|
||||
|
||||
/**
|
||||
* Set a value in storage. Setting a value to `null` or `undefined` is equivalent to calling
|
||||
* `removeItem`.
|
||||
* Set a value in storage. Setting a value to `null` or `undefined` is
|
||||
* equivalent to calling `removeItem`.
|
||||
*
|
||||
* @example
|
||||
* await storage.setItem<number>("local:installDate", Date.now());
|
||||
* await storage.setItem<number>('local:installDate', Date.now());
|
||||
*/
|
||||
setItem<T>(key: StorageItemKey, value: T | null): Promise<void>;
|
||||
|
||||
/**
|
||||
* Set multiple values in storage. If a value is set to `null` or `undefined`, the key is removed.
|
||||
* Set multiple values in storage. If a value is set to `null` or `undefined`,
|
||||
* the key is removed.
|
||||
*
|
||||
* @example
|
||||
* await storage.setItem([
|
||||
* await storage.setItem([
|
||||
* { key: "local:installDate", value: Date.now() },
|
||||
* { key: "session:someCounter, value: 5 },
|
||||
* ]);
|
||||
* ]);
|
||||
*/
|
||||
setItems(
|
||||
values: Array<
|
||||
@@ -761,11 +764,11 @@ export interface WxtStorage {
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Sets metadata properties. If some properties are already set, but are not included in the
|
||||
* `properties` parameter, they will not be removed.
|
||||
* Sets metadata properties. If some properties are already set, but are not
|
||||
* included in the `properties` parameter, they will not be removed.
|
||||
*
|
||||
* @example
|
||||
* await storage.setMeta("local:installDate", { appVersion });
|
||||
* await storage.setMeta('local:installDate', { appVersion });
|
||||
*/
|
||||
setMeta<T extends Record<string, unknown>>(
|
||||
key: StorageItemKey,
|
||||
@@ -788,13 +791,11 @@ export interface WxtStorage {
|
||||
* Removes an item from storage.
|
||||
*
|
||||
* @example
|
||||
* await storage.removeItem("local:installDate");
|
||||
* await storage.removeItem('local:installDate');
|
||||
*/
|
||||
removeItem(key: StorageItemKey, opts?: RemoveItemOptions): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove a list of keys from storage.
|
||||
*/
|
||||
/** Remove a list of keys from storage. */
|
||||
removeItems(
|
||||
keys: Array<
|
||||
| StorageItemKey
|
||||
@@ -804,48 +805,41 @@ export interface WxtStorage {
|
||||
>,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Removes all items from the provided storage area.
|
||||
*/
|
||||
/** Removes all items from the provided storage area. */
|
||||
clear(base: StorageArea): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove the entire metadata for a key, or specific properties by name.
|
||||
*
|
||||
* @example
|
||||
* // Remove all metadata properties from the item
|
||||
* await storage.removeMeta("local:installDate");
|
||||
* // Remove all metadata properties from the item
|
||||
* await storage.removeMeta('local:installDate');
|
||||
*
|
||||
* // Remove only specific the "v" field
|
||||
* await storage.removeMeta("local:installDate", "v")
|
||||
* // Remove only specific the "v" field
|
||||
* await storage.removeMeta('local:installDate', 'v');
|
||||
*/
|
||||
removeMeta(
|
||||
key: StorageItemKey,
|
||||
properties?: string | string[],
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Return all the items in storage.
|
||||
*/
|
||||
/** Return all the items in storage. */
|
||||
snapshot(
|
||||
base: StorageArea,
|
||||
opts?: SnapshotOptions,
|
||||
): Promise<Record<string, unknown>>;
|
||||
|
||||
/**
|
||||
* Restores the results of `snapshot`. If new properties have been saved since the snapshot, they are
|
||||
* not overridden. Only values existing in the snapshot are overridden.
|
||||
* Restores the results of `snapshot`. If new properties have been saved since
|
||||
* the snapshot, they are not overridden. Only values existing in the snapshot
|
||||
* are overridden.
|
||||
*/
|
||||
restoreSnapshot(base: StorageArea, data: any): Promise<void>;
|
||||
|
||||
/**
|
||||
* Watch for changes to a specific key in storage.
|
||||
*/
|
||||
/** Watch for changes to a specific key in storage. */
|
||||
watch<T>(key: StorageItemKey, cb: WatchCallback<T | null>): Unwatch;
|
||||
|
||||
/**
|
||||
* Remove all watch listeners.
|
||||
*/
|
||||
/** Remove all watch listeners. */
|
||||
unwatch(): void;
|
||||
|
||||
/**
|
||||
@@ -894,61 +888,42 @@ export interface WxtStorageItem<
|
||||
TValue,
|
||||
TMetadata extends Record<string, unknown>,
|
||||
> {
|
||||
/**
|
||||
* The storage key passed when creating the storage item.
|
||||
*/
|
||||
/** The storage key passed when creating the storage item. */
|
||||
key: StorageItemKey;
|
||||
|
||||
/**
|
||||
* @deprecated Renamed to fallback, use it instead.
|
||||
*/
|
||||
/** @deprecated Renamed to fallback, use it instead. */
|
||||
defaultValue: TValue;
|
||||
|
||||
/**
|
||||
* The value provided by the `fallback` option.
|
||||
*/
|
||||
/** The value provided by the `fallback` option. */
|
||||
fallback: TValue;
|
||||
|
||||
/**
|
||||
* Get the latest value from storage.
|
||||
*/
|
||||
/** Get the latest value from storage. */
|
||||
getValue(): Promise<TValue>;
|
||||
|
||||
/**
|
||||
* Get metadata.
|
||||
*/
|
||||
/** Get metadata. */
|
||||
getMeta(): Promise<NullablePartial<TMetadata>>;
|
||||
|
||||
/**
|
||||
* Set the value in storage.
|
||||
*/
|
||||
/** Set the value in storage. */
|
||||
setValue(value: TValue): Promise<void>;
|
||||
|
||||
/**
|
||||
* Set metadata properties.
|
||||
*/
|
||||
/** Set metadata properties. */
|
||||
setMeta(properties: NullablePartial<TMetadata>): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove the value from storage.
|
||||
*/
|
||||
/** Remove the value from storage. */
|
||||
removeValue(opts?: RemoveItemOptions): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove all metadata or certain properties from metadata.
|
||||
*/
|
||||
/** Remove all metadata or certain properties from metadata. */
|
||||
removeMeta(properties?: string[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Listen for changes to the value in storage.
|
||||
*/
|
||||
/** Listen for changes to the value in storage. */
|
||||
watch(cb: WatchCallback<TValue>): Unwatch;
|
||||
|
||||
/**
|
||||
* If there are migrations defined on the storage item, migrate to the latest version.
|
||||
* If there are migrations defined on the storage item, migrate to the latest
|
||||
* version.
|
||||
*
|
||||
* **This function is ran automatically whenever the extension updates**, so you don't have to call it
|
||||
* manually.
|
||||
* **This function is ran automatically whenever the extension updates**, so
|
||||
* you don't have to call it manually.
|
||||
*/
|
||||
migrate(): Promise<void>;
|
||||
}
|
||||
@@ -957,14 +932,10 @@ export type StorageArea = 'local' | 'session' | 'sync' | 'managed';
|
||||
export type StorageItemKey = `${StorageArea}:${string}`;
|
||||
|
||||
export interface GetItemOptions<T> {
|
||||
/**
|
||||
* @deprecated Renamed to `fallback`, use it instead.
|
||||
*/
|
||||
/** @deprecated Renamed to `fallback`, use it instead. */
|
||||
defaultValue?: T;
|
||||
|
||||
/**
|
||||
* Default value returned when `getItem` would otherwise return `null`.
|
||||
*/
|
||||
/** Default value returned when `getItem` would otherwise return `null`. */
|
||||
fallback?: T;
|
||||
}
|
||||
|
||||
@@ -979,21 +950,17 @@ export interface RemoveItemOptions {
|
||||
|
||||
export interface SnapshotOptions {
|
||||
/**
|
||||
* Exclude a list of keys. The storage area prefix should be removed since the snapshot is for a
|
||||
* specific storage area already.
|
||||
* Exclude a list of keys. The storage area prefix should be removed since the
|
||||
* snapshot is for a specific storage area already.
|
||||
*/
|
||||
excludeKeys?: string[];
|
||||
}
|
||||
|
||||
export interface WxtStorageItemOptions<T> {
|
||||
/**
|
||||
* @deprecated Renamed to `fallback`, use it instead.
|
||||
*/
|
||||
/** @deprecated Renamed to `fallback`, use it instead. */
|
||||
defaultValue?: T;
|
||||
|
||||
/**
|
||||
* Default value returned when `getValue` would otherwise return `null`.
|
||||
*/
|
||||
/** Default value returned when `getValue` would otherwise return `null`. */
|
||||
fallback?: T;
|
||||
|
||||
/**
|
||||
@@ -1005,25 +972,26 @@ export interface WxtStorageItemOptions<T> {
|
||||
init?: () => T | Promise<T>;
|
||||
|
||||
/**
|
||||
* Provide a version number for the storage item to enable migrations. When changing the version
|
||||
* in the future, migration functions will be ran on application startup.
|
||||
* Provide a version number for the storage item to enable migrations. When
|
||||
* changing the version in the future, migration functions will be ran on
|
||||
* application startup.
|
||||
*/
|
||||
version?: number;
|
||||
|
||||
/**
|
||||
* A map of version numbers to the functions used to migrate the data to that version.
|
||||
* A map of version numbers to the functions used to migrate the data to that
|
||||
* version.
|
||||
*/
|
||||
migrations?: Record<number, (oldValue: any) => any>;
|
||||
|
||||
/**
|
||||
* Print debug logs, such as migration process.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
debug?: boolean;
|
||||
|
||||
/**
|
||||
* A callback function that runs on migration complete.
|
||||
*/
|
||||
/** A callback function that runs on migration complete. */
|
||||
onMigrationComplete?: (migratedValue: T, targetVersion: number) => void;
|
||||
}
|
||||
|
||||
@@ -1032,21 +1000,17 @@ export type StorageAreaChanges = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as `Partial`, but includes `| null`. It makes all the properties of an object optional and
|
||||
* nullable.
|
||||
* Same as `Partial`, but includes `| null`. It makes all the properties of an
|
||||
* object optional and nullable.
|
||||
*/
|
||||
type NullablePartial<T> = {
|
||||
[key in keyof T]+?: T[key] | undefined | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback called when a value in storage is changed.
|
||||
*/
|
||||
/** Callback called when a value in storage is changed. */
|
||||
export type WatchCallback<T> = (newValue: T, oldValue: T) => void;
|
||||
|
||||
/**
|
||||
* Call to remove a watch listener
|
||||
*/
|
||||
/** Call to remove a watch listener */
|
||||
export type Unwatch = () => void;
|
||||
|
||||
export class MigrationError extends Error {
|
||||
|
||||
@@ -33,21 +33,22 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "buildc --deps-only -- check"
|
||||
"check": "buildc --deps-only -- check",
|
||||
"prepack": "pnpm build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"unocss": ">=0.60.0",
|
||||
"wxt": ">=0.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"oxlint": "^1.43.0",
|
||||
"publint": "^0.3.17",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"unocss": "^0.64.0 || ^0.65.0 || ^65.0.0 || ^66.0.0",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"defu": "^6.1.4",
|
||||
"fast-glob": "^3.3.3"
|
||||
"tinyglobby": "^0.2.15"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,12 +38,11 @@ export default defineWxtModule<UnoCSSOptions>({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Options for the UnoCSS module
|
||||
*/
|
||||
/** Options for the UnoCSS module */
|
||||
export interface UnoCSSOptions<Theme extends object = object> {
|
||||
/**
|
||||
* Enable UnoCSS
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enabled?: boolean;
|
||||
@@ -51,12 +50,17 @@ export interface UnoCSSOptions<Theme extends object = object> {
|
||||
* List of entrypoint names that UnoCSS is not used in. By default, the UnoCSS
|
||||
* vite plugin is added to all build steps, but this option is used to exclude
|
||||
* it from specific builds.
|
||||
* @example ["popup", "options"]
|
||||
* @default []
|
||||
*
|
||||
* @example
|
||||
* {undefined} ('popup',
|
||||
* 'options');
|
||||
*
|
||||
* @default [ ]
|
||||
*/
|
||||
excludeEntrypoints?: string[];
|
||||
/**
|
||||
* The path to your `unocss.config.ts` file, relative to <rootDir>, or inline configuration.
|
||||
* The path to your `unocss.config.ts` file, relative to <rootDir>, or inline
|
||||
* configuration.
|
||||
*/
|
||||
configOrPath?: Parameters<typeof UnoCSS<Theme>>[0];
|
||||
}
|
||||
|
||||
@@ -38,15 +38,16 @@
|
||||
"dev": "wxt",
|
||||
"check": "pnpm build && check",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepare": "buildc --deps-only -- wxt prepare"
|
||||
"prepare": "buildc --deps-only -- wxt prepare",
|
||||
"prepack": "pnpm build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webextension-polyfill": "*",
|
||||
"wxt": ">=0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/webextension-polyfill": "^0.12.4",
|
||||
"publint": "^0.3.17",
|
||||
"@types/webextension-polyfill": "^0.12.5",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"webextension-polyfill": "^0.12.0",
|
||||
"wxt": "workspace:*"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"react-dom": "^19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.13",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@wxt-dev/auto-icons": "workspace:*",
|
||||
"@wxt-dev/unocss": "workspace:*",
|
||||
|
||||
@@ -1,5 +1,119 @@
|
||||
# Changelog
|
||||
|
||||
## v0.20.20
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.19...wxt-v0.20.20)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Unlisted script return values broken with Vite 8 sourcemaps ([#2197](https://github.com/wxt-dev/wxt/pull/2197))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Add Dymo extension ID to UsingWxtSection.vue ([#2187](https://github.com/wxt-dev/wxt/pull/2187))
|
||||
- Add Extension Rank Checker to the list of extensions ([#2193](https://github.com/wxt-dev/wxt/pull/2193))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps:** Add vite-node 6 support ([64b68671](https://github.com/wxt-dev/wxt/commit/64b68671))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Joseph Hu ([@KiJO94GO](https://github.com/KiJO94GO))
|
||||
- FJRG2007 ツ ([@FJRG2007](https://github.com/FJRG2007))
|
||||
- Hampus Tågerud ([@hampustagerud](https://github.com/hampustagerud))
|
||||
|
||||
## v0.20.19
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.18...wxt-v0.20.19)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Vite 8 support ([bfd4af5d](https://github.com/wxt-dev/wxt/commit/bfd4af5d))
|
||||
|
||||
### 🔥 Performance
|
||||
|
||||
- Use filter to improve plugin performance with rolldown ([#1787](https://github.com/wxt-dev/wxt/pull/1787))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- List eslint 10 as supported ([55dc263d](https://github.com/wxt-dev/wxt/commit/55dc263d))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Added "Scrape Similar" to the homepage ([#2158](https://github.com/wxt-dev/wxt/pull/2158))
|
||||
- Added "isTrust" to the homepage ([#2161](https://github.com/wxt-dev/wxt/pull/2161))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add `prettier-plugin-jsdoc` to project ([#2171](https://github.com/wxt-dev/wxt/pull/2171))
|
||||
- Replace fast-glob and ora with lighter alternatives ([#2173](https://github.com/wxt-dev/wxt/pull/2173))
|
||||
- **deps:** Upgrade deps ([#2175](https://github.com/wxt-dev/wxt/pull/2175))
|
||||
- Replace `fs-extra` with `node:fs/promises` ([#2174](https://github.com/wxt-dev/wxt/pull/2174))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Florian Metz ([@Timeraa](https://github.com/Timeraa))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
|
||||
- Etoome ([@etoome](https://github.com/etoome))
|
||||
- Kuba ([@zizzfizzix](https://github.com/zizzfizzix))
|
||||
|
||||
## v0.20.18
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.17...wxt-v0.20.18)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Create tests for all func of `network.ts` ([#2132](https://github.com/wxt-dev/wxt/pull/2132))
|
||||
- Use Navigation API for location change detection with polling fallback ([#2136](https://github.com/wxt-dev/wxt/pull/2136))
|
||||
- **modules:** Add support for augumenting entrypoint options ([#2149](https://github.com/wxt-dev/wxt/pull/2149))
|
||||
- New `@wxt-dev/is-background` package ([#2152](https://github.com/wxt-dev/wxt/pull/2152))
|
||||
- Add `globalName` entrypoint option. ([#2017](https://github.com/wxt-dev/wxt/pull/2017))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Add `getAppConfig` as an alias to `useAppConfig` ([#2144](https://github.com/wxt-dev/wxt/pull/2144))
|
||||
- Enable mv3 dev mode with firefox 147 ([#2135](https://github.com/wxt-dev/wxt/pull/2135))
|
||||
- **types:** Add type safety to `browser.runtime.executeScript` `files` option ([#2142](https://github.com/wxt-dev/wxt/pull/2142))
|
||||
- **types:** Include CSS entrypoints in PublicPath generation ([#2150](https://github.com/wxt-dev/wxt/pull/2150))
|
||||
- Fix submit command ([#2157](https://github.com/wxt-dev/wxt/pull/2157))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Added "Glossy New Tab" to the homepage ([#2133](https://github.com/wxt-dev/wxt/pull/2133))
|
||||
- Add All API Hub to showcase extensions ([#2137](https://github.com/wxt-dev/wxt/pull/2137))
|
||||
- Add `wxt-local-analytics` to community resources ([#2141](https://github.com/wxt-dev/wxt/pull/2141))
|
||||
- Add deep outline to modules page to show recipes ([653608c9](https://github.com/wxt-dev/wxt/commit/653608c9))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add missing `async` to network.ts ([#2107](https://github.com/wxt-dev/wxt/pull/2107))
|
||||
- Make type for `__ENTRYPOINT__` of vitest.globalSetup.ts ([#2128](https://github.com/wxt-dev/wxt/pull/2128))
|
||||
- Remove unnecessary `@ts-ignore` of `builders/vite` index.ts ([#2127](https://github.com/wxt-dev/wxt/pull/2127))
|
||||
- Rename `zipdir` to `zipDir` ([#2126](https://github.com/wxt-dev/wxt/pull/2126))
|
||||
- Simplify imports in `wxt/e2e` ([#2122](https://github.com/wxt-dev/wxt/pull/2122))
|
||||
- Remove `@ts-expect-error` from manifest.test.ts and fix typo ([#2123](https://github.com/wxt-dev/wxt/pull/2123))
|
||||
|
||||
### 🤖 CI
|
||||
|
||||
- Fix VHS action ([#2155](https://github.com/wxt-dev/wxt/pull/2155))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Jonathan Viney ([@jviney](https://github.com/jviney))
|
||||
- Tam Dang ([@dahomita](https://github.com/dahomita))
|
||||
- Nick Doan ([@nickbar01234](https://github.com/nickbar01234))
|
||||
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
|
||||
- Anthony Ciccarello ([@aciccarello](https://github.com/aciccarello))
|
||||
- Henrik Wenz ([@HaNdTriX](https://github.com/HaNdTriX))
|
||||
- Sheng Zhang ([@Arktomson](https://github.com/Arktomson))
|
||||
- Oleksii ([@Aler1x](https://github.com/Aler1x))
|
||||
- Qixing-jk <vq3d5d8c@duck.com>
|
||||
- Muzammil ([@oyzamil](https://github.com/oyzamil))
|
||||
|
||||
## v0.20.17
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.16...wxt-v0.20.17)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* A alias around `publish-extension` that is always installed on the path without having to install
|
||||
* `publish-browser-extension` as a direct dependency (like for PNPM, which doesn't link
|
||||
* sub-dependency binaries to "node_modules/.bin")
|
||||
* A alias around `publish-extension` that is always installed on the path
|
||||
* without having to install `publish-browser-extension` as a direct dependency
|
||||
* (like for PNPM, which doesn't link sub-dependency binaries to
|
||||
* "node_modules/.bin")
|
||||
*/
|
||||
import 'publish-browser-extension/cli';
|
||||
await import('publish-browser-extension/cli');
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { TestProject, WXT_PACKAGE_DIR } from '../utils';
|
||||
import spawn from 'nano-spawn';
|
||||
import glob from 'fast-glob';
|
||||
import { mkdir, writeJson } from 'fs-extra';
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import { glob } from 'tinyglobby';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { TestProject, WXT_PACKAGE_DIR } from '../utils';
|
||||
|
||||
describe('Init command', () => {
|
||||
it('should download and create a template', async () => {
|
||||
@@ -21,6 +21,7 @@ describe('Init command', () => {
|
||||
cwd: project.root,
|
||||
onlyFiles: true,
|
||||
dot: true,
|
||||
expandDirectories: false,
|
||||
});
|
||||
|
||||
expect(files.sort()).toMatchInlineSnapshot(`
|
||||
@@ -52,7 +53,7 @@ describe('Init command', () => {
|
||||
it('should throw an error if the directory is not empty', async () => {
|
||||
const project = new TestProject();
|
||||
await mkdir(project.root, { recursive: true });
|
||||
await writeJson(project.resolvePath('package.json'), {});
|
||||
await writeFile(project.resolvePath('package.json'), JSON.stringify({}));
|
||||
|
||||
await expect(() =>
|
||||
spawn(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
import type { InlineConfig, UnlistedScriptEntrypoint } from '../../src';
|
||||
import { readFile } from 'fs-extra';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { normalizePath } from '../../src';
|
||||
|
||||
describe('Module Helpers', () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
import extract from 'extract-zip';
|
||||
import spawn from 'nano-spawn';
|
||||
import { readFile, writeFile } from 'fs-extra';
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
|
||||
process.env.WXT_PNPM_IGNORE_WORKSPACE = 'true';
|
||||
|
||||
|
||||
+21
-21
@@ -1,8 +1,8 @@
|
||||
import glob from 'fast-glob';
|
||||
import fs, { mkdir } from 'fs-extra';
|
||||
import merge from 'lodash.merge';
|
||||
import spawn from 'nano-spawn';
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import { dirname, relative, resolve } from 'path';
|
||||
import { glob } from 'tinyglobby';
|
||||
import {
|
||||
InlineConfig,
|
||||
UserConfig,
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
zip,
|
||||
} from '../src';
|
||||
import { normalizePath } from '../src/core/utils';
|
||||
import { pathExists, readJson } from '../src/core/utils/fs';
|
||||
|
||||
// Run "pnpm wxt" to use the "wxt" dev script, not the "wxt" binary from the
|
||||
// wxt package. This uses the TS files instead of the compiled JS package
|
||||
@@ -59,9 +60,7 @@ export class TestProject {
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a `wxt.config.ts` to the project with specific contents.
|
||||
*/
|
||||
/** Add a `wxt.config.ts` to the project with specific contents. */
|
||||
setConfigFileConfig(config: UserConfig = {}) {
|
||||
this.config = config;
|
||||
this.files.push([
|
||||
@@ -109,9 +108,7 @@ export class TestProject {
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call `path.resolve` relative to the project's root directory.
|
||||
*/
|
||||
/** Call `path.resolve` relative to the project's root directory. */
|
||||
resolvePath(...path: string[]): string {
|
||||
return resolve(this.root, ...path);
|
||||
}
|
||||
@@ -123,8 +120,8 @@ export class TestProject {
|
||||
const [name, content] = file;
|
||||
const filePath = this.resolvePath(name);
|
||||
const fileDir = dirname(filePath);
|
||||
await fs.ensureDir(fileDir);
|
||||
await fs.writeFile(filePath, content ?? '', 'utf-8');
|
||||
await mkdir(fileDir, { recursive: true });
|
||||
await writeFile(filePath, content ?? '', 'utf-8');
|
||||
}
|
||||
|
||||
// Only install dependencies if the project has custom ones - otherwise the
|
||||
@@ -141,11 +138,11 @@ export class TestProject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all the files from the test project's `.output` directory and combine them into a string
|
||||
* that can be used in a snapshot.
|
||||
* Read all the files from the test project's `.output` directory and combine
|
||||
* them into a string that can be used in a snapshot.
|
||||
*
|
||||
* Optionally, provide a list of filenames whose content is not printed (because it's inconsistent
|
||||
* or not relevant to a test).
|
||||
* Optionally, provide a list of filenames whose content is not printed
|
||||
* (because it's inconsistent or not relevant to a test).
|
||||
*/
|
||||
serializeOutput(ignoreContentsOfFilenames?: string[]): Promise<string> {
|
||||
return this.serializeDir('.output', ignoreContentsOfFilenames);
|
||||
@@ -154,8 +151,8 @@ export class TestProject {
|
||||
/**
|
||||
* Deeply print the filename and contents of all files in a directory.
|
||||
*
|
||||
* Optionally, provide a list of filenames whose content is not printed (because it's inconsistent
|
||||
* or not relevant to a test).
|
||||
* Optionally, provide a list of filenames whose content is not printed
|
||||
* (because it's inconsistent or not relevant to a test).
|
||||
*/
|
||||
private async serializeDir(
|
||||
dir: string,
|
||||
@@ -164,6 +161,7 @@ export class TestProject {
|
||||
const outputFiles = await glob('**/*', {
|
||||
cwd: this.resolvePath(dir),
|
||||
ignore: ['**/node_modules', '**/.output'],
|
||||
expandDirectories: false,
|
||||
});
|
||||
outputFiles.sort();
|
||||
const fileContents = [];
|
||||
@@ -179,24 +177,26 @@ export class TestProject {
|
||||
|
||||
/**
|
||||
* @param path An absolute path to a file or a path relative to the root.
|
||||
* @param ignoreContents An optional boolean that, when true, causes this function to not print
|
||||
* the file contents.
|
||||
* @param ignoreContents An optional boolean that, when true, causes this
|
||||
* function to not print the file contents.
|
||||
*/
|
||||
async serializeFile(path: string, ignoreContents?: boolean): Promise<string> {
|
||||
const absolutePath = this.resolvePath(path);
|
||||
return [
|
||||
normalizePath(relative(this.root, absolutePath)),
|
||||
ignoreContents ? '<contents-ignored>' : await fs.readFile(absolutePath),
|
||||
ignoreContents
|
||||
? '<contents-ignored>'
|
||||
: await readFile(absolutePath, 'utf-8'),
|
||||
].join(`\n${''.padEnd(40, '-')}\n`);
|
||||
}
|
||||
|
||||
pathExists(...path: string[]): Promise<boolean> {
|
||||
return fs.pathExists(this.resolvePath(...path));
|
||||
return pathExists(this.resolvePath(...path));
|
||||
}
|
||||
|
||||
getOutputManifest(
|
||||
path: string = '.output/chrome-mv3/manifest.json',
|
||||
): Promise<any> {
|
||||
return fs.readJson(this.resolvePath(path));
|
||||
return readJson(this.resolvePath(path));
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -1,3 +1,4 @@
|
||||
// oxlint-disable-next-line eslint(no-shadow-restricted-names)
|
||||
declare namespace globalThis {
|
||||
var __ENTRYPOINT__: string;
|
||||
}
|
||||
|
||||
+17
-19
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.20.17",
|
||||
"version": "0.20.20",
|
||||
"description": "⚡ Next-gen Web Extension Framework",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -25,45 +25,44 @@
|
||||
"@wxt-dev/storage": "workspace:^1.0.0",
|
||||
"async-mutex": "^0.5.0",
|
||||
"c12": "^3.3.3",
|
||||
"cac": "^6.7.14",
|
||||
"cac": "^6.7.14 || ^7.0.0",
|
||||
"chokidar": "^5.0.0",
|
||||
"ci-info": "^4.4.0",
|
||||
"consola": "^3.4.2",
|
||||
"defu": "^6.1.4",
|
||||
"dotenv": "^17.2.4",
|
||||
"dotenv": "^17.3.1",
|
||||
"dotenv-expand": "^12.0.3",
|
||||
"esbuild": "^0.27.1",
|
||||
"fast-glob": "^3.3.3",
|
||||
"filesize": "^11.0.13",
|
||||
"fs-extra": "^11.3.3",
|
||||
"get-port-please": "^3.2.0",
|
||||
"giget": "^1.2.3 || ^2.0.0 || ^3.0.0",
|
||||
"hookable": "^6.0.1",
|
||||
"import-meta-resolve": "^4.2.0",
|
||||
"is-wsl": "^3.1.0",
|
||||
"is-wsl": "^3.1.1",
|
||||
"json5": "^2.2.3",
|
||||
"jszip": "^3.10.1",
|
||||
"linkedom": "^0.18.12",
|
||||
"magicast": "^0.5.2",
|
||||
"minimatch": "^10.1.2",
|
||||
"minimatch": "^10.2.4",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"nanospinner": "^1.2.2",
|
||||
"normalize-path": "^3.0.0",
|
||||
"nypm": "^0.6.5",
|
||||
"ohash": "^2.0.11",
|
||||
"open": "^11.0.0",
|
||||
"ora": "^9.3.0",
|
||||
"perfect-debounce": "^2.1.0",
|
||||
"picocolors": "^1.1.1",
|
||||
"prompts": "^2.4.2",
|
||||
"publish-browser-extension": "^2.3.0 || ^3.0.2 || ^4.0.0",
|
||||
"publish-browser-extension": "^2.3.0 || ^3.0.2 || ^4.0.4",
|
||||
"scule": "^1.3.0",
|
||||
"unimport": "^3.13.1 || ^4.0.0 || ^5.0.0",
|
||||
"vite": "^5.4.19 || ^6.3.4 || ^7.0.0",
|
||||
"vite-node": "^3.2.4 || ^5.0.0",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"unimport": "^3.13.1 || ^4.0.0 || ^5.0.0 || ^6.0.0",
|
||||
"vite": "^5.4.19 || ^6.3.4 || ^7.0.0 || ^8.0.0-0",
|
||||
"vite-node": "^3.2.4 || ^5.0.0 || ^6.0.0",
|
||||
"web-ext-run": "^0.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0"
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"eslint": {
|
||||
@@ -71,18 +70,17 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^10.2.0",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@faker-js/faker": "^10.3.0",
|
||||
"@types/lodash.merge": "^4.6.9",
|
||||
"@types/node": "^20.17.6",
|
||||
"@types/normalize-path": "^3.0.2",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"eslint": "^9.38.0",
|
||||
"eslint": "^10.0.0",
|
||||
"extract-zip": "^2.0.1",
|
||||
"happy-dom": "^20.5.0",
|
||||
"happy-dom": "^20.8.3",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"oxlint": "^1.43.0",
|
||||
"publint": "^0.3.17",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18",
|
||||
"vitest-plugin-random-seed": "^1.1.2"
|
||||
|
||||
@@ -1,25 +1,32 @@
|
||||
/**
|
||||
* Contains the `browser` export which you should use to access the extension APIs in your project:
|
||||
* Contains the `browser` export which you should use to access the extension
|
||||
* APIs in your project:
|
||||
*
|
||||
* ```ts
|
||||
* import { browser } from 'wxt/browser';
|
||||
*
|
||||
* browser.runtime.onInstalled.addListener(() => {
|
||||
* // ...
|
||||
* })
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @module wxt/browser
|
||||
*/
|
||||
import { browser as _browser, type Browser } from '@wxt-dev/browser';
|
||||
import type { ScriptPublicPath } from './utils/inject-script';
|
||||
|
||||
/**
|
||||
* This interface is empty because it is generated per-project when running `wxt prepare`. See:
|
||||
* This interface is empty because it is generated per-project when running `wxt
|
||||
* prepare`. See:
|
||||
*
|
||||
* - `.wxt/types/paths.d.ts`
|
||||
*/
|
||||
export interface WxtRuntime {}
|
||||
|
||||
/**
|
||||
* This interface is empty because it is generated per-project when running `wxt prepare`. See:
|
||||
* This interface is empty because it is generated per-project when running `wxt
|
||||
* prepare`. See:
|
||||
*
|
||||
* - `.wxt/types/i18n.d.ts`
|
||||
*/
|
||||
export interface WxtI18n {}
|
||||
@@ -36,9 +43,7 @@ type InjectionResult<Result> = Array<
|
||||
|
||||
export interface WxtScripting {
|
||||
executeScript: {
|
||||
/**
|
||||
* @see {@link Browser.scripting.executeScript}
|
||||
*/
|
||||
/** @see {@link Browser.scripting.executeScript} */
|
||||
<Args extends any[], Result>(
|
||||
injection: ScriptInjection<Args, Result>,
|
||||
): Promise<InjectionResult<Result>>;
|
||||
|
||||
@@ -8,7 +8,8 @@ import { registerWxt } from '../core/wxt';
|
||||
import spawn from 'nano-spawn';
|
||||
|
||||
/**
|
||||
* Wrap an action handler to add a timer, error handling, and maybe enable debug mode.
|
||||
* Wrap an action handler to add a timer, error handling, and maybe enable debug
|
||||
* mode.
|
||||
*/
|
||||
export function wrapAction(
|
||||
cb: (
|
||||
@@ -54,8 +55,8 @@ export function wrapAction(
|
||||
}
|
||||
|
||||
/**
|
||||
* Array flags, when not passed, are either `undefined` or `[undefined]`. This function filters out
|
||||
* the
|
||||
* Array flags, when not passed, are either `undefined` or `[undefined]`. This
|
||||
* function filters out the
|
||||
*/
|
||||
export function getArrayFromFlags<T>(
|
||||
flags: any,
|
||||
|
||||
@@ -3,17 +3,18 @@ import { internalBuild } from './utils/building';
|
||||
import { registerWxt } from './wxt';
|
||||
|
||||
/**
|
||||
* Bundles the extension for production. Returns a promise of the build result. Discovers the `wxt.config.ts` file in
|
||||
* the root directory, and merges that config with what is passed in.
|
||||
* Bundles the extension for production. Returns a promise of the build result.
|
||||
* Discovers the `wxt.config.ts` file in the root directory, and merges that
|
||||
* config with what is passed in.
|
||||
*
|
||||
* @example
|
||||
* // Use config from `wxt.config.ts`
|
||||
* const res = await build()
|
||||
* // Use config from `wxt.config.ts`
|
||||
* const res = await build();
|
||||
*
|
||||
* // or override config `from wxt.config.ts`
|
||||
* const res = await build({
|
||||
* // Override config...
|
||||
* })
|
||||
* // or override config `from wxt.config.ts`
|
||||
* const res = await build({
|
||||
* // Override config...
|
||||
* });
|
||||
*/
|
||||
export async function build(config?: InlineConfig): Promise<BuildOutput> {
|
||||
await registerWxt('build', config);
|
||||
|
||||
@@ -26,7 +26,7 @@ import { ViteNodeRunner } from 'vite-node/client';
|
||||
import { installSourcemapsSupport } from 'vite-node/source-map';
|
||||
import { createExtensionEnvironment } from '../../utils/environments';
|
||||
import { dirname, extname, join, relative } from 'node:path';
|
||||
import fs from 'fs-extra';
|
||||
import { mkdir, readdir, rename, rmdir, stat } from 'node:fs/promises';
|
||||
import { normalizePath } from '../../utils';
|
||||
|
||||
export async function createViteBuilder(
|
||||
@@ -37,7 +37,8 @@ export async function createViteBuilder(
|
||||
const vite = await import('vite');
|
||||
|
||||
/**
|
||||
* Returns the base vite config shared by all builds based on the inline and user config.
|
||||
* Returns the base vite config shared by all builds based on the inline and
|
||||
* user config.
|
||||
*/
|
||||
const getBaseConfig = async (baseConfigOptions?: {
|
||||
excludeAnalysisPlugin?: boolean;
|
||||
@@ -102,7 +103,8 @@ export async function createViteBuilder(
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the basic config for building an entrypoint in [lib mode](https://vitejs.dev/guide/build.html#library-mode).
|
||||
* Return the basic config for building an entrypoint in [lib
|
||||
* mode](https://vitejs.dev/guide/build.html#library-mode).
|
||||
*/
|
||||
const getLibModeConfig = (entrypoint: Entrypoint): vite.InlineConfig => {
|
||||
const entry = getRollupEntry(entrypoint);
|
||||
@@ -177,7 +179,8 @@ export async function createViteBuilder(
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the basic config for building multiple entrypoints in [multi-page mode](https://vitejs.dev/guide/build.html#multi-page-app).
|
||||
* Return the basic config for building multiple entrypoints in [multi-page
|
||||
* mode](https://vitejs.dev/guide/build.html#multi-page-app).
|
||||
*/
|
||||
const getMultiPageConfig = (entrypoints: Entrypoint[]): vite.InlineConfig => {
|
||||
const htmlEntrypoints = new Set(
|
||||
@@ -210,7 +213,8 @@ export async function createViteBuilder(
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the basic config for building a single CSS entrypoint in [multi-page mode](https://vitejs.dev/guide/build.html#multi-page-app).
|
||||
* Return the basic config for building a single CSS entrypoint in [multi-page
|
||||
* mode](https://vitejs.dev/guide/build.html#multi-page-app).
|
||||
*/
|
||||
const getCssConfig = (entrypoint: Entrypoint): vite.InlineConfig => {
|
||||
return {
|
||||
@@ -387,8 +391,8 @@ function getBuildOutputChunks(
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input module ID (virtual or real file) for an entrypoint. The returned string should
|
||||
* be passed as an input to rollup.
|
||||
* Returns the input module ID (virtual or real file) for an entrypoint. The
|
||||
* returned string should be passed as an input to rollup.
|
||||
*/
|
||||
function getRollupEntry(entrypoint: Entrypoint): string {
|
||||
let virtualEntrypointType: VirtualEntrypointType | undefined;
|
||||
@@ -413,15 +417,17 @@ function getRollupEntry(entrypoint: Entrypoint): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the HTML files output by a multipage build are in the correct location. This does two
|
||||
* things:
|
||||
* Ensures the HTML files output by a multipage build are in the correct
|
||||
* location. This does two things:
|
||||
*
|
||||
* 1. Moves the HTML files to their final location at `<outDir>/<entrypoint.name>.html`.
|
||||
* 2. Updates the bundle so it summarizes the files correctly in the returned build output.
|
||||
* 1. Moves the HTML files to their final location at
|
||||
* `<outDir>/<entrypoint.name>.html`.
|
||||
* 2. Updates the bundle so it summarizes the files correctly in the returned build
|
||||
* output.
|
||||
*
|
||||
* Assets (JS and CSS) are output to the `<outDir>/assets` directory, and don't need to be modified.
|
||||
* HTML files access them via absolute URLs, so we don't need to update any import paths in the HTML
|
||||
* files either.
|
||||
* Assets (JS and CSS) are output to the `<outDir>/assets` directory, and don't
|
||||
* need to be modified. HTML files access them via absolute URLs, so we don't
|
||||
* need to update any import paths in the HTML files either.
|
||||
*/
|
||||
async function moveHtmlFiles(
|
||||
config: ResolvedConfig,
|
||||
@@ -449,8 +455,8 @@ async function moveHtmlFiles(
|
||||
);
|
||||
const oldAbsPath = join(config.outDir, oldBundlePath);
|
||||
const newAbsPath = join(config.outDir, newBundlePath);
|
||||
await fs.ensureDir(dirname(newAbsPath));
|
||||
await fs.move(oldAbsPath, newAbsPath, { overwrite: true });
|
||||
await mkdir(dirname(newAbsPath), { recursive: true });
|
||||
await rename(oldAbsPath, newAbsPath);
|
||||
|
||||
return {
|
||||
...chunk,
|
||||
@@ -465,21 +471,19 @@ async function moveHtmlFiles(
|
||||
return movedChunks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively remove all directories that are empty/
|
||||
*/
|
||||
/** Recursively remove all directories that are empty/ */
|
||||
export async function removeEmptyDirs(dir: string): Promise<void> {
|
||||
const files = await fs.readdir(dir);
|
||||
const files = await readdir(dir);
|
||||
for (const file of files) {
|
||||
const filePath = join(dir, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
const stats = await stat(filePath);
|
||||
if (stats.isDirectory()) {
|
||||
await removeEmptyDirs(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.rmdir(dir);
|
||||
await rmdir(dir);
|
||||
} catch {
|
||||
// noop on failure - this means the directory was not empty.
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { iifeFooter } from '../iifeFooter';
|
||||
|
||||
interface OutputChunk {
|
||||
type: 'chunk';
|
||||
code: string;
|
||||
isEntry: boolean;
|
||||
}
|
||||
|
||||
interface OutputAsset {
|
||||
type: 'asset';
|
||||
source: string;
|
||||
}
|
||||
|
||||
type OutputBundle = Record<string, OutputChunk | OutputAsset>;
|
||||
|
||||
function dedent(code: string) {
|
||||
const lines = code.trim().split('\n');
|
||||
return lines.map((line) => line.trimStart()).join('\n');
|
||||
}
|
||||
|
||||
function createBundle(code: string): OutputBundle {
|
||||
return {
|
||||
'entry.js': {
|
||||
type: 'chunk',
|
||||
code: dedent(code),
|
||||
isEntry: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getCode(bundle: OutputBundle): string {
|
||||
const entry = bundle['entry.js'];
|
||||
|
||||
if (entry.type !== 'chunk') {
|
||||
throw new Error('expected chunk');
|
||||
}
|
||||
|
||||
return entry.code;
|
||||
}
|
||||
|
||||
function runPlugin(name: string, bundle: OutputBundle) {
|
||||
const plugin = iifeFooter(name);
|
||||
// @ts-expect-error -- calling the hook directly
|
||||
plugin.generateBundle(undefined, bundle);
|
||||
}
|
||||
|
||||
describe('IIFE return value plugin', () => {
|
||||
it('should append return value when no sourcemap comment', () => {
|
||||
const bundle = createBundle(`
|
||||
var foo = (function(){return 1})();
|
||||
`);
|
||||
|
||||
runPlugin('foo', bundle);
|
||||
|
||||
expect(getCode(bundle)).toBe(
|
||||
dedent(`
|
||||
var foo = (function(){return 1})();
|
||||
foo;
|
||||
`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should insert return value before sourcemap comment', () => {
|
||||
const bundle = createBundle(`
|
||||
var foo = (function(){return 1})();
|
||||
//# ${'sourceMappingURL'}=foo.js.map
|
||||
`);
|
||||
|
||||
runPlugin('foo', bundle);
|
||||
|
||||
expect(getCode(bundle)).toBe(
|
||||
dedent(`
|
||||
var foo = (function(){return 1})();
|
||||
foo;
|
||||
//# ${'sourceMappingURL'}=foo.js.map
|
||||
`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should insert return value before inline sourcemap', () => {
|
||||
const bundle = createBundle(`
|
||||
var foo = (function(){return 1})();
|
||||
//# ${'sourceMappingURL'}=data:application/json;base64,abc123
|
||||
`);
|
||||
|
||||
runPlugin('foo', bundle);
|
||||
|
||||
expect(getCode(bundle)).toBe(
|
||||
dedent(`
|
||||
var foo = (function(){return 1})();
|
||||
foo;
|
||||
//# ${'sourceMappingURL'}=data:application/json;base64,abc123
|
||||
`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip non-entry chunks', () => {
|
||||
const bundle = createBundle('var x = 1;');
|
||||
(bundle['entry.js'] as OutputChunk).isEntry = false;
|
||||
|
||||
runPlugin('x', bundle);
|
||||
|
||||
expect(getCode(bundle)).toBe('var x = 1;');
|
||||
});
|
||||
|
||||
it('should skip assets', () => {
|
||||
const bundle: OutputBundle = {
|
||||
'style.css': {
|
||||
type: 'asset',
|
||||
source: 'body {}',
|
||||
} satisfies OutputAsset,
|
||||
};
|
||||
|
||||
runPlugin('style', bundle);
|
||||
|
||||
expect((bundle['style.css'] as OutputAsset).source).toBe('body {}');
|
||||
});
|
||||
});
|
||||
@@ -15,9 +15,7 @@ export function bundleAnalysis(config: ResolvedConfig): vite.Plugin {
|
||||
}) as vite.Plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal FOR TESTING ONLY.
|
||||
*/
|
||||
/** @internal FOR TESTING ONLY. */
|
||||
export function resetBundleIncrement() {
|
||||
increment = 0;
|
||||
}
|
||||
|
||||
@@ -3,14 +3,16 @@ import { Entrypoint, ResolvedConfig } from '../../../../types';
|
||||
import { getEntrypointBundlePath } from '../../../utils/entrypoints';
|
||||
|
||||
/**
|
||||
* Rename CSS entrypoint outputs to ensure a JS file is not generated, and that the CSS file is
|
||||
* placed in the correct place.
|
||||
* Rename CSS entrypoint outputs to ensure a JS file is not generated, and that
|
||||
* the CSS file is placed in the correct place.
|
||||
*
|
||||
* It:
|
||||
*
|
||||
* 1. Renames CSS files to their final paths
|
||||
* 2. Removes the JS file that get's output by lib mode
|
||||
*
|
||||
* THIS PLUGIN SHOULD ONLY BE APPLIED TO CSS LIB MODE BUILDS. It should not be added to every build.
|
||||
* THIS PLUGIN SHOULD ONLY BE APPLIED TO CSS LIB MODE BUILDS. It should not be
|
||||
* added to every build.
|
||||
*/
|
||||
export function cssEntrypoints(
|
||||
entrypoint: Entrypoint,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* Overrides definitions for `import.meta.*`
|
||||
*
|
||||
* - `import.meta.url`: Without this, background service workers crash trying to access
|
||||
* `document.location`, see https://github.com/wxt-dev/wxt/issues/392
|
||||
* - `import.meta.url`: Without this, background service workers crash trying to
|
||||
* access `document.location`, see https://github.com/wxt-dev/wxt/issues/392
|
||||
*/
|
||||
export function defineImportMeta() {
|
||||
return {
|
||||
|
||||
@@ -10,7 +10,8 @@ import { hash } from 'ohash';
|
||||
const inlineScriptContents: Record<string, string> = {};
|
||||
|
||||
/**
|
||||
* Pre-renders the HTML entrypoints when building the extension to connect to the dev server.
|
||||
* Pre-renders the HTML entrypoints when building the extension to connect to
|
||||
* the dev server.
|
||||
*/
|
||||
export function devHtmlPrerender(
|
||||
config: ResolvedConfig,
|
||||
@@ -40,32 +41,32 @@ export function devHtmlPrerender(
|
||||
},
|
||||
// Convert scripts like src="./main.tsx" -> src="http://localhost:3000/entrypoints/popup/main.tsx"
|
||||
// before the paths are replaced with their bundled path
|
||||
transform(code, id) {
|
||||
if (
|
||||
config.command !== 'serve' ||
|
||||
server == null ||
|
||||
!id.endsWith('.html')
|
||||
)
|
||||
return;
|
||||
transform: {
|
||||
filter: {
|
||||
id: /\.html$/,
|
||||
},
|
||||
handler(code, id) {
|
||||
if (config.command !== 'serve' || server == null) return;
|
||||
|
||||
const { document } = parseHTML(code);
|
||||
const { document } = parseHTML(code);
|
||||
|
||||
const _pointToDevServer = (querySelector: string, attr: string) =>
|
||||
pointToDevServer(config, server, id, document, querySelector, attr);
|
||||
_pointToDevServer('script[type=module]', 'src');
|
||||
_pointToDevServer('link[rel=stylesheet]', 'href');
|
||||
const _pointToDevServer = (querySelector: string, attr: string) =>
|
||||
pointToDevServer(config, server, id, document, querySelector, attr);
|
||||
_pointToDevServer('script[type=module]', 'src');
|
||||
_pointToDevServer('link[rel=stylesheet]', 'href');
|
||||
|
||||
// Add a script to add page reloading
|
||||
const reloader = document.createElement('script');
|
||||
reloader.src = htmlReloadId;
|
||||
reloader.type = 'module';
|
||||
document.head.appendChild(reloader);
|
||||
// Add a script to add page reloading
|
||||
const reloader = document.createElement('script');
|
||||
reloader.src = htmlReloadId;
|
||||
reloader.type = 'module';
|
||||
document.head.appendChild(reloader);
|
||||
|
||||
const newHtml = document.toString();
|
||||
config.logger.debug('transform ' + id);
|
||||
config.logger.debug('Old HTML:\n' + code);
|
||||
config.logger.debug('New HTML:\n' + newHtml);
|
||||
return newHtml;
|
||||
const newHtml = document.toString();
|
||||
config.logger.debug('transform ' + id);
|
||||
config.logger.debug('Old HTML:\n' + code);
|
||||
config.logger.debug('New HTML:\n' + newHtml);
|
||||
return newHtml;
|
||||
},
|
||||
},
|
||||
|
||||
// Pass the HTML through the dev server to add dev-mode specific code
|
||||
@@ -113,29 +114,37 @@ export function devHtmlPrerender(
|
||||
{
|
||||
name: 'wxt:virtualize-inline-scripts',
|
||||
apply: 'serve',
|
||||
resolveId(id) {
|
||||
// Resolve inline scripts
|
||||
if (id.startsWith(virtualInlineScript)) {
|
||||
return '\0' + id;
|
||||
}
|
||||
resolveId: {
|
||||
filter: {
|
||||
id: [new RegExp(`^${virtualInlineScript}`), new RegExp('^/chunks/')],
|
||||
},
|
||||
handler(id) {
|
||||
// Ignore chunks during HTML file pre-rendering
|
||||
if (id.startsWith('/chunks/')) {
|
||||
return '\0noop';
|
||||
}
|
||||
|
||||
// Ignore chunks during HTML file pre-rendering
|
||||
if (id.startsWith('/chunks/')) {
|
||||
return '\0noop';
|
||||
}
|
||||
return `\0${id}`;
|
||||
},
|
||||
},
|
||||
load(id) {
|
||||
// Resolve virtualized inline scripts
|
||||
if (id.startsWith(resolvedVirtualInlineScript)) {
|
||||
load: {
|
||||
filter: {
|
||||
id: [
|
||||
new RegExp(`^${resolvedVirtualInlineScript}`),
|
||||
//eslint-disable-next-line no-control-regex
|
||||
new RegExp('^\x00noop'),
|
||||
],
|
||||
},
|
||||
handler(id) {
|
||||
// Ignore chunks during HTML file pre-rendering
|
||||
if (id === '\0noop') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// id="virtual:wxt-inline-script?<key>"
|
||||
const key = id.substring(id.indexOf('?') + 1);
|
||||
return inlineScriptContents[key];
|
||||
}
|
||||
|
||||
// Ignore chunks during HTML file pre-rendering
|
||||
if (id === '\0noop') {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -2,7 +2,8 @@ import type { Plugin } from 'vite';
|
||||
import type { ResolvedConfig, WxtDevServer } from '../../../../types';
|
||||
|
||||
/**
|
||||
* Defines global constants about the dev server. Helps scripts connect to the server's web socket.
|
||||
* Defines global constants about the dev server. Helps scripts connect to the
|
||||
* server's web socket.
|
||||
*/
|
||||
export function devServerGlobals(
|
||||
config: ResolvedConfig,
|
||||
|
||||
@@ -3,24 +3,33 @@ import type { ResolvedConfig } from '../../../../types';
|
||||
import { fetchCached } from '../../../utils/network';
|
||||
|
||||
/**
|
||||
* Downloads any URL imports, like Google Analytics, into virtual modules so they are bundled with
|
||||
* the extension instead of depending on remote code at runtime.
|
||||
* Downloads any URL imports, like Google Analytics, into virtual modules so
|
||||
* they are bundled with the extension instead of depending on remote code at
|
||||
* runtime.
|
||||
*
|
||||
* @example
|
||||
* import "url:https://google-tagmanager.com/gtag?id=XYZ";
|
||||
* import 'url:https://google-tagmanager.com/gtag?id=XYZ';
|
||||
*/
|
||||
export function download(config: ResolvedConfig): Plugin {
|
||||
return {
|
||||
name: 'wxt:download',
|
||||
resolveId(id) {
|
||||
if (id.startsWith('url:')) return '\0' + id;
|
||||
resolveId: {
|
||||
filter: {
|
||||
id: /^url:/,
|
||||
},
|
||||
handler(id) {
|
||||
return `\0${id}`;
|
||||
},
|
||||
},
|
||||
load(id) {
|
||||
if (!id.startsWith('\0url:')) return;
|
||||
|
||||
// Load file from network or cache
|
||||
const url = id.replace('\0url:', '');
|
||||
return fetchCached(url, config);
|
||||
load: {
|
||||
filter: {
|
||||
//eslint-disable-next-line no-control-regex
|
||||
id: /^\x00url:/,
|
||||
},
|
||||
handler(id) {
|
||||
const url = id.replace('\0url:', '');
|
||||
return fetchCached(url, config);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ import type * as vite from 'vite';
|
||||
import { EntrypointGroup } from '../../../../types';
|
||||
import { getEntrypointGlobals } from '../../../utils/globals';
|
||||
|
||||
/**
|
||||
* Define a set of global variables specific to an entrypoint.
|
||||
*/
|
||||
/** Define a set of global variables specific to an entrypoint. */
|
||||
export function entrypointGroupGlobals(
|
||||
entrypointGroup: EntrypointGroup,
|
||||
): vite.PluginOption {
|
||||
|
||||
@@ -3,7 +3,8 @@ import type * as vite from 'vite';
|
||||
import { ResolvedConfig } from '../../../../types';
|
||||
|
||||
/**
|
||||
* Mock `wxt/browser` and stub the global `browser`/`chrome` types with a fake version of the extension APIs
|
||||
* Mock `wxt/browser` and stub the global `browser`/`chrome` types with a fake
|
||||
* version of the extension APIs
|
||||
*/
|
||||
export function extensionApiMock(config: ResolvedConfig): vite.PluginOption {
|
||||
const virtualSetupModule = 'virtual:wxt-setup';
|
||||
@@ -32,11 +33,21 @@ export function extensionApiMock(config: ResolvedConfig): vite.PluginOption {
|
||||
},
|
||||
};
|
||||
},
|
||||
resolveId(id) {
|
||||
if (id.endsWith(virtualSetupModule)) return resolvedVirtualSetupModule;
|
||||
resolveId: {
|
||||
filter: {
|
||||
id: new RegExp(`${virtualSetupModule}$`),
|
||||
},
|
||||
handler() {
|
||||
return resolvedVirtualSetupModule;
|
||||
},
|
||||
},
|
||||
load(id) {
|
||||
if (id === resolvedVirtualSetupModule) return setupTemplate;
|
||||
load: {
|
||||
filter: {
|
||||
id: new RegExp(`^${resolvedVirtualSetupModule}$`),
|
||||
},
|
||||
handler() {
|
||||
return setupTemplate;
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
/**
|
||||
* Add a footer with the returned value so it can return values to `scripting.executeScript`
|
||||
* Footer is added a part of esbuild to make sure it's not minified. It
|
||||
* get's removed if added to `build.rollupOptions.output.footer`
|
||||
* See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript#return_value
|
||||
* Add a footer with the returned value so it can return values to
|
||||
* `scripting.executeScript` Footer is added a part of esbuild to make sure it's
|
||||
* not minified. It get's removed if added to
|
||||
* `build.rollupOptions.output.footer` See
|
||||
* https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript#return_value
|
||||
*/
|
||||
export function iifeFooter(iifeReturnValueName: string): Plugin {
|
||||
return {
|
||||
@@ -12,7 +13,20 @@ export function iifeFooter(iifeReturnValueName: string): Plugin {
|
||||
generateBundle(_, bundle) {
|
||||
for (const chunk of Object.values(bundle)) {
|
||||
if (chunk.type === 'chunk' && chunk.isEntry) {
|
||||
chunk.code += `${iifeReturnValueName};`;
|
||||
const code = chunk.code;
|
||||
const marker = '\n//# sourceMappingURL=';
|
||||
const returnValue = `${iifeReturnValueName};`;
|
||||
|
||||
const index = code.indexOf(marker);
|
||||
|
||||
if (index >= 0) {
|
||||
chunk.code =
|
||||
code.slice(0, index + 1) +
|
||||
`${returnValue}\n` +
|
||||
code.slice(index + 1);
|
||||
} else {
|
||||
chunk.code += `\n${returnValue}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,21 +2,30 @@ import type { Plugin } from 'vite';
|
||||
import { VIRTUAL_NOOP_BACKGROUND_MODULE_ID } from '../../../utils/constants';
|
||||
|
||||
/**
|
||||
* In dev mode, if there's not a background script listed, we need to add one so that the web socket
|
||||
* connection is setup and the extension reloads HTML pages and content scripts correctly.
|
||||
* In dev mode, if there's not a background script listed, we need to add one so
|
||||
* that the web socket connection is setup and the extension reloads HTML pages
|
||||
* and content scripts correctly.
|
||||
*/
|
||||
export function noopBackground(): Plugin {
|
||||
const virtualModuleId = VIRTUAL_NOOP_BACKGROUND_MODULE_ID;
|
||||
const resolvedVirtualModuleId = '\0' + virtualModuleId;
|
||||
return {
|
||||
name: 'wxt:noop-background',
|
||||
resolveId(id) {
|
||||
if (id === virtualModuleId) return resolvedVirtualModuleId;
|
||||
resolveId: {
|
||||
filter: {
|
||||
id: new RegExp(`^${virtualModuleId}$`),
|
||||
},
|
||||
handler() {
|
||||
return resolvedVirtualModuleId;
|
||||
},
|
||||
},
|
||||
load(id) {
|
||||
if (id === resolvedVirtualModuleId) {
|
||||
load: {
|
||||
filter: {
|
||||
id: new RegExp(`^${resolvedVirtualModuleId}$`),
|
||||
},
|
||||
handler() {
|
||||
return `import { defineBackground } from 'wxt/utils/define-background';\nexport default defineBackground(() => void 0)`;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import { removeMainFunctionCode } from '../../../utils/transform';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
/**
|
||||
* Transforms entrypoints, removing the main function from the entrypoint if it exists.
|
||||
* Transforms entrypoints, removing the main function from the entrypoint if it
|
||||
* exists.
|
||||
*/
|
||||
export function removeEntrypointMainFunction(
|
||||
config: ResolvedConfig,
|
||||
@@ -16,14 +17,15 @@ export function removeEntrypointMainFunction(
|
||||
name: 'wxt:remove-entrypoint-main-function',
|
||||
transform: {
|
||||
order: 'pre',
|
||||
handler(code, id) {
|
||||
if (id === absPath) {
|
||||
const newCode = removeMainFunctionCode(code);
|
||||
config.logger.debug('vite-node transformed entrypoint', path);
|
||||
config.logger.debug(`Original:\n---\n${code}\n---`);
|
||||
config.logger.debug(`Transformed:\n---\n${newCode.code}\n---`);
|
||||
return newCode;
|
||||
}
|
||||
filter: {
|
||||
id: new RegExp(`^${absPath}$`),
|
||||
},
|
||||
handler(code) {
|
||||
const newCode = removeMainFunctionCode(code);
|
||||
config.logger.debug('vite-node transformed entrypoint', path);
|
||||
config.logger.debug(`Original:\n---\n${code}\n---`);
|
||||
config.logger.debug(`Transformed:\n---\n${newCode.code}\n---`);
|
||||
return newCode;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { pathExists } from 'fs-extra';
|
||||
import { pathExists } from '../../../utils/fs';
|
||||
import { resolve } from 'node:path';
|
||||
import type * as vite from 'vite';
|
||||
import { ResolvedConfig } from '../../../../types';
|
||||
|
||||
/**
|
||||
* When importing `virtual:app-config`, resolve it to the `app.config.ts` file in the project.
|
||||
* When importing `virtual:app-config`, resolve it to the `app.config.ts` file
|
||||
* in the project.
|
||||
*/
|
||||
export function resolveAppConfig(config: ResolvedConfig): vite.Plugin {
|
||||
const virtualModuleId = 'virtual:app-config';
|
||||
@@ -22,15 +23,23 @@ export function resolveAppConfig(config: ResolvedConfig): vite.Plugin {
|
||||
},
|
||||
};
|
||||
},
|
||||
async resolveId(id) {
|
||||
if (id !== virtualModuleId) return;
|
||||
|
||||
return (await pathExists(appConfigFile))
|
||||
? appConfigFile
|
||||
: resolvedVirtualModuleId;
|
||||
resolveId: {
|
||||
filter: {
|
||||
id: new RegExp(`^${virtualModuleId}$`),
|
||||
},
|
||||
async handler() {
|
||||
return (await pathExists(appConfigFile))
|
||||
? appConfigFile
|
||||
: resolvedVirtualModuleId;
|
||||
},
|
||||
},
|
||||
load(id) {
|
||||
if (id === resolvedVirtualModuleId) return `export default {}`;
|
||||
load: {
|
||||
filter: {
|
||||
id: new RegExp(`^${resolvedVirtualModuleId}$`),
|
||||
},
|
||||
handler() {
|
||||
return `export default {}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { resolve } from 'path';
|
||||
import type { Plugin } from 'vite';
|
||||
import { ResolvedConfig } from '../../../../types';
|
||||
import { normalizePath } from '../../../utils';
|
||||
@@ -5,36 +7,41 @@ import {
|
||||
VirtualModuleId,
|
||||
virtualModuleNames,
|
||||
} from '../../../utils/virtual-modules';
|
||||
import fs from 'fs-extra';
|
||||
import { resolve } from 'path';
|
||||
|
||||
/**
|
||||
* Resolve all the virtual modules to the `node_modules/wxt/dist/virtual` directory.
|
||||
* Resolve all the virtual modules to the `node_modules/wxt/dist/virtual`
|
||||
* directory.
|
||||
*/
|
||||
export function resolveVirtualModules(config: ResolvedConfig): Plugin[] {
|
||||
return virtualModuleNames.map((name) => {
|
||||
const virtualId: `${VirtualModuleId}?` = `virtual:wxt-${name}?`;
|
||||
const resolvedVirtualId = '\0' + virtualId;
|
||||
|
||||
return {
|
||||
name: `wxt:resolve-virtual-${name}`,
|
||||
resolveId(id) {
|
||||
// Id doesn't start with prefix, it looks like this:
|
||||
// /path/to/project/virtual:wxt-background?/path/to/project/entrypoints/background.ts
|
||||
const index = id.indexOf(virtualId);
|
||||
if (index === -1) return;
|
||||
|
||||
const inputPath = normalizePath(id.substring(index + virtualId.length));
|
||||
return resolvedVirtualId + inputPath;
|
||||
resolveId: {
|
||||
filter: {
|
||||
id: new RegExp(virtualId),
|
||||
},
|
||||
handler(id) {
|
||||
const inputPath = normalizePath(
|
||||
id.substring(id.indexOf(virtualId) + virtualId.length),
|
||||
);
|
||||
return resolvedVirtualId + inputPath;
|
||||
},
|
||||
},
|
||||
async load(id) {
|
||||
if (!id.startsWith(resolvedVirtualId)) return;
|
||||
|
||||
const inputPath = id.replace(resolvedVirtualId, '');
|
||||
const template = await fs.readFile(
|
||||
resolve(config.wxtModuleDir, `dist/virtual/${name}.mjs`),
|
||||
'utf-8',
|
||||
);
|
||||
return template.replace(`virtual:user-${name}`, inputPath);
|
||||
load: {
|
||||
filter: {
|
||||
id: new RegExp(`^${resolvedVirtualId}`),
|
||||
},
|
||||
async handler(id) {
|
||||
const inputPath = id.replace(resolvedVirtualId, '');
|
||||
const template = await readFile(
|
||||
resolve(config.wxtModuleDir, `dist/virtual/${name}.mjs`),
|
||||
'utf-8',
|
||||
);
|
||||
return template.replace(`virtual:user-${name}`, inputPath);
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -4,7 +4,9 @@ import { normalizePath } from '../../../utils';
|
||||
import { ResolvedConfig } from '../../../../types';
|
||||
|
||||
/**
|
||||
* Resolve and load plugins for each entrypoint. This handles both JS entrypoints via the `virtual:wxt-plugins` import, and HTML files by adding `virtual:wxt-html-plugins` to the document's `<head>`
|
||||
* Resolve and load plugins for each entrypoint. This handles both JS
|
||||
* entrypoints via the `virtual:wxt-plugins` import, and HTML files by adding
|
||||
* `virtual:wxt-html-plugins` to the document's `<head>`
|
||||
*/
|
||||
export function wxtPluginLoader(config: ResolvedConfig): vite.Plugin {
|
||||
const virtualModuleId = 'virtual:wxt-plugins';
|
||||
@@ -14,33 +16,50 @@ export function wxtPluginLoader(config: ResolvedConfig): vite.Plugin {
|
||||
|
||||
return {
|
||||
name: 'wxt:plugin-loader',
|
||||
resolveId(id) {
|
||||
if (id === virtualModuleId) return resolvedVirtualModuleId;
|
||||
if (id === virtualHtmlModuleId) return resolvedVirtualHtmlModuleId;
|
||||
},
|
||||
load(id) {
|
||||
if (id === resolvedVirtualModuleId) {
|
||||
// Import and init all plugins
|
||||
const imports = config.plugins
|
||||
.map(
|
||||
(plugin, i) =>
|
||||
`import initPlugin${i} from '${normalizePath(plugin)}';`,
|
||||
)
|
||||
.join('\n');
|
||||
const initCalls = config.plugins
|
||||
.map((_, i) => ` initPlugin${i}();`)
|
||||
.join('\n');
|
||||
return `${imports}\n\nexport function initPlugins() {\n${initCalls}\n}`;
|
||||
}
|
||||
if (id === resolvedVirtualHtmlModuleId) {
|
||||
return `import { initPlugins } from '${virtualModuleId}';
|
||||
resolveId: {
|
||||
filter: {
|
||||
id: [
|
||||
new RegExp(`^${virtualModuleId}$`),
|
||||
new RegExp(`^${virtualHtmlModuleId}$`),
|
||||
],
|
||||
},
|
||||
handler(id) {
|
||||
if (id === virtualModuleId) {
|
||||
return resolvedVirtualModuleId;
|
||||
}
|
||||
|
||||
try {
|
||||
initPlugins();
|
||||
} catch (err) {
|
||||
console.error("[wxt] Failed to initialize plugins", err);
|
||||
}`;
|
||||
}
|
||||
return resolvedVirtualHtmlModuleId;
|
||||
},
|
||||
},
|
||||
load: {
|
||||
filter: {
|
||||
id: [
|
||||
new RegExp(`^${resolvedVirtualModuleId}$`),
|
||||
new RegExp(`^${resolvedVirtualHtmlModuleId}$`),
|
||||
],
|
||||
},
|
||||
handler(id) {
|
||||
if (id === resolvedVirtualModuleId) {
|
||||
// Import and init all plugins
|
||||
const imports = config.plugins
|
||||
.map(
|
||||
(plugin, i) =>
|
||||
`import initPlugin${i} from '${normalizePath(plugin)}';`,
|
||||
)
|
||||
.join('\n');
|
||||
const initCalls = config.plugins
|
||||
.map((_, i) => ` initPlugin${i}();`)
|
||||
.join('\n');
|
||||
return `${imports}\n\nexport function initPlugins() {\n${initCalls}\n}`;
|
||||
} else {
|
||||
return `import { initPlugins } from '${virtualModuleId}';
|
||||
try {
|
||||
initPlugins();
|
||||
} catch (err) {
|
||||
console.error("[wxt] Failed to initialize plugins", err);
|
||||
}`;
|
||||
}
|
||||
},
|
||||
},
|
||||
transformIndexHtml: {
|
||||
// Use "pre" so the new script is added before vite bundles all the scripts
|
||||
@@ -63,7 +82,8 @@ try {
|
||||
const newHead = document.createElement('head');
|
||||
document.documentElement.prepend(newHead);
|
||||
}
|
||||
document.head.prepend(script);
|
||||
|
||||
document.head?.prepend(script);
|
||||
return document.toString();
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { rm } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import glob from 'fast-glob';
|
||||
import fs from 'fs-extra';
|
||||
import pc from 'picocolors';
|
||||
import { glob } from 'tinyglobby';
|
||||
import { InlineConfig } from '../types';
|
||||
import { registerWxt, wxt } from './wxt';
|
||||
|
||||
/**
|
||||
* Remove generated/temp files from the directory.
|
||||
*
|
||||
* @param config Optional config that will override your `<root>/wxt.config.ts`.
|
||||
*
|
||||
* @example
|
||||
* await clean();
|
||||
* await clean();
|
||||
*
|
||||
* @param config Optional config that will override your `<root>/wxt.config.ts`.
|
||||
*/
|
||||
export async function clean(config?: InlineConfig): Promise<void>;
|
||||
/**
|
||||
* Remove generated/temp files from the directory.
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
* @param root The directory to look for generated/temp files in. Defaults to `process.cwd()`. Can be relative to `process.cwd()` or absolute.
|
||||
*
|
||||
* @example
|
||||
* await clean();
|
||||
* await clean();
|
||||
*
|
||||
* @param root The directory to look for generated/temp files in. Defaults to
|
||||
* `process.cwd()`. Can be relative to `process.cwd()` or absolute.
|
||||
*/
|
||||
export async function clean(root?: string): Promise<void>;
|
||||
|
||||
@@ -48,6 +48,7 @@ export async function clean(config?: string | InlineConfig) {
|
||||
absolute: true,
|
||||
onlyDirectories: true,
|
||||
deep: 2,
|
||||
expandDirectories: false,
|
||||
});
|
||||
if (directories.length === 0) {
|
||||
wxt.logger.debug('No generated files found.');
|
||||
@@ -59,7 +60,7 @@ export async function clean(config?: string | InlineConfig) {
|
||||
directories.map((dir) => pc.cyan(path.relative(root, dir))).join(', '),
|
||||
);
|
||||
for (const directory of directories) {
|
||||
await fs.rm(directory, { force: true, recursive: true });
|
||||
await rm(directory, { force: true, recursive: true });
|
||||
wxt.logger.debug('Deleted ' + pc.cyan(path.relative(root, directory)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,13 +32,14 @@ import { createKeyboardShortcuts } from './keyboard-shortcuts';
|
||||
import { isBabelSyntaxError, logBabelSyntaxError } from './utils/syntax-errors';
|
||||
|
||||
/**
|
||||
* Creates a dev server and pre-builds all the files that need to exist before loading the extension.
|
||||
* Creates a dev server and pre-builds all the files that need to exist before
|
||||
* loading the extension.
|
||||
*
|
||||
* @example
|
||||
* const server = await wxt.createServer({
|
||||
* // Enter config...
|
||||
* });
|
||||
* await server.start();
|
||||
* const server = await wxt.createServer({
|
||||
* // Enter config...
|
||||
* });
|
||||
* await server.start();
|
||||
*/
|
||||
export async function createServer(
|
||||
inlineConfig?: InlineConfig,
|
||||
@@ -199,8 +200,8 @@ async function createServerInternal(): Promise<WxtDevServer> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function responsible for reloading different parts of the extension when a file
|
||||
* changes.
|
||||
* Returns a function responsible for reloading different parts of the extension
|
||||
* when a file changes.
|
||||
*/
|
||||
function createFileReloader(server: WxtDevServer) {
|
||||
const fileChangedMutex = new Mutex();
|
||||
@@ -295,7 +296,8 @@ function createFileReloader(server: WxtDevServer) {
|
||||
}
|
||||
|
||||
/**
|
||||
* From the server, tell the client to reload content scripts from the provided build step outputs.
|
||||
* From the server, tell the client to reload content scripts from the provided
|
||||
* build step outputs.
|
||||
*/
|
||||
function reloadContentScripts(steps: BuildStepOutput[], server: WxtDevServer) {
|
||||
if (wxt.config.manifestVersion === 3) {
|
||||
@@ -350,6 +352,7 @@ function getFilenameList(names: string[]): string {
|
||||
|
||||
/**
|
||||
* Based on the current build output, return a list of files that are:
|
||||
*
|
||||
* 1. Not in node_modules
|
||||
* 2. Not inside project root
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import consola from 'consola';
|
||||
import { WebExtConfig } from '../types';
|
||||
|
||||
/**
|
||||
* @deprecated Use `defineWebExtConfig` instead. Same function, different name.
|
||||
*/
|
||||
/** @deprecated Use `defineWebExtConfig` instead. Same function, different name. */
|
||||
export function defineRunnerConfig(config: WebExtConfig): WebExtConfig {
|
||||
consola.warn(
|
||||
'`defineRunnerConfig` is deprecated, use `defineWebExtConfig` instead. See https://wxt.dev/guide/resources/upgrading.html#v0-19-0-rarr-v0-20-0',
|
||||
@@ -12,7 +10,8 @@ export function defineRunnerConfig(config: WebExtConfig): WebExtConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure how [`web-ext`](https://github.com/mozilla/web-ext) starts the browser during development.
|
||||
* Configure how [`web-ext`](https://github.com/mozilla/web-ext) starts the
|
||||
* browser during development.
|
||||
*/
|
||||
export function defineWebExtConfig(config: WebExtConfig): WebExtConfig {
|
||||
return config;
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { Entrypoint, WxtDirEntry, WxtDirFileEntry } from '../types';
|
||||
import fs from 'fs-extra';
|
||||
import { mkdir, readFile } from 'node:fs/promises';
|
||||
import { dirname, relative, resolve } from 'node:path';
|
||||
import { getEntrypointBundlePath, isHtmlEntrypoint } from './utils/entrypoints';
|
||||
import { getEntrypointGlobals, getGlobals } from './utils/globals';
|
||||
import { normalizePath } from './utils';
|
||||
import path from 'node:path';
|
||||
import { Message, parseI18nMessages } from './utils/i18n';
|
||||
import { writeFileIfDifferent, getPublicFiles } from './utils/fs';
|
||||
import { pathExists, writeFileIfDifferent, getPublicFiles } from './utils/fs';
|
||||
import { wxt } from './wxt';
|
||||
|
||||
/**
|
||||
* Generate and write all the files inside the `InternalConfig.typesDir` directory.
|
||||
* Generate and write all the files inside the `InternalConfig.typesDir`
|
||||
* directory.
|
||||
*/
|
||||
export async function generateWxtDir(entrypoints: Entrypoint[]): Promise<void> {
|
||||
await fs.ensureDir(wxt.config.typesDir);
|
||||
await mkdir(wxt.config.typesDir, { recursive: true });
|
||||
|
||||
const entries: WxtDirEntry[] = [
|
||||
// Hard-coded entries
|
||||
@@ -56,7 +57,7 @@ export async function generateWxtDir(entrypoints: Entrypoint[]): Promise<void> {
|
||||
|
||||
await Promise.all(
|
||||
absoluteFileEntries.map(async (file) => {
|
||||
await fs.ensureDir(dirname(file.path));
|
||||
await mkdir(dirname(file.path), { recursive: true });
|
||||
await writeFileIfDifferent(file.path, file.text);
|
||||
}),
|
||||
);
|
||||
@@ -148,8 +149,8 @@ declare module "wxt/browser" {
|
||||
'messages.json',
|
||||
);
|
||||
let messages: Message[];
|
||||
if (await fs.pathExists(defaultLocalePath)) {
|
||||
const content = JSON.parse(await fs.readFile(defaultLocalePath, 'utf-8'));
|
||||
if (await pathExists(defaultLocalePath)) {
|
||||
const content = JSON.parse(await readFile(defaultLocalePath, 'utf-8'));
|
||||
messages = parseI18nMessages(content);
|
||||
} else {
|
||||
messages = parseI18nMessages({});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import prompts from 'prompts';
|
||||
import { consola } from 'consola';
|
||||
import { downloadTemplate } from 'giget';
|
||||
import fs from 'fs-extra';
|
||||
import { readdir, rename } from 'node:fs/promises';
|
||||
import { pathExists } from './utils/fs';
|
||||
import path from 'node:path';
|
||||
import pc from 'picocolors';
|
||||
import { Formatter } from 'picocolors/types';
|
||||
@@ -56,10 +57,10 @@ export async function initialize(options: {
|
||||
input.template ??= defaultTemplate;
|
||||
input.packageManager ??= options.packageManager;
|
||||
|
||||
const isExists = await fs.pathExists(input.directory);
|
||||
const isExists = await pathExists(input.directory);
|
||||
if (isExists) {
|
||||
const isEmpty =
|
||||
(await fs.readdir(input.directory)).filter((dir) => dir !== '.git')
|
||||
(await readdir(input.directory)).filter((dir) => dir !== '.git')
|
||||
.length === 0;
|
||||
if (!isEmpty) {
|
||||
consola.error(
|
||||
@@ -87,13 +88,9 @@ export async function initialize(options: {
|
||||
}
|
||||
|
||||
interface Template {
|
||||
/**
|
||||
* Template's name.
|
||||
*/
|
||||
/** Template's name. */
|
||||
name: string;
|
||||
/**
|
||||
* Path to template directory in github repo.
|
||||
*/
|
||||
/** Path to template directory in github repo. */
|
||||
path: string;
|
||||
}
|
||||
|
||||
@@ -161,8 +158,8 @@ async function cloneProject({
|
||||
directory: string;
|
||||
template: Template;
|
||||
}) {
|
||||
const { default: ora } = await import('ora');
|
||||
const spinner = ora('Downloading template').start();
|
||||
const { createSpinner } = await import('nanospinner');
|
||||
const spinner = createSpinner('Downloading template').start();
|
||||
try {
|
||||
// 1. Clone repo
|
||||
await downloadTemplate(`gh:${REPO}/${template.path}`, {
|
||||
@@ -171,18 +168,16 @@ async function cloneProject({
|
||||
});
|
||||
|
||||
// 2. Move _gitignore -> .gitignore
|
||||
await fs
|
||||
.move(
|
||||
path.join(directory, '_gitignore'),
|
||||
path.join(directory, '.gitignore'),
|
||||
)
|
||||
.catch((err) =>
|
||||
consola.warn('Failed to move _gitignore to .gitignore:', err),
|
||||
);
|
||||
await rename(
|
||||
path.join(directory, '_gitignore'),
|
||||
path.join(directory, '.gitignore'),
|
||||
).catch((err) =>
|
||||
consola.warn('Failed to move _gitignore to .gitignore:', err),
|
||||
);
|
||||
|
||||
spinner.succeed();
|
||||
spinner.success();
|
||||
} catch (err) {
|
||||
spinner.fail();
|
||||
spinner.error();
|
||||
throw Error(`Failed to setup new project: ${JSON.stringify(err, null, 2)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,7 @@ export interface KeyboardShortcutWatcher {
|
||||
printHelp(flags: { canReopenBrowser: boolean }): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that creates a keyboard shortcut handler for the extension.
|
||||
*/
|
||||
/** Function that creates a keyboard shortcut handler for the extension. */
|
||||
export function createKeyboardShortcuts(
|
||||
server: WxtDevServer,
|
||||
): KeyboardShortcutWatcher {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import path from 'node:path';
|
||||
import { npm } from '../npm';
|
||||
import spawn from 'nano-spawn';
|
||||
import { pathExists } from 'fs-extra';
|
||||
import { pathExists } from '../../utils/fs';
|
||||
|
||||
describe('NPM Package Management Utils', () => {
|
||||
describe('listDependencies', () => {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Dependency } from '../../types';
|
||||
import { WxtPackageManagerImpl } from './types';
|
||||
import path from 'node:path';
|
||||
import { ensureDir } from 'fs-extra';
|
||||
import { mkdir } from 'node:fs/promises';
|
||||
import spawn from 'nano-spawn';
|
||||
|
||||
export const npm: WxtPackageManagerImpl = {
|
||||
overridesKey: 'overrides',
|
||||
async downloadDependency(id, downloadDir) {
|
||||
await ensureDir(downloadDir);
|
||||
await mkdir(downloadDir, { recursive: true });
|
||||
const res = await spawn('npm', ['pack', id, '--json'], {
|
||||
cwd: downloadDir,
|
||||
});
|
||||
|
||||
@@ -20,9 +20,9 @@ import { createFsCache } from './utils/cache';
|
||||
import consola, { LogLevels } from 'consola';
|
||||
import defu from 'defu';
|
||||
import { NullablyRequired } from './utils/types';
|
||||
import fs from 'fs-extra';
|
||||
import { pathExists } from './utils/fs';
|
||||
import { normalizePath } from './utils';
|
||||
import glob from 'fast-glob';
|
||||
import { glob } from 'tinyglobby';
|
||||
import { builtinModules } from '../builtin-modules';
|
||||
import { getEslintVersion } from './utils/eslint';
|
||||
import { safeStringToNumber } from './utils/number';
|
||||
@@ -31,11 +31,12 @@ import { getPort } from 'get-port-please';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
|
||||
/**
|
||||
* Given an inline config, discover the config file if necessary, merge the results, resolve any
|
||||
* relative paths, and apply any defaults.
|
||||
* Given an inline config, discover the config file if necessary, merge the
|
||||
* results, resolve any relative paths, and apply any defaults.
|
||||
*
|
||||
* Inline config always has priority over user config. Cli flags are passed as inline config if set.
|
||||
* If unset, undefined is passed in, letting this function decide default values.
|
||||
* Inline config always has priority over user config. Cli flags are passed as
|
||||
* inline config if set. If unset, undefined is passed in, letting this function
|
||||
* decide default values.
|
||||
*/
|
||||
export async function resolveConfig(
|
||||
inlineConfig: InlineConfig,
|
||||
@@ -249,7 +250,8 @@ async function resolveManifestConfig(
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the inline config and user config. Inline config is given priority. Defaults are not applied here.
|
||||
* Merge the inline config and user config. Inline config is given priority.
|
||||
* Defaults are not applied here.
|
||||
*/
|
||||
async function mergeInlineConfig(
|
||||
inlineConfig: InlineConfig,
|
||||
@@ -528,9 +530,7 @@ async function getUnimportEslintOptions(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to `node_modules/wxt`.
|
||||
*/
|
||||
/** Returns the path to `node_modules/wxt`. */
|
||||
function resolveWxtModuleDir() {
|
||||
// TODO: Switch to import.meta.resolve() once the parent argument is unflagged
|
||||
// (e.g. --experimental-import-meta-resolve) and all Node.js versions we support
|
||||
@@ -543,7 +543,7 @@ function resolveWxtModuleDir() {
|
||||
}
|
||||
|
||||
async function isDirMissing(dir: string) {
|
||||
return !(await fs.pathExists(dir));
|
||||
return !(await pathExists(dir));
|
||||
}
|
||||
|
||||
function logMissingDir(logger: Logger, name: string, expected: string) {
|
||||
@@ -554,9 +554,7 @@ function logMissingDir(logger: Logger, name: string, expected: string) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of `ConfigEnv` commands to their default modes.
|
||||
*/
|
||||
/** Map of `ConfigEnv` commands to their default modes. */
|
||||
const COMMAND_MODES: Record<WxtCommand, string> = {
|
||||
build: 'production',
|
||||
serve: 'development',
|
||||
@@ -614,6 +612,7 @@ export async function resolveWxtUserModules(
|
||||
const localModulePaths = await glob(['*.[tj]s', '*/index.[tj]s'], {
|
||||
cwd: modulesDir,
|
||||
onlyFiles: true,
|
||||
expandDirectories: false,
|
||||
}).catch(() => []);
|
||||
// Sort modules to ensure a consistent execution order
|
||||
localModulePaths.sort();
|
||||
|
||||
@@ -2,9 +2,7 @@ import { ExtensionRunner } from '../../types';
|
||||
import { relative } from 'node:path';
|
||||
import { wxt } from '../wxt';
|
||||
|
||||
/**
|
||||
* The manual runner tells the user to load the unpacked extension manually.
|
||||
*/
|
||||
/** The manual runner tells the user to load the unpacked extension manually. */
|
||||
export function createManualRunner(): ExtensionRunner {
|
||||
return {
|
||||
async openBrowser() {
|
||||
|
||||
@@ -3,7 +3,8 @@ import { relative } from 'node:path';
|
||||
import { wxt } from '../wxt';
|
||||
|
||||
/**
|
||||
* The Safari runner just logs a warning message because `web-ext` doesn't work with Safari.
|
||||
* The Safari runner just logs a warning message because `web-ext` doesn't work
|
||||
* with Safari.
|
||||
*/
|
||||
export function createSafariRunner(): ExtensionRunner {
|
||||
return {
|
||||
|
||||
@@ -4,9 +4,7 @@ import { formatDuration } from '../utils/time';
|
||||
import defu from 'defu';
|
||||
import { wxt } from '../wxt';
|
||||
|
||||
/**
|
||||
* Create an `ExtensionRunner` backed by `web-ext`.
|
||||
*/
|
||||
/** Create an `ExtensionRunner` backed by `web-ext`. */
|
||||
export function createWebExtRunner(): ExtensionRunner {
|
||||
let runner: WebExtRunInstance | undefined;
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import { relative } from 'node:path';
|
||||
import { wxt } from '../wxt';
|
||||
|
||||
/**
|
||||
* The WSL runner just logs a warning message because `web-ext` doesn't work in WSL.
|
||||
* The WSL runner just logs a warning message because `web-ext` doesn't work in
|
||||
* WSL.
|
||||
*/
|
||||
export function createWslRunner(): ExtensionRunner {
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/**
|
||||
* Checks if `predicate` returns truthy for all elements of the array.
|
||||
*/
|
||||
/** Checks if `predicate` returns truthy for all elements of the array. */
|
||||
export function every<T>(
|
||||
array: T[],
|
||||
predicate: (item: T, index: number) => boolean,
|
||||
@@ -10,9 +8,7 @@ export function every<T>(
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when any of the predicates return true;
|
||||
*/
|
||||
/** Returns true when any of the predicates return true; */
|
||||
export function some<T>(
|
||||
array: T[],
|
||||
predicate: (item: T, index: number) => boolean,
|
||||
@@ -22,9 +18,7 @@ export function some<T>(
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an item or array to an array.
|
||||
*/
|
||||
/** Convert an item or array to an array. */
|
||||
export function toArray<T>(a: T | T[]): T[] {
|
||||
return Array.isArray(a) ? a : [a];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { resolve } from 'path';
|
||||
import { glob } from 'tinyglobby';
|
||||
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
|
||||
import {
|
||||
BackgroundEntrypoint,
|
||||
BackgroundEntrypointOptions,
|
||||
@@ -9,21 +12,16 @@ import {
|
||||
PopupEntrypoint,
|
||||
SidepanelEntrypoint,
|
||||
} from '../../../../types';
|
||||
import { resolve } from 'path';
|
||||
import { findEntrypoints } from '../find-entrypoints';
|
||||
import fs from 'fs-extra';
|
||||
import glob from 'fast-glob';
|
||||
import { fakeResolvedConfig, setFakeWxt } from '../../testing/fake-objects';
|
||||
import { unnormalizePath } from '../../paths';
|
||||
import { wxt } from '../../../wxt';
|
||||
import { unnormalizePath } from '../../paths';
|
||||
import { fakeResolvedConfig, setFakeWxt } from '../../testing/fake-objects';
|
||||
import { findEntrypoints } from '../find-entrypoints';
|
||||
|
||||
vi.mock('fast-glob');
|
||||
vi.mock('tinyglobby');
|
||||
const globMock = vi.mocked(glob);
|
||||
|
||||
vi.mock('fs-extra');
|
||||
const readFileMock = vi.mocked(
|
||||
fs.readFile as (path: string) => Promise<string>,
|
||||
);
|
||||
vi.mock('node:fs/promises');
|
||||
const readFileMock = vi.mocked(readFile);
|
||||
|
||||
describe('findEntrypoints', () => {
|
||||
const config = fakeResolvedConfig({
|
||||
|
||||
@@ -5,28 +5,30 @@ import {
|
||||
ResolvedPublicFile,
|
||||
} from '../../../types';
|
||||
import { getPublicFiles } from '../fs';
|
||||
import fs from 'fs-extra';
|
||||
import { copyFile, mkdir, writeFile } from 'node:fs/promises';
|
||||
import { dirname, resolve } from 'path';
|
||||
import type { Ora } from 'ora';
|
||||
import type { Spinner } from 'nanospinner';
|
||||
import pc from 'picocolors';
|
||||
import { wxt } from '../../wxt';
|
||||
import { toArray } from '../arrays';
|
||||
|
||||
export async function buildEntrypoints(
|
||||
groups: EntrypointGroup[],
|
||||
spinner: Ora,
|
||||
spinner: Spinner,
|
||||
): Promise<Omit<BuildOutput, 'manifest'>> {
|
||||
const steps: BuildStepOutput[] = [];
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
const group = groups[i];
|
||||
const groupNames = toArray(group).map((e) => e.name);
|
||||
const groupNameColored = groupNames.join(pc.dim(', '));
|
||||
spinner.text =
|
||||
pc.dim(`[${i + 1}/${groups.length}]`) + ` ${groupNameColored}`;
|
||||
spinner.update({
|
||||
text: pc.dim(`[${i + 1}/${groups.length}]`) + ` ${groupNameColored}`,
|
||||
});
|
||||
try {
|
||||
steps.push(await wxt.builder.build(group));
|
||||
} catch (err) {
|
||||
spinner.stop().clear();
|
||||
spinner.stop();
|
||||
spinner.clear();
|
||||
wxt.logger.error(err);
|
||||
throw Error(`Failed to build ${groupNames.join(', ')}`, { cause: err });
|
||||
}
|
||||
@@ -48,11 +50,11 @@ async function copyPublicDirectory(): Promise<BuildOutput['publicAssets']> {
|
||||
for (const file of files) {
|
||||
const absoluteDest = resolve(wxt.config.outDir, file.relativeDest);
|
||||
|
||||
await fs.ensureDir(dirname(absoluteDest));
|
||||
await mkdir(dirname(absoluteDest), { recursive: true });
|
||||
if ('absoluteSrc' in file) {
|
||||
await fs.copyFile(file.absoluteSrc, absoluteDest);
|
||||
await copyFile(file.absoluteSrc, absoluteDest);
|
||||
} else {
|
||||
await fs.writeFile(absoluteDest, file.contents, 'utf8');
|
||||
await writeFile(absoluteDest, file.contents, 'utf8');
|
||||
}
|
||||
publicAssets.push({
|
||||
type: 'asset',
|
||||
|
||||
@@ -9,27 +9,36 @@ import { normalizePath } from '../paths';
|
||||
import { wxt } from '../../wxt';
|
||||
|
||||
/**
|
||||
* Compare the changed files vs the build output and determine what kind of reload needs to happen:
|
||||
* Compare the changed files vs the build output and determine what kind of
|
||||
* reload needs to happen:
|
||||
*
|
||||
* - Do nothing
|
||||
* - CSS or JS file associated with an HTML page is changed - this is handled automatically by the
|
||||
* dev server
|
||||
*
|
||||
* - CSS or JS file associated with an HTML page is changed - this is handled
|
||||
* automatically by the dev server
|
||||
* - Change isn't used by any of the entrypoints
|
||||
* - Reload Content script
|
||||
*
|
||||
* - CSS or JS file associated with a content script
|
||||
* - Background script will be told to reload the content script
|
||||
* - Reload HTML file
|
||||
* - HTML file itself is saved - HMR doesn't handle this because the HTML pages are pre-rendered
|
||||
* - Chrome is OK reloading the page when the HTML file is changed without reloading the whole
|
||||
* extension. Not sure about firefox, this might need to change to an extension reload
|
||||
*
|
||||
* - HTML file itself is saved - HMR doesn't handle this because the HTML pages
|
||||
* are pre-rendered
|
||||
* - Chrome is OK reloading the page when the HTML file is changed without
|
||||
* reloading the whole extension. Not sure about firefox, this might need
|
||||
* to change to an extension reload
|
||||
* - Reload extension
|
||||
*
|
||||
* - Background script is changed
|
||||
* - Manifest is different
|
||||
* - Restart browser
|
||||
* - web-ext.config.ts (runner config changes)
|
||||
*
|
||||
* - Web-ext.config.ts (runner config changes)
|
||||
* - Full dev server restart
|
||||
* - wxt.config.ts (main config file)
|
||||
* - modules/* (any file related to WXT modules)
|
||||
*
|
||||
* - Wxt.config.ts (main config file)
|
||||
* - Modules/* (any file related to WXT modules)
|
||||
* - .env (environment variable changed could effect build)
|
||||
*/
|
||||
export function detectDevChanges(
|
||||
@@ -126,7 +135,8 @@ export function detectDevChanges(
|
||||
}
|
||||
|
||||
/**
|
||||
* For a single change, return all the step of the build output that were effected by it.
|
||||
* For a single change, return all the step of the build output that were
|
||||
* effected by it.
|
||||
*/
|
||||
function findEffectedSteps(
|
||||
changedFile: string,
|
||||
@@ -165,8 +175,8 @@ function findEffectedSteps(
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains information about what files changed, what needs rebuilt, and the type of reload that is
|
||||
* required.
|
||||
* Contains information about what files changed, what needs rebuilt, and the
|
||||
* type of reload that is required.
|
||||
*/
|
||||
export type DevModeChange =
|
||||
| NoChange
|
||||
@@ -181,13 +191,9 @@ interface NoChange {
|
||||
}
|
||||
|
||||
interface RebuildChange {
|
||||
/**
|
||||
* The list of entrypoints that need rebuilt.
|
||||
*/
|
||||
/** The list of entrypoints that need rebuilt. */
|
||||
rebuildGroups: EntrypointGroup[];
|
||||
/**
|
||||
* The previous output stripped of any files are going to change.
|
||||
*/
|
||||
/** The previous output stripped of any files are going to change. */
|
||||
cachedOutput: BuildOutput;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ import {
|
||||
IsolatedWorldContentScriptEntrypointOptions,
|
||||
UnlistedScriptEntrypoint,
|
||||
} from '../../../types';
|
||||
import fs from 'fs-extra';
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { parseHTML } from 'linkedom';
|
||||
import JSON5 from 'json5';
|
||||
import glob from 'fast-glob';
|
||||
import { glob } from 'tinyglobby';
|
||||
import {
|
||||
getEntrypointName,
|
||||
isHtmlEntrypoint,
|
||||
@@ -30,15 +30,16 @@ import { wxt } from '../../wxt';
|
||||
import { camelCase } from 'scule';
|
||||
|
||||
/**
|
||||
* Return entrypoints and their configuration by looking through the project's files.
|
||||
* Return entrypoints and their configuration by looking through the project's
|
||||
* files.
|
||||
*/
|
||||
export async function findEntrypoints(): Promise<Entrypoint[]> {
|
||||
// Make sure required TSConfig file exists to load dependencies
|
||||
await fs.mkdir(wxt.config.wxtDir, { recursive: true });
|
||||
await mkdir(wxt.config.wxtDir, { recursive: true });
|
||||
try {
|
||||
await fs.writeJson(
|
||||
await writeFile(
|
||||
resolve(wxt.config.wxtDir, 'tsconfig.json'),
|
||||
{},
|
||||
JSON.stringify({}),
|
||||
{ flag: 'wx' },
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -49,6 +50,7 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
|
||||
|
||||
const relativePaths = await glob(Object.keys(PATH_GLOB_TO_TYPE_MAP), {
|
||||
cwd: wxt.config.entrypointsDir,
|
||||
expandDirectories: false,
|
||||
});
|
||||
// Ensure consistent output
|
||||
relativePaths.sort();
|
||||
@@ -195,11 +197,14 @@ async function importEntrypoints(infos: EntrypointInfo[]) {
|
||||
return resMap;
|
||||
}
|
||||
|
||||
/** Extract `manifest.` and `wxt.` options from meta tags, converting snake_case keys to camelCase */
|
||||
/**
|
||||
* Extract `manifest.` and `wxt.` options from meta tags, converting snake_case
|
||||
* keys to camelCase
|
||||
*/
|
||||
async function importHtmlEntrypoint(
|
||||
info: EntrypointInfo,
|
||||
): Promise<Record<string, any>> {
|
||||
const content = await fs.readFile(info.inputPath, 'utf-8');
|
||||
const content = await readFile(info.inputPath, 'utf-8');
|
||||
const { document } = parseHTML(content);
|
||||
|
||||
const metaTags = document.querySelectorAll('meta');
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Entrypoint, EntrypointGroup } from '../../../types';
|
||||
|
||||
/**
|
||||
* Entrypoints are built in groups. HTML pages can all be built together in a single step,
|
||||
* content scripts must be build individually, etc.
|
||||
* Entrypoints are built in groups. HTML pages can all be built together in a
|
||||
* single step, content scripts must be build individually, etc.
|
||||
*
|
||||
* This function returns the entrypoints put into these types of groups.
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { findEntrypoints } from './find-entrypoints';
|
||||
import { BuildOutput, Entrypoint } from '../../../types';
|
||||
import pc from 'picocolors';
|
||||
import fs from 'fs-extra';
|
||||
import { mkdir, rm } from 'node:fs/promises';
|
||||
import { groupEntrypoints } from './group-entrypoints';
|
||||
import { formatDuration } from '../time';
|
||||
import { printBuildSummary } from '../log';
|
||||
import glob from 'fast-glob';
|
||||
import { glob } from 'tinyglobby';
|
||||
import { unnormalizePath } from '../paths';
|
||||
import { rebuild } from './rebuild';
|
||||
import { relative } from 'node:path';
|
||||
@@ -20,13 +20,14 @@ import { mergeJsonOutputs } from '@aklinker1/rollup-plugin-visualizer';
|
||||
import { isCI } from 'ci-info';
|
||||
|
||||
/**
|
||||
* Builds the extension based on an internal config. No more config discovery is performed, the
|
||||
* build is based on exactly what is passed in.
|
||||
* Builds the extension based on an internal config. No more config discovery is
|
||||
* performed, the build is based on exactly what is passed in.
|
||||
*
|
||||
* This function:
|
||||
*
|
||||
* 1. Cleans the output directory
|
||||
* 2. Executes the rebuild function with a blank previous output so everything is built (see
|
||||
* `rebuild` for more details)
|
||||
* 2. Executes the rebuild function with a blank previous output so everything is
|
||||
* built (see `rebuild` for more details)
|
||||
* 3. Prints the summary
|
||||
*/
|
||||
export async function internalBuild(): Promise<BuildOutput> {
|
||||
@@ -42,8 +43,8 @@ export async function internalBuild(): Promise<BuildOutput> {
|
||||
const startTime = Date.now();
|
||||
|
||||
// Cleanup
|
||||
await fs.rm(wxt.config.outDir, { recursive: true, force: true });
|
||||
await fs.ensureDir(wxt.config.outDir);
|
||||
await rm(wxt.config.outDir, { recursive: true, force: true });
|
||||
await mkdir(wxt.config.outDir, { recursive: true });
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
wxt.logger.debug('Detected entrypoints:', entrypoints);
|
||||
@@ -99,6 +100,7 @@ async function combineAnalysisStats(): Promise<void> {
|
||||
const unixFiles = await glob(`${wxt.config.analysis.outputName}-*.json`, {
|
||||
cwd: wxt.config.analysis.outputDir,
|
||||
absolute: true,
|
||||
expandDirectories: false,
|
||||
});
|
||||
const absolutePaths = unixFiles.map(unnormalizePath);
|
||||
|
||||
@@ -109,7 +111,9 @@ async function combineAnalysisStats(): Promise<void> {
|
||||
});
|
||||
|
||||
if (!wxt.config.analysis.keepArtifacts) {
|
||||
await Promise.all(absolutePaths.map((statsFile) => fs.remove(statsFile)));
|
||||
await Promise.all(
|
||||
absolutePaths.map((statsFile) => rm(statsFile, { force: true })),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import type { Browser } from '@wxt-dev/browser';
|
||||
import { createSpinner } from 'nanospinner';
|
||||
import { BuildOutput, Entrypoint, EntrypointGroup } from '../../../types';
|
||||
import { generateWxtDir } from '../../generate-wxt-dir';
|
||||
import { buildEntrypoints } from './build-entrypoints';
|
||||
import { generateManifest, writeManifest } from '../../utils/manifest';
|
||||
import { wxt } from '../../wxt';
|
||||
import type { Browser } from '@wxt-dev/browser';
|
||||
import { buildEntrypoints } from './build-entrypoints';
|
||||
|
||||
/**
|
||||
* Given a configuration, list of entrypoints, and an existing, partial output, build the
|
||||
* entrypoints and merge the new output with the existing output.
|
||||
* Given a configuration, list of entrypoints, and an existing, partial output,
|
||||
* build the entrypoints and merge the new output with the existing output.
|
||||
*
|
||||
* This function will:
|
||||
*
|
||||
* 1. Generate the .wxt directory's types
|
||||
* 2. Build the `entrypointGroups` (and copies public files)
|
||||
* 3. Generate the latest manifest for all entrypoints
|
||||
* 4. Write the new manifest to the file system
|
||||
*
|
||||
* @param config Internal config containing all the project information.
|
||||
* @param allEntrypoints List of entrypoints used to generate the types inside .wxt directory.
|
||||
* @param allEntrypoints List of entrypoints used to generate the types inside
|
||||
* .wxt directory.
|
||||
* @param entrypointGroups The list of entrypoint groups to build.
|
||||
* @param existingOutput The previous output to combine the rebuild results into. An emptry array if
|
||||
* this is the first build.
|
||||
* @param existingOutput The previous output to combine the rebuild results
|
||||
* into. An empty array if this is the first build.
|
||||
*/
|
||||
export async function rebuild(
|
||||
allEntrypoints: Entrypoint[],
|
||||
@@ -33,8 +36,7 @@ export async function rebuild(
|
||||
manifest: Browser.runtime.Manifest;
|
||||
warnings: any[][];
|
||||
}> {
|
||||
const { default: ora } = await import('ora');
|
||||
const spinner = ora(`Preparing...`).start();
|
||||
const spinner = createSpinner('Preparing...').start();
|
||||
|
||||
// Update types directory with new files and types
|
||||
await generateWxtDir(allEntrypoints).catch((err) => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import fs, { ensureDir } from 'fs-extra';
|
||||
import { mkdir, readFile } from 'node:fs/promises';
|
||||
import { FsCache } from '../../types';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { writeFileIfDifferent } from './fs';
|
||||
|
||||
/**
|
||||
* A basic file system cache stored at `<srcDir>/.wxt/cache/<key>`. Just caches a string in a
|
||||
* file for the given key.
|
||||
* A basic file system cache stored at `<srcDir>/.wxt/cache/<key>`. Just caches
|
||||
* a string in a file for the given key.
|
||||
*
|
||||
* @param srcDir Absolute path to source directory. See `InternalConfig.srcDir`
|
||||
*/
|
||||
@@ -16,13 +16,13 @@ export function createFsCache(wxtDir: string): FsCache {
|
||||
return {
|
||||
async set(key: string, value: string): Promise<void> {
|
||||
const path = getPath(key);
|
||||
await ensureDir(dirname(path));
|
||||
await mkdir(dirname(path), { recursive: true });
|
||||
await writeFileIfDifferent(path, value);
|
||||
},
|
||||
async get(key: string): Promise<string | undefined> {
|
||||
const path = getPath(key);
|
||||
try {
|
||||
return await fs.readFile(path, 'utf-8');
|
||||
return await readFile(path, 'utf-8');
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Module ID used to build the background in dev mode if the extension doesn't include a background
|
||||
* script/service worker.
|
||||
* Module ID used to build the background in dev mode if the extension doesn't
|
||||
* include a background script/service worker.
|
||||
*/
|
||||
export const VIRTUAL_NOOP_BACKGROUND_MODULE_ID = 'virtual:user-background';
|
||||
|
||||
@@ -4,7 +4,8 @@ import { getEntrypointBundlePath } from './entrypoints';
|
||||
import { ManifestContentScript } from './types';
|
||||
|
||||
/**
|
||||
* Returns a unique and consistent string hash based on a content scripts options.
|
||||
* Returns a unique and consistent string hash based on a content scripts
|
||||
* options.
|
||||
*
|
||||
* It is able to recognize default values,
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Directive names that make up CSPs. There are more, this is all I need for the plugin.
|
||||
* Directive names that make up CSPs. There are more, this is all I need for the
|
||||
* plugin.
|
||||
*/
|
||||
export type CspDirective = 'default-src' | 'script-src' | 'object-src';
|
||||
|
||||
@@ -25,9 +26,7 @@ export class ContentSecurityPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a set of values are listed under a directive.
|
||||
*/
|
||||
/** Ensure a set of values are listed under a directive. */
|
||||
add(directive: CspDirective, ...newValues: string[]): ContentSecurityPolicy {
|
||||
const values = this.data[directive] ?? [];
|
||||
newValues.forEach((newValue) => {
|
||||
|
||||
@@ -27,8 +27,8 @@ export function getEntrypointOutputFile(
|
||||
}
|
||||
|
||||
/**
|
||||
* Return's the entrypoint's output path relative to the output directory. Used for paths in the
|
||||
* manifest and rollup's bundle.
|
||||
* Return's the entrypoint's output path relative to the output directory. Used
|
||||
* for paths in the manifest and rollup's bundle.
|
||||
*/
|
||||
export function getEntrypointBundlePath(
|
||||
entrypoint: Entrypoint,
|
||||
@@ -40,9 +40,7 @@ export function getEntrypointBundlePath(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an entrypoint option, resolve it's value based on a target browser.
|
||||
*/
|
||||
/** Given an entrypoint option, resolve it's value based on a target browser. */
|
||||
export function resolvePerBrowserOption<T>(
|
||||
option: PerBrowserOption<T>,
|
||||
browser: TargetBrowser,
|
||||
@@ -55,8 +53,8 @@ export function resolvePerBrowserOption<T>(
|
||||
/**
|
||||
* Given an entrypoint option, resolve it's value based on a target browser.
|
||||
*
|
||||
* defaultIcon is special, it's the only key that's a record, which can confuse this function. So
|
||||
* it's been manually excluded from resolution.
|
||||
* DefaultIcon is special, it's the only key that's a record, which can confuse
|
||||
* this function. So it's been manually excluded from resolution.
|
||||
*/
|
||||
export function resolvePerBrowserOptions<
|
||||
T extends Record<string, any>,
|
||||
|
||||
@@ -2,9 +2,7 @@ import { config } from 'dotenv';
|
||||
import { expand } from 'dotenv-expand';
|
||||
import type { TargetBrowser } from '../../types';
|
||||
|
||||
/**
|
||||
* Load environment files based on the current mode and browser.
|
||||
*/
|
||||
/** Load environment files based on the current mode and browser. */
|
||||
export function loadEnv(mode: string, browser: TargetBrowser) {
|
||||
return expand(
|
||||
config({
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
import fs from 'fs-extra';
|
||||
import glob from 'fast-glob';
|
||||
import { unnormalizePath } from './paths';
|
||||
import { access, readFile, writeFile } from 'node:fs/promises';
|
||||
import { glob } from 'tinyglobby';
|
||||
import { wxt } from '../wxt';
|
||||
import { unnormalizePath } from './paths';
|
||||
|
||||
export async function pathExists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await access(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function readJson<T = any>(path: string): Promise<T> {
|
||||
return JSON.parse(await readFile(path, 'utf-8'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Only write the contents to a file if it results in a change. This prevents unnecessary file
|
||||
* watchers from being triggered, like WXT's dev server or the TS language server in editors.
|
||||
* Only write the contents to a file if it results in a change. This prevents
|
||||
* unnecessary file watchers from being triggered, like WXT's dev server or the
|
||||
* TS language server in editors.
|
||||
*
|
||||
* @param file The file to write to.
|
||||
* @param newContents The new text content to write.
|
||||
@@ -14,22 +28,19 @@ export async function writeFileIfDifferent(
|
||||
file: string,
|
||||
newContents: string,
|
||||
): Promise<void> {
|
||||
const existingContents = await fs
|
||||
.readFile(file, 'utf-8')
|
||||
.catch(() => undefined);
|
||||
const existingContents = await readFile(file, 'utf-8').catch(() => undefined);
|
||||
|
||||
if (existingContents !== newContents) {
|
||||
await fs.writeFile(file, newContents);
|
||||
await writeFile(file, newContents);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the files in the project's public directory. Returned paths are relative to the
|
||||
* `config.publicDir`.
|
||||
*/
|
||||
export async function getPublicFiles(): Promise<string[]> {
|
||||
if (!(await fs.pathExists(wxt.config.publicDir))) return [];
|
||||
if (!(await pathExists(wxt.config.publicDir))) return [];
|
||||
|
||||
const files = await glob('**/*', { cwd: wxt.config.publicDir });
|
||||
const files = await glob('**/*', {
|
||||
cwd: wxt.config.publicDir,
|
||||
expandDirectories: false,
|
||||
});
|
||||
return files.map(unnormalizePath);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from 'node:path';
|
||||
import pc from 'picocolors';
|
||||
import fs from 'fs-extra';
|
||||
import { lstat } from 'node:fs/promises';
|
||||
import { filesize } from 'filesize';
|
||||
import { printTable } from './printTable';
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function printFileList(
|
||||
];
|
||||
const prefix = i === files.length - 1 ? ' └─' : ' ├─';
|
||||
const color = getChunkColor(file);
|
||||
const stats = await fs.lstat(file);
|
||||
const stats = await lstat(file);
|
||||
totalSize += stats.size;
|
||||
const size = String(filesize(stats.size));
|
||||
return [
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
PopupEntrypoint,
|
||||
SidepanelEntrypoint,
|
||||
} from '../../types';
|
||||
import fs from 'fs-extra';
|
||||
import { mkdir } from 'node:fs/promises';
|
||||
import { resolve } from 'path';
|
||||
import { getEntrypointBundlePath } from './entrypoints';
|
||||
import { ContentSecurityPolicy } from './content-security-policy';
|
||||
@@ -23,9 +23,7 @@ import { wxt } from '../wxt';
|
||||
import { ManifestV3WebAccessibleResource } from './types';
|
||||
import type { Browser } from '@wxt-dev/browser';
|
||||
|
||||
/**
|
||||
* Writes the manifest to the output directory and the build output.
|
||||
*/
|
||||
/** Writes the manifest to the output directory and the build output. */
|
||||
export async function writeManifest(
|
||||
manifest: Browser.runtime.Manifest,
|
||||
output: BuildOutput,
|
||||
@@ -35,7 +33,7 @@ export async function writeManifest(
|
||||
? JSON.stringify(manifest)
|
||||
: JSON.stringify(manifest, null, 2);
|
||||
|
||||
await fs.ensureDir(wxt.config.outDir);
|
||||
await mkdir(wxt.config.outDir, { recursive: true });
|
||||
await writeFileIfDifferent(resolve(wxt.config.outDir, 'manifest.json'), str);
|
||||
|
||||
output.publicAssets.unshift({
|
||||
@@ -44,9 +42,7 @@ export async function writeManifest(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the manifest based on the config and entrypoints.
|
||||
*/
|
||||
/** Generates the manifest based on the config and entrypoints. */
|
||||
export async function generateManifest(
|
||||
allEntrypoints: Entrypoint[],
|
||||
buildOutput: Omit<BuildOutput, 'manifest'>,
|
||||
@@ -155,8 +151,9 @@ export async function generateManifest(
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes suffixes from the version, like X.Y.Z-alpha1 (which browsers don't allow), so it's a
|
||||
* simple version number, like X or X.Y or X.Y.Z, which browsers allow.
|
||||
* Removes suffixes from the version, like X.Y.Z-alpha1 (which browsers don't
|
||||
* allow), so it's a simple version number, like X or X.Y or X.Y.Z, which
|
||||
* browsers allow.
|
||||
*/
|
||||
function simplifyVersion(versionName: string): string {
|
||||
// Regex adapted from here: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/version#version_format
|
||||
@@ -514,8 +511,8 @@ function addDevModePermissions(manifest: Browser.runtime.Manifest) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bundle paths to CSS files associated with a list of content scripts, or undefined if
|
||||
* there is no associated CSS.
|
||||
* Returns the bundle paths to CSS files associated with a list of content
|
||||
* scripts, or undefined if there is no associated CSS.
|
||||
*/
|
||||
export function getContentScriptCssFiles(
|
||||
contentScripts: ContentScriptEntrypoint[],
|
||||
@@ -541,9 +538,9 @@ export function getContentScriptCssFiles(
|
||||
}
|
||||
|
||||
/**
|
||||
* Content scripts configured with `cssInjectionMode: "ui"` need to add their CSS files to web
|
||||
* accessible resources so they can be fetched as text and added to shadow roots that the UI is
|
||||
* added to.
|
||||
* Content scripts configured with `cssInjectionMode: "ui"` need to add their
|
||||
* CSS files to web accessible resources so they can be fetched as text and
|
||||
* added to shadow roots that the UI is added to.
|
||||
*/
|
||||
export function getContentScriptCssWebAccessibleResources(
|
||||
contentScripts: ContentScriptEntrypoint[],
|
||||
@@ -571,8 +568,8 @@ export function getContentScriptCssWebAccessibleResources(
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the build output, return a Record of each content script's name to it CSS file if the
|
||||
* script includes one.
|
||||
* Based on the build output, return a Record of each content script's name to
|
||||
* it CSS file if the script includes one.
|
||||
*/
|
||||
export function getContentScriptsCssMap(
|
||||
buildOutput: Omit<BuildOutput, 'manifest'>,
|
||||
@@ -610,8 +607,8 @@ function addHostPermission(
|
||||
}
|
||||
|
||||
/**
|
||||
* - "<all_urls>" → "<all_urls>"
|
||||
* - "*://play.google.com/books/*" → "*://play.google.com/*"
|
||||
* - "<all_urls>" → "<all_urls>"
|
||||
* - "_://play.google.com/books/_" → "_://play.google.com/_"
|
||||
*/
|
||||
export function stripPathFromMatchPattern(pattern: string) {
|
||||
const protocolSepIndex = pattern.indexOf('://');
|
||||
@@ -622,9 +619,10 @@ export function stripPathFromMatchPattern(pattern: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all MV3 web accessible resources to their MV2 forms. MV3 web accessible resources are
|
||||
* generated in this file, and may be defined by the user in their manifest. In both cases, when
|
||||
* targeting MV2, automatically convert their definitions down to the basic MV2 array.
|
||||
* Converts all MV3 web accessible resources to their MV2 forms. MV3 web
|
||||
* accessible resources are generated in this file, and may be defined by the
|
||||
* user in their manifest. In both cases, when targeting MV2, automatically
|
||||
* convert their definitions down to the basic MV2 array.
|
||||
*/
|
||||
export function convertWebAccessibleResourcesToMv2(
|
||||
manifest: Browser.runtime.Manifest,
|
||||
@@ -674,9 +672,7 @@ function convertCspToMv2(manifest: Browser.runtime.Manifest): void {
|
||||
manifest.content_security_policy.extension_pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure all resources are in MV3 format. If not, add a warning.
|
||||
*/
|
||||
/** Make sure all resources are in MV3 format. If not, add a warning. */
|
||||
function validateMv3WebAccessibleResources(
|
||||
manifest: Browser.runtime.Manifest,
|
||||
): void {
|
||||
@@ -694,9 +690,7 @@ function validateMv3WebAccessibleResources(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove keys from the manifest based on the build target.
|
||||
*/
|
||||
/** Remove keys from the manifest based on the build target. */
|
||||
function stripKeys(manifest: Browser.runtime.Manifest): void {
|
||||
let keysToRemove: string[] = [];
|
||||
if (wxt.config.manifestVersion === 2) {
|
||||
|
||||
@@ -8,10 +8,10 @@ import { minimatch, MinimatchOptions } from 'minimatch';
|
||||
* matches any of the negative patterns, it will return `false`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* minimatchMultiple('a.json', ['*.json', '!b.json']); // => true
|
||||
* minimatchMultiple('b.json', ['*.json', '!b.json']); // => false
|
||||
* ```
|
||||
* ```ts
|
||||
* minimatchMultiple('a.json', ['*.json', '!b.json']); // => true
|
||||
* minimatchMultiple('b.json', ['*.json', '!b.json']); // => false
|
||||
* ```;
|
||||
*/
|
||||
export function minimatchMultiple(
|
||||
search: string,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user