Compare commits

..

17 Commits

Author SHA1 Message Date
GitHub Actions 86d0c9b397 chore(release): v0.16.8 2024-02-15 21:41:43 +00:00
Aaron c275ccf0da fix: Watch files outside project root during development (#454) 2024-02-15 14:23:32 -06:00
Aaron Klinker 447a48f306 docs: Add loading and error states for "Who's using WXT" section 2024-02-15 10:37:58 -06:00
GitHub Actions 70b5cbc8db chore(release): v0.16.7 2024-02-15 16:35:13 +00:00
Aaron 3459de0375 perf: Upgrade Vite to 5.1 (#452) 2024-02-15 10:31:20 -06:00
Aaron c6817479bd feat: Generate ESLint globals file for auto-imports (#450) 2024-02-15 10:14:57 -06:00
Aaron f02aae21f6 chore(e2e): Use wxt prepare instead of wxt build when possible to speed up E2E tests (#451) 2024-02-15 09:59:36 -06:00
Aaron Klinker 87a62a1791 docs: Remove anchor from content script ui examples 2024-02-14 14:43:34 -06:00
Aaron Klinker a0d1643021 docs: Add section about dev mode differences 2024-02-14 14:41:40 -06:00
William Gérald Blondel 856a946fa7 Fix typo on homepage hero section (#438) 2024-02-11 10:13:17 -06:00
GitHub Actions 9d97164c20 chore(release): v0.16.6 2024-02-10 10:29:52 +00:00
lionelhorn 6d526e7043 feat: Add option to customize the analysis artifacts output (#431)
Co-authored-by: Aaron Klinker <aaronklinker1@gmail.com>
2024-02-10 04:26:33 -06:00
Aaron Klinker 946072fc5a docs: Fix createShadowRootUi unmount calls 2024-02-10 04:04:16 -06:00
Aaron Klinker 6b8dfdf02d chore: Enable skipped test since it works now 2024-02-10 03:01:38 -06:00
okou ba85fdf470 fix: Use insertBefore on mounting content script UI 2024-02-10 03:01:38 -06:00
okou 295f8601e3 refactor: Use Element.prepend on mounting UI 2024-02-10 03:01:38 -06:00
Aaron a38ccd8624 doc: Fix custom block 2024-02-09 05:20:51 -06:00
31 changed files with 614 additions and 155 deletions
+62
View File
@@ -1,5 +1,67 @@
# Changelog
## v0.16.8
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.7...v0.16.8)
### 🩹 Fixes
- Watch files outside project root during development ([#454](https://github.com/wxt-dev/wxt/pull/454))
### 📖 Documentation
- Add loading and error states for "Who's using WXT" section ([447a48f](https://github.com/wxt-dev/wxt/commit/447a48f))
## v0.16.7
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.6...v0.16.7)
### 🚀 Enhancements
- Generate ESLint globals file for auto-imports ([#450](https://github.com/wxt-dev/wxt/pull/450))
### 🔥 Performance
- Upgrade Vite to 5.1 ([#452](https://github.com/wxt-dev/wxt/pull/452))
### 📖 Documentation
- Add section about dev mode differences ([a0d1643](https://github.com/wxt-dev/wxt/commit/a0d1643))
- Remove anchor from content script ui examples ([87a62a1](https://github.com/wxt-dev/wxt/commit/87a62a1))
### 🏡 Chore
- **e2e:** Use `wxt prepare` instead of `wxt build` when possible to speed up E2E tests ([#451](https://github.com/wxt-dev/wxt/pull/451))
## v0.16.6
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.5...v0.16.6)
### 🚀 Enhancements
- Add option to customize the analysis artifacts output ([#431](https://github.com/wxt-dev/wxt/pull/431))
### 🩹 Fixes
- Use `insertBefore` on mounting content script UI ([ba85fdf](https://github.com/wxt-dev/wxt/commit/ba85fdf))
### 💅 Refactors
- Use `Element.prepend` on mounting UI ([295f860](https://github.com/wxt-dev/wxt/commit/295f860))
### 📖 Documentation
- Fix `createShadowRootUi` unmount calls ([946072f](https://github.com/wxt-dev/wxt/commit/946072f))
### 🏡 Chore
- Enable skipped test since it works now ([6b8dfdf](https://github.com/wxt-dev/wxt/commit/6b8dfdf))
### ❤️ Contributors
- Lionelhorn
- Okou
## v0.16.5
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.4...v0.16.5)
+1 -1
View File
@@ -19,7 +19,7 @@
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"vitest": "^1.1.0"
"vitest": "^1.2.2"
},
"devDependencies": {
"@types/react": "^18.2.34",
+12 -2
View File
@@ -20,11 +20,12 @@ const chromeExtensionIds = [
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
];
const { data } = useListExtensionDetails(chromeExtensionIds);
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
const sortedExtensions = computed(() => {
if (!data.value?.length) return [];
return [...data.value]
.filter((item) => item != null)
.map((item) => ({
...item,
// Sort based on the user count weighted by the rating
@@ -49,7 +50,16 @@ function getStoreUrl(extension: ChromeExtension) {
Battle tested and ready for production. Explore chrome extensions made
with WXT.
</p>
<ul>
<p v-if="isLoading" style="text-align: center; opacity: 50%">
Loading...
</p>
<p
v-else-if="err || sortedExtensions.length === 0"
style="text-align: center; opacity: 50%"
>
Failed to load extension details.
</p>
<ul v-else>
<li
v-for="extension of sortedExtensions"
:key="extension.id"
@@ -26,6 +26,7 @@ const query = `query ${operationName}($ids:[String!]!) {
export default function (ids: string[]) {
const data = ref<ChromeExtension[]>();
const err = ref<unknown>();
const isLoading = ref(true);
fetch('https://queue.wxt.dev/api', {
method: 'POST',
@@ -36,6 +37,7 @@ export default function (ids: string[]) {
}),
})
.then(async (res) => {
isLoading.value = false;
const {
data: { chromeExtensions },
} = await res.json();
@@ -43,6 +45,7 @@ export default function (ids: string[]) {
err.value = undefined;
})
.catch((error) => {
isLoading.value = false;
console.error(error);
data.value = undefined;
err.value = error;
@@ -51,5 +54,6 @@ export default function (ids: string[]) {
return {
data,
err,
isLoading,
};
}
+4 -15
View File
@@ -26,7 +26,6 @@ export default defineContentScript({
main(ctx) {
const ui = createIntegratedUi(ctx, {
position: 'inline',
anchor: '#anchor',
onMount: (container) => {
// Append children to the container
const app = document.createElement('p');
@@ -52,7 +51,6 @@ export default defineContentScript({
main(ctx) {
const ui = createIntegratedUi(ctx, {
position: 'inline',
anchor: '#anchor',
onMount: (container) => {
// Create the app and mount it to the UI container
const app = createApp(App);
@@ -82,7 +80,6 @@ export default defineContentScript({
main(ctx) {
const ui = createIntegratedUi(ctx, {
position: 'inline',
anchor: '#anchor',
onMount: (container) => {
// Create a root on the UI container and render a component
const root = ReactDOM.createRoot(container);
@@ -111,7 +108,6 @@ export default defineContentScript({
main(ctx) {
const ui = createIntegratedUi(ctx, {
position: 'inline',
anchor: '#anchor',
onMount: (container) => {
// Create the Svelte app inside the UI container
const app = new App({
@@ -141,7 +137,6 @@ export default defineContentScript({
main(ctx) {
const ui = createIntegratedUi(ctx, {
position: 'inline',
anchor: '#anchor',
onMount: (container) => {
// Render your app to the UI container
const unmount = render(() => <div>...</div>, container);
@@ -192,7 +187,6 @@ export default defineContentScript({
// 3. Define your UI
const ui = await createShadowRootUi(ctx, {
name: 'example-ui',
anchor: '#anchor',
position: 'inline',
onMount(container) {
// Define how your UI will be mounted inside the container
@@ -223,7 +217,6 @@ export default defineContentScript({
// 3. Define your UI
const ui = await createShadowRootUi(ctx, {
name: 'example-ui',
anchor: '#anchor',
position: 'inline',
onMount: (container) => {
// Define how your UI will be mounted inside the container
@@ -233,7 +226,7 @@ export default defineContentScript({
},
onRemove: (app) => {
// Unmount the app when the UI is removed
app.unmount();
app?.unmount();
},
});
@@ -259,7 +252,6 @@ export default defineContentScript({
const ui = await createShadowRootUi(ctx, {
name: 'example-ui',
position: 'inline',
anchor: '#anchor',
onMount: (container) => {
// Create a root on the UI container and render a component
const root = ReactDOM.createRoot(container);
@@ -268,7 +260,7 @@ export default defineContentScript({
},
onRemove: (root) => {
// Unmount the root when the UI is removed
root.unmount();
root?.unmount();
},
});
@@ -293,7 +285,6 @@ export default defineContentScript({
const ui = await createShadowRootUi(ctx, {
name: 'example-ui',
position: 'inline',
anchor: '#anchor',
onMount: (container) => {
// Create the Svelte app inside the UI container
const app = new App({
@@ -303,7 +294,7 @@ export default defineContentScript({
},
onRemove: (app) => {
// Destroy the app when the UI is removed
app.$destroy();
app?.$destroy();
},
});
@@ -328,14 +319,13 @@ export default defineContentScript({
const ui = await createShadowRootUi(ctx, {
name: 'example-ui',
position: 'inline',
anchor: '#anchor',
onMount: (container) => {
// Render your app to the UI container
const unmount = render(() => <div>...</div>, container);
},
onRemove: (unmount) => {
// Unmount the app when the UI is removed
unmount();
unmount?.();
},
});
@@ -398,7 +388,6 @@ WXT provides a helper function, [`createIframeUi`](/api/wxt/client/functions/cre
// Define the UI
const ui = await createIframeUi(ctx, {
page: '/example-iframe.html',
anchor: '#anchor',
position: 'inline',
onMount: (wrapper, iframe) => {
// Add styles to the iframe like width
+19 -1
View File
@@ -13,6 +13,24 @@ WXT's main goal is providing the best DX it possibly can. When running your exte
| `wxt.config.ts` | | | | 🟡 See [#10](https://github.com/wxt-dev/wxt/issues/10) |
| `web-ext.config.ts` | | | | 🟡 See [#10](https://github.com/wxt-dev/wxt/issues/10) |
## Dev Mode vs Production Builds
There are some notible differences between the development and production versions of an extension. During development:
1. **Content scripts are not listed in the `manifest.json`** when targetting MV3. Instead, the [`scripting`](https://developer.chrome.com/docs/extensions/reference/api/scripting) permission is used to register content scripts at runtime so they can be reloaded individually.
To get the list of content scripts during development, run the following in the background's console:
```ts
await chrome.scripting.getRegisteredContentScripts();
```
2. **The CSP is modified to allow loading scripts from the dev server**. Make sure you're using Chrome v110 or above for HMR to work.
3. If you don't include a background script/service worker, one will be created to perform various tasks in dev mode, mostly related to reloading different parts of the extension on change.
For production builds, none of the above modifications will be applied, and you're extension/manifest will only include what you have defined.
## Configure Browser Startup
WXT uses [`web-ext` by Mozilla](https://github.com/mozilla/web-ext) to automatically open a browser with the extension installed. You can configure the runner's behavior via the [`runner`](/api/wxt/interfaces/InlineConfig#runner) option, or in a separate gitignored file, `web-ext.config.ts`.
@@ -83,6 +101,6 @@ Normally, to manually reload an extension, you have to visit `chrome://extension
When running `wxt` command to start the dev server, WXT adds a keyboard shortcut, `ctrl+E` for Windows/Linux and `cmd+E` for Mac, that reloads the extension when pressed, without visiting `chrome://extensions`.
:::note
:::info
This shortcut is only available during development, and is not be added to your extension when running `wxt build` or `wxt-zip`.
:::
+1 -1
View File
@@ -6,7 +6,7 @@ title: Next-gen Web Extension Framework
hero:
name: WXT
text: Next-gen Web Extension Framework
tagline: An open source tool that makes Chrome Extension devlopment faster than ever before.
tagline: An open source tool that makes Chrome Extension development faster than ever before.
image:
src: /hero-logo.svg
alt: WXT
+93
View File
@@ -0,0 +1,93 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { TestProject } from '../utils';
import { resetBundleIncrement } from '~/core/builders/vite/plugins';
describe('Analysis', () => {
beforeEach(() => {
resetBundleIncrement();
});
it('should outptut a stats.html with no part files by default', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html');
project.addFile('entrypoints/options.html');
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
);
await project.build({
analysis: {
enabled: true,
},
});
expect(await project.fileExists('stats.html')).toBe(true);
expect(await project.fileExists('.output/chrome-mv3/stats-0.json')).toBe(
false,
);
});
it('should save part files when requested', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html');
project.addFile('entrypoints/options.html');
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
);
await project.build({
analysis: {
enabled: true,
keepArtifacts: true,
},
});
expect(await project.fileExists('stats.html')).toBe(true);
expect(await project.fileExists('stats-0.json')).toBe(true);
expect(await project.fileExists('stats-1.json')).toBe(true);
});
it('should support customizing the stats output directory', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html');
project.addFile('entrypoints/options.html');
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
);
await project.build({
analysis: {
enabled: true,
outputFile: 'stats/bundle.html',
},
});
expect(await project.fileExists('stats/bundle.html')).toBe(true);
});
it('should place artifacts next to the custom output file', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html');
project.addFile('entrypoints/options.html');
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
);
await project.build({
analysis: {
enabled: true,
outputFile: 'stats/bundle.html',
keepArtifacts: true,
},
});
console.log(project.root);
expect(await project.fileExists('stats/bundle.html')).toBe(true);
expect(await project.fileExists('stats/bundle-0.json')).toBe(true);
expect(await project.fileExists('stats/bundle-1.json')).toBe(true);
});
});
+82 -4
View File
@@ -7,7 +7,7 @@ describe('Auto Imports', () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html', `<html></html>`);
await project.build();
await project.prepare();
expect(await project.serializeFile('.wxt/types/imports.d.ts'))
.toMatchInlineSnapshot(`
@@ -38,7 +38,7 @@ describe('Auto Imports', () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html', `<html></html>`);
await project.build();
await project.prepare();
expect(await project.serializeFile('.wxt/wxt.d.ts'))
.toMatchInlineSnapshot(`
@@ -63,7 +63,7 @@ describe('Auto Imports', () => {
});
project.addFile('entrypoints/popup.html', `<html></html>`);
await project.build();
await project.prepare();
expect(await project.fileExists('.wxt/types/imports.d.ts')).toBe(false);
});
@@ -75,7 +75,7 @@ describe('Auto Imports', () => {
});
project.addFile('entrypoints/popup.html', `<html></html>`);
await project.build();
await project.prepare();
expect(
await project.serializeFile('.wxt/wxt.d.ts'),
@@ -93,4 +93,82 @@ describe('Auto Imports', () => {
);
});
});
describe('eslintrc', () => {
it('should output the globals list for ESLint to consume', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html', `<html></html>`);
await project.prepare({
imports: {
eslintrc: {
enabled: true,
},
},
});
expect(await project.serializeFile('.wxt/eslintrc-auto-import.json'))
.toMatchInlineSnapshot(`
".wxt/eslintrc-auto-import.json
----------------------------------------
{
"globals": {
"ContentScriptContext": true,
"InvalidMatchPattern": true,
"MatchPattern": true,
"browser": true,
"createIframeUi": true,
"createIntegratedUi": true,
"createShadowRootUi": true,
"defineBackground": true,
"defineConfig": true,
"defineContentScript": true,
"defineUnlistedScript": true,
"fakeBrowser": true,
"storage": true
}
}
"
`);
});
it('should allow customizing the output', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html', `<html></html>`);
await project.prepare({
imports: {
eslintrc: {
enabled: true,
filePath: project.resolvePath('example.json'),
globalsPropValue: 'readonly',
},
},
});
expect(await project.serializeFile('example.json'))
.toMatchInlineSnapshot(`
"example.json
----------------------------------------
{
"globals": {
"ContentScriptContext": "readonly",
"InvalidMatchPattern": "readonly",
"MatchPattern": "readonly",
"browser": "readonly",
"createIframeUi": "readonly",
"createIntegratedUi": "readonly",
"createShadowRootUi": "readonly",
"defineBackground": "readonly",
"defineConfig": "readonly",
"defineContentScript": "readonly",
"defineUnlistedScript": "readonly",
"fakeBrowser": "readonly",
"storage": "readonly"
}
}
"
`);
});
});
});
+7 -7
View File
@@ -6,7 +6,7 @@ describe('TypeScript Project', () => {
const project = new TestProject();
project.addFile('entrypoints/unlisted.html');
await project.build();
await project.prepare();
const output = await project.serializeFile('.wxt/types/globals.d.ts');
expect(output).toMatchInlineSnapshot(`
@@ -38,7 +38,7 @@ describe('TypeScript Project', () => {
project.addFile('entrypoints/options.html');
project.addFile('entrypoints/sandbox.html');
await project.build();
await project.prepare();
const output = await project.serializeFile('.wxt/types/paths.d.ts');
expect(output).toMatchInlineSnapshot(`
@@ -103,7 +103,7 @@ describe('TypeScript Project', () => {
},
});
await project.build();
await project.prepare();
const output = await project.serializeFile('.wxt/types/i18n.d.ts');
expect(output).toMatchInlineSnapshot(`
@@ -225,7 +225,7 @@ describe('TypeScript Project', () => {
const project = new TestProject();
project.addFile('entrypoints/unlisted.html');
await project.build();
await project.prepare();
const output = await project.serializeFile('.wxt/wxt.d.ts');
expect(output).toMatchInlineSnapshot(`
@@ -245,7 +245,7 @@ describe('TypeScript Project', () => {
const project = new TestProject();
project.addFile('entrypoints/unlisted.html');
await project.build();
await project.prepare();
const output = await project.serializeFile('.wxt/tsconfig.json');
expect(output).toMatchInlineSnapshot(`
@@ -289,7 +289,7 @@ describe('TypeScript Project', () => {
srcDir: 'src',
});
await project.build();
await project.prepare();
const output = await project.serializeFile('.wxt/tsconfig.json');
expect(output).toMatchInlineSnapshot(`
@@ -337,7 +337,7 @@ describe('TypeScript Project', () => {
},
});
await project.build();
await project.prepare();
const output = await project.serializeFile('.wxt/tsconfig.json');
expect(output).toMatchInlineSnapshot(`
+13 -6
View File
@@ -92,12 +92,19 @@ export class TestProject {
return server;
}
/**
* Call `path.resolve` relative to the project's root directory.
*/
resolvePath(...path: string[]): string {
return resolve(this.root, ...path);
}
private async writeProjectToDisk() {
if (this.config == null) this.setConfigFileConfig();
for (const file of this.files) {
const [name, content] = file;
const filePath = resolve(this.root, name);
const filePath = this.resolvePath(name);
const fileDir = dirname(filePath);
await fs.ensureDir(fileDir);
await fs.writeFile(filePath, content ?? '', 'utf-8');
@@ -140,13 +147,13 @@ export class TestProject {
ignoreContentsOfFilenames?: string[],
): Promise<string> {
const outputFiles = await glob('**/*', {
cwd: resolve(this.root, dir),
cwd: this.resolvePath(dir),
ignore: ['**/node_modules', '**/.output'],
});
outputFiles.sort();
const fileContents = [];
for (const file of outputFiles) {
const path = resolve(this.root, dir, file);
const path = this.resolvePath(dir, file);
const isContentIgnored = !!ignoreContentsOfFilenames?.find(
(ignoredFile) => normalizePath(path).endsWith(ignoredFile),
);
@@ -161,7 +168,7 @@ export class TestProject {
* the file contents.
*/
async serializeFile(path: string, ignoreContents?: boolean): Promise<string> {
const absolutePath = resolve(this.root, path);
const absolutePath = this.resolvePath(path);
return [
normalizePath(relative(this.root, absolutePath)),
ignoreContents ? '<contents-ignored>' : await fs.readFile(absolutePath),
@@ -169,12 +176,12 @@ export class TestProject {
}
fileExists(path: string): Promise<boolean> {
return fs.exists(resolve(this.root, path));
return fs.exists(this.resolvePath(path));
}
async getOutputManifest(
path: string = '.output/chrome-mv3/manifest.json',
): Promise<any> {
return await fs.readJson(resolve(this.root, path));
return await fs.readJson(this.resolvePath(path));
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.16.5",
"version": "0.16.8",
"description": "Next gen framework for developing web extensions",
"engines": {
"node": ">=18",
@@ -135,7 +135,7 @@
"publish-browser-extension": "^2.1.2",
"rollup-plugin-visualizer": "^5.9.2",
"unimport": "^3.4.0",
"vite": "^5.0.12",
"vite": "^5.1.3",
"web-ext-run": "^0.2.0",
"webextension-polyfill": "^0.10.0",
"zip-dir": "^2.0.0"
@@ -165,7 +165,7 @@
"typedoc-vitepress-theme": "1.0.0-next.3",
"typescript": "^5.3.2",
"vitepress": "1.0.0-rc.34",
"vitest": "^1.1.0",
"vitest": "^1.2.2",
"vitest-mock-extended": "^1.3.1",
"vue": "^3.3.10"
},
+66 -52
View File
@@ -105,8 +105,8 @@ importers:
specifier: ^3.4.0
version: 3.4.0
vite:
specifier: ^5.0.12
version: 5.0.12(@types/node@20.10.3)(sass@1.69.5)
specifier: ^5.1.3
version: 5.1.3(@types/node@20.10.3)(sass@1.69.5)
web-ext-run:
specifier: ^0.2.0
version: 0.2.0
@@ -140,7 +140,7 @@ importers:
version: 2.4.9
'@vitest/coverage-v8':
specifier: ^1.0.1
version: 1.2.2(vitest@1.1.0)
version: 1.2.2(vitest@1.2.2)
execa:
specifier: ^8.0.1
version: 8.0.1
@@ -190,11 +190,11 @@ importers:
specifier: 1.0.0-rc.34
version: 1.0.0-rc.34(@types/node@20.10.3)(typescript@5.3.3)
vitest:
specifier: ^1.1.0
version: 1.1.0(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5)
specifier: ^1.2.2
version: 1.2.2(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5)
vitest-mock-extended:
specifier: ^1.3.1
version: 1.3.1(typescript@5.3.3)(vitest@1.1.0)
version: 1.3.1(typescript@5.3.3)(vitest@1.2.2)
vue:
specifier: ^3.3.10
version: 3.3.10(typescript@5.3.3)
@@ -208,8 +208,8 @@ importers:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
vitest:
specifier: ^1.1.0
version: 1.1.0(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5)
specifier: ^1.2.2
version: 1.2.2(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5)
devDependencies:
'@types/react':
specifier: ^18.2.34
@@ -1058,7 +1058,6 @@ packages:
/@types/estree@1.0.4:
resolution: {integrity: sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw==}
dev: false
/@types/fs-extra@11.0.4:
resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==}
@@ -1168,18 +1167,18 @@ packages:
dev: false
optional: true
/@vitejs/plugin-vue@5.0.2(vite@5.0.12)(vue@3.4.3):
/@vitejs/plugin-vue@5.0.2(vite@5.1.3)(vue@3.4.3):
resolution: {integrity: sha512-kEjJHrLb5ePBvjD0SPZwJlw1QTRcjjCA9sB5VyfonoXVBxTS7TMnqL6EkLt1Eu61RDeiuZ/WN9Hf6PxXhPI2uA==}
engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies:
vite: ^5.0.0
vue: ^3.2.25
dependencies:
vite: 5.0.12(@types/node@20.10.3)(sass@1.69.5)
vite: 5.1.3(@types/node@20.10.3)(sass@1.69.5)
vue: 3.4.3(typescript@5.3.3)
dev: true
/@vitest/coverage-v8@1.2.2(vitest@1.1.0):
/@vitest/coverage-v8@1.2.2(vitest@1.2.2):
resolution: {integrity: sha512-IHyKnDz18SFclIEEAHb9Y4Uxx0sPKC2VO1kdDCs1BF6Ip4S8rQprs971zIsooLUn7Afs71GRxWMWpkCGZpRMhw==}
peerDependencies:
vitest: ^1.0.0
@@ -1197,41 +1196,42 @@ packages:
std-env: 3.6.0
test-exclude: 6.0.0
v8-to-istanbul: 9.2.0
vitest: 1.1.0(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5)
vitest: 1.2.2(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5)
transitivePeerDependencies:
- supports-color
dev: true
/@vitest/expect@1.1.0:
resolution: {integrity: sha512-9IE2WWkcJo2BR9eqtY5MIo3TPmS50Pnwpm66A6neb2hvk/QSLfPXBz2qdiwUOQkwyFuuXEUj5380CbwfzW4+/w==}
/@vitest/expect@1.2.2:
resolution: {integrity: sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==}
dependencies:
'@vitest/spy': 1.1.0
'@vitest/utils': 1.1.0
'@vitest/spy': 1.2.2
'@vitest/utils': 1.2.2
chai: 4.3.10
/@vitest/runner@1.1.0:
resolution: {integrity: sha512-zdNLJ00pm5z/uhbWF6aeIJCGMSyTyWImy3Fcp9piRGvueERFlQFbUwCpzVce79OLm2UHk9iwaMSOaU9jVHgNVw==}
/@vitest/runner@1.2.2:
resolution: {integrity: sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==}
dependencies:
'@vitest/utils': 1.1.0
'@vitest/utils': 1.2.2
p-limit: 5.0.0
pathe: 1.1.1
/@vitest/snapshot@1.1.0:
resolution: {integrity: sha512-5O/wyZg09V5qmNmAlUgCBqflvn2ylgsWJRRuPrnHEfDNT6tQpQ8O1isNGgo+VxofISHqz961SG3iVvt3SPK/QQ==}
/@vitest/snapshot@1.2.2:
resolution: {integrity: sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==}
dependencies:
magic-string: 0.30.5
pathe: 1.1.1
pretty-format: 29.7.0
/@vitest/spy@1.1.0:
resolution: {integrity: sha512-sNOVSU/GE+7+P76qYo+VXdXhXffzWZcYIPQfmkiRxaNCSPiLANvQx5Mx6ZURJ/ndtEkUJEpvKLXqAYTKEY+lTg==}
/@vitest/spy@1.2.2:
resolution: {integrity: sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==}
dependencies:
tinyspy: 2.2.0
/@vitest/utils@1.1.0:
resolution: {integrity: sha512-z+s510fKmYz4Y41XhNs3vcuFTFhcij2YF7F8VQfMEYAAUfqQh0Zfg7+w9xdgFGhPf3tX3TicAe+8BDITk6ampQ==}
/@vitest/utils@1.2.2:
resolution: {integrity: sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==}
dependencies:
diff-sequences: 29.6.3
estree-walker: 3.0.3
loupe: 2.3.7
pretty-format: 29.7.0
@@ -1293,7 +1293,7 @@ packages:
'@vue/shared': 3.4.3
estree-walker: 2.0.2
magic-string: 0.30.5
postcss: 8.4.32
postcss: 8.4.35
source-map-js: 1.0.2
dev: true
@@ -1484,8 +1484,8 @@ packages:
resolution: {integrity: sha512-NY39ACqCxdKBmHgw361M9pfJma8e4AZo20w9AY+5ZjIj1W2dvXC8J31G5fjfOGbulW9w4WKpT8fPooi0mLkn9A==}
dev: false
/acorn-walk@8.3.0:
resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==}
/acorn-walk@8.3.2:
resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
engines: {node: '>=0.4.0'}
/acorn@8.11.2:
@@ -2406,6 +2406,11 @@ packages:
/estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
/estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
dependencies:
'@types/estree': 1.0.4
/eventemitter3@5.0.1:
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
@@ -4109,6 +4114,15 @@ packages:
nanoid: 3.3.7
picocolors: 1.0.0
source-map-js: 1.0.2
dev: true
/postcss@8.4.35:
resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.7
picocolors: 1.0.0
source-map-js: 1.0.2
/preact@10.18.1:
resolution: {integrity: sha512-mKUD7RRkQQM6s7Rkmi7IFkoEHjuFqRQUaXamO61E6Nn7vqF/bo7EZCmSyrUnp2UWHw0O7XjZ2eeXis+m7tf4lg==}
@@ -4885,8 +4899,8 @@ packages:
/tinybench@2.5.1:
resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==}
/tinypool@0.8.1:
resolution: {integrity: sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==}
/tinypool@0.8.2:
resolution: {integrity: sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==}
engines: {node: '>=14.0.0'}
/tinyspy@2.2.0:
@@ -5178,8 +5192,8 @@ packages:
spdx-expression-parse: 3.0.1
dev: true
/vite-node@1.1.0(@types/node@20.10.3)(sass@1.69.5):
resolution: {integrity: sha512-jV48DDUxGLEBdHCQvxL1mEh7+naVy+nhUUUaPAZLd3FJgXuxQiewHcfeZebbJ6onDqNGkP4r3MhQ342PRlG81Q==}
/vite-node@1.2.2(@types/node@20.10.3)(sass@1.69.5):
resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
dependencies:
@@ -5187,7 +5201,7 @@ packages:
debug: 4.3.4
pathe: 1.1.1
picocolors: 1.0.0
vite: 5.0.12(@types/node@20.10.3)(sass@1.69.5)
vite: 5.1.3(@types/node@20.10.3)(sass@1.69.5)
transitivePeerDependencies:
- '@types/node'
- less
@@ -5198,8 +5212,8 @@ packages:
- supports-color
- terser
/vite@5.0.12(@types/node@20.10.3)(sass@1.69.5):
resolution: {integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==}
/vite@5.1.3(@types/node@20.10.3)(sass@1.69.5):
resolution: {integrity: sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
@@ -5228,7 +5242,7 @@ packages:
dependencies:
'@types/node': 20.10.3
esbuild: 0.19.11
postcss: 8.4.32
postcss: 8.4.35
rollup: 4.6.1
sass: 1.69.5
optionalDependencies:
@@ -5249,7 +5263,7 @@ packages:
'@docsearch/css': 3.5.2
'@docsearch/js': 3.5.2
'@types/markdown-it': 13.0.7
'@vitejs/plugin-vue': 5.0.2(vite@5.0.12)(vue@3.4.3)
'@vitejs/plugin-vue': 5.0.2(vite@5.1.3)(vue@3.4.3)
'@vue/devtools-api': 6.5.1
'@vueuse/core': 10.7.1(vue@3.4.3)
'@vueuse/integrations': 10.7.1(focus-trap@7.5.4)(vue@3.4.3)
@@ -5260,7 +5274,7 @@ packages:
shikiji: 0.9.16
shikiji-core: 0.9.16
shikiji-transformers: 0.9.16
vite: 5.0.12(@types/node@20.10.3)(sass@1.69.5)
vite: 5.1.3(@types/node@20.10.3)(sass@1.69.5)
vue: 3.4.3(typescript@5.3.3)
transitivePeerDependencies:
- '@algolia/client-search'
@@ -5290,7 +5304,7 @@ packages:
- universal-cookie
dev: true
/vitest-mock-extended@1.3.1(typescript@5.3.3)(vitest@1.1.0):
/vitest-mock-extended@1.3.1(typescript@5.3.3)(vitest@1.2.2):
resolution: {integrity: sha512-OpghYjh4BDuQ/Mzs3lFMQ1QRk9D8/2O9T47MLUA5eLn7K4RWIy+MfIivYOWEyxjTENjsBnzgMihDjyNalN/K0Q==}
peerDependencies:
typescript: 3.x || 4.x || 5.x
@@ -5298,11 +5312,11 @@ packages:
dependencies:
ts-essentials: 9.3.2(typescript@5.3.3)
typescript: 5.3.3
vitest: 1.1.0(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5)
vitest: 1.2.2(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5)
dev: true
/vitest@1.1.0(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5):
resolution: {integrity: sha512-oDFiCrw7dd3Jf06HoMtSRARivvyjHJaTxikFxuqJjO76U436PqlVw1uLn7a8OSPrhSfMGVaRakKpA2lePdw79A==}
/vitest@1.2.2(@types/node@20.10.3)(happy-dom@13.3.8)(sass@1.69.5):
resolution: {integrity: sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
@@ -5327,12 +5341,12 @@ packages:
optional: true
dependencies:
'@types/node': 20.10.3
'@vitest/expect': 1.1.0
'@vitest/runner': 1.1.0
'@vitest/snapshot': 1.1.0
'@vitest/spy': 1.1.0
'@vitest/utils': 1.1.0
acorn-walk: 8.3.0
'@vitest/expect': 1.2.2
'@vitest/runner': 1.2.2
'@vitest/snapshot': 1.2.2
'@vitest/spy': 1.2.2
'@vitest/utils': 1.2.2
acorn-walk: 8.3.2
cac: 6.7.14
chai: 4.3.10
debug: 4.3.4
@@ -5345,9 +5359,9 @@ packages:
std-env: 3.6.0
strip-literal: 1.3.0
tinybench: 2.5.1
tinypool: 0.8.1
vite: 5.0.12(@types/node@20.10.3)(sass@1.69.5)
vite-node: 1.1.0(@types/node@20.10.3)(sass@1.69.5)
tinypool: 0.8.2
vite: 5.1.3(@types/node@20.10.3)(sass@1.69.5)
vite-node: 1.2.2(@types/node@20.10.3)(sass@1.69.5)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less
@@ -367,7 +367,7 @@ describe('Content Script UIs', () => {
});
});
describe.todo('after', () => {
describe('after', () => {
it('should append the UI after the anchor', () => {
const ui = createIntegratedUi(ctx, {
position: 'inline',
+3 -7
View File
@@ -211,20 +211,16 @@ function mountUi(
anchor.append(root);
break;
case 'first':
if (anchor.firstChild) {
anchor.insertBefore(root, anchor.firstChild);
} else {
anchor.append(root);
}
anchor.prepend(root);
break;
case 'replace':
anchor.replaceWith(root);
break;
case 'after':
anchor.replaceWith(anchor, root);
anchor.parentElement?.insertBefore(root, anchor.nextElementSibling);
break;
case 'before':
anchor.replaceWith(root, anchor);
anchor.parentElement?.insertBefore(root, anchor);
break;
default:
options.append(anchor, root);
@@ -10,6 +10,16 @@ export function bundleAnalysis(
): vite.Plugin {
return visualizer({
template: 'raw-data',
filename: path.resolve(config.outDir, `stats-${increment++}.json`),
}) as vite.Plugin;
filename: path.resolve(
config.analysis.outputDir,
`${config.analysis.outputName}-${increment++}.json`,
),
});
}
/**
* @deprecated FOR TESTING ONLY.
*/
export function resetBundleIncrement() {
increment = 0;
}
+1 -2
View File
@@ -1,6 +1,5 @@
import { createUnimport } from 'unimport';
import { ResolvedConfig } from '~/types';
import { getUnimportOptions } from '~/core/utils/unimport';
import type * as vite from 'vite';
import { extname } from 'path';
@@ -19,7 +18,7 @@ const ENABLED_EXTENSIONS = new Set([
export function unimport(
config: Omit<ResolvedConfig, 'builder'>,
): vite.PluginOption {
const options = getUnimportOptions(config);
const options = config.imports;
if (options === false) return [];
const unimport = createUnimport(options);
+36
View File
@@ -25,6 +25,7 @@ import { Mutex } from 'async-mutex';
import pc from 'picocolors';
import { relative } from 'node:path';
import { registerWxt, wxt } from './wxt';
import { unnormalizePath } from './utils/paths';
/**
* Creates a dev server and pre-builds all the files that need to exist before loading the extension.
@@ -51,6 +52,14 @@ export async function createServer(
// Build after starting the dev server so it can be used to transform HTML files
server.currentOutput = await internalBuild();
// Add file watchers for files not loaded by the dev server. See
// https://github.com/wxt-dev/wxt/issues/428#issuecomment-1944731870
try {
server.watcher.add(getExternalOutputDependencies(server));
} catch (err) {
wxt.config.logger.warn('Failed to register additional file paths:', err);
}
// Open browser after everything is ready to go.
await runner.openBrowser();
};
@@ -278,3 +287,30 @@ function getFilenameList(names: string[]): string {
})
.join(pc.dim(', '));
}
/**
* Based on the current build output, return a list of files that are:
* 1. Not in node_modules
* 2. Not inside project root
*/
function getExternalOutputDependencies(server: WxtDevServer) {
return (
server.currentOutput?.steps
.flatMap((step, i) => {
if (Array.isArray(step.entrypoints) && i === 0) {
// Dev server is already watching all HTML/esm files
return [];
}
return step.chunks.flatMap((chunk) => {
if (chunk.type === 'asset') return [];
return chunk.moduleIds;
});
})
.filter(
(file) => !file.includes('node_modules') && !file.startsWith('\x00'),
)
.map(unnormalizePath)
.filter((file) => !file.startsWith(wxt.config.root)) ?? []
);
}
+30 -10
View File
@@ -1,9 +1,12 @@
import { UnimportOptions, createUnimport } from 'unimport';
import { Entrypoint } from '~/types';
import { Unimport, createUnimport } from 'unimport';
import {
EslintGlobalsPropValue,
Entrypoint,
WxtResolvedUnimportOptions,
} from '~/types';
import fs from 'fs-extra';
import { relative, resolve } from 'path';
import { getEntrypointBundlePath } from '~/core/utils/entrypoints';
import { getUnimportOptions } from '~/core/utils/unimport';
import { getEntrypointGlobals, getGlobals } from '~/core/utils/globals';
import { normalizePath } from '~/core/utils/paths';
import path from 'node:path';
@@ -21,9 +24,12 @@ export async function generateTypesDir(
const references: string[] = [];
const imports = getUnimportOptions(wxt.config);
if (imports !== false) {
references.push(await writeImportsDeclarationFile(imports));
if (wxt.config.imports !== false) {
const unimport = createUnimport(wxt.config.imports);
references.push(await writeImportsDeclarationFile(unimport));
if (wxt.config.imports.eslintrc.enabled) {
await writeImportsEslintFile(unimport, wxt.config.imports);
}
}
references.push(await writePathsDeclarationFile(entrypoints));
@@ -34,11 +40,8 @@ export async function generateTypesDir(
await writeTsConfigFile(mainReference);
}
async function writeImportsDeclarationFile(
unimportOptions: Partial<UnimportOptions>,
): Promise<string> {
async function writeImportsDeclarationFile(unimport: Unimport) {
const filePath = resolve(wxt.config.typesDir, 'imports.d.ts');
const unimport = createUnimport(unimportOptions);
// Load project imports into unimport memory so they are output via generateTypeDeclarations
await unimport.scanImportsFromDir(undefined, { cwd: wxt.config.srcDir });
@@ -53,6 +56,23 @@ async function writeImportsDeclarationFile(
return filePath;
}
async function writeImportsEslintFile(
unimport: Unimport,
options: WxtResolvedUnimportOptions,
) {
const globals: Record<string, EslintGlobalsPropValue> = {};
const eslintrc = { globals };
(await unimport.getImports())
.map((i) => i.as ?? i.name)
.filter(Boolean)
.sort()
.forEach((name) => {
eslintrc.globals[name] = options.eslintrc.globalsPropValue;
});
await fs.writeJson(options.eslintrc.filePath, eslintrc, { spaces: 2 });
}
async function writePathsDeclarationFile(
entrypoints: Entrypoint[],
): Promise<string> {
+1 -2
View File
@@ -2,7 +2,6 @@ import createJITI, { TransformOptions as JitiTransformOptions } from 'jiti';
import { createUnimport } from 'unimport';
import fs from 'fs-extra';
import { relative, resolve } from 'node:path';
import { getUnimportOptions } from '~/core/utils/unimport';
import { removeProjectImportStatements } from '~/core/utils/strings';
import { normalizePath } from '~/core/utils/paths';
import { TransformOptions, transformSync } from 'esbuild';
@@ -30,7 +29,7 @@ export async function importEntrypointFile<T>(path: string): Promise<T> {
const normalPath = normalizePath(path);
const unimport = createUnimport({
...getUnimportOptions(wxt.config),
...wxt.config.imports,
// Only allow specific imports, not all from the project
dirs: [],
});
+6 -3
View File
@@ -87,8 +87,8 @@ export async function internalBuild(): Promise<BuildOutput> {
}
async function combineAnalysisStats(): Promise<void> {
const unixFiles = await glob(`stats-*.json`, {
cwd: wxt.config.outDir,
const unixFiles = await glob(`${wxt.config.analysis.outputName}-*.json`, {
cwd: wxt.config.analysis.outputDir,
absolute: true,
});
const absolutePaths = unixFiles.map(unnormalizePath);
@@ -104,7 +104,10 @@ async function combineAnalysisStats(): Promise<void> {
],
{ cwd: wxt.config.root, stdio: 'inherit' },
);
await Promise.all(absolutePaths.map((statsFile) => fs.remove(statsFile)));
if (!wxt.config.analysis.keepArtifacts) {
await Promise.all(absolutePaths.map((statsFile) => fs.remove(statsFile)));
}
}
function printValidationResults({
+60 -5
View File
@@ -8,6 +8,8 @@ import {
UserManifest,
ExtensionRunnerConfig,
WxtDevServer,
WxtResolvedUnimportOptions,
Logger,
} from '~/types';
import path from 'node:path';
import { createFsCache } from '~/core/utils/cache';
@@ -15,6 +17,7 @@ import consola, { LogLevels } from 'consola';
import { createViteBuilder } from '~/core/builders/vite';
import defu from 'defu';
import { NullablyRequired } from '../types';
import { isModuleInstalled } from '../package';
/**
* Given an inline config, discover the config file if necessary, merge the results, resolve any
@@ -100,6 +103,13 @@ export async function resolveConfig(
}).map(([key, value]) => [key, path.resolve(root, value)]),
);
const analysisOutputFile = path.resolve(
root,
mergedConfig.analysis?.outputFile ?? 'stats.html',
);
const analysisOutputDir = path.dirname(analysisOutputFile);
const analysisOutputName = path.parse(analysisOutputFile).name;
const finalConfig: Omit<ResolvedConfig, 'builder'> = {
browser,
command,
@@ -108,7 +118,7 @@ export async function resolveConfig(
filterEntrypoints,
env,
fsCache: createFsCache(wxtDir),
imports: mergedConfig.imports ?? {},
imports: await getUnimportOptions(wxtDir, logger, mergedConfig),
logger,
manifest: await resolveManifestConfig(env, mergedConfig.manifest),
manifestVersion,
@@ -129,10 +139,10 @@ export async function resolveConfig(
analysis: {
enabled: mergedConfig.analysis?.enabled ?? false,
template: mergedConfig.analysis?.template ?? 'treemap',
outputFile: path.resolve(
root,
mergedConfig.analysis?.outputFile ?? 'stats.html',
),
outputFile: analysisOutputFile,
outputDir: analysisOutputDir,
outputName: analysisOutputName,
keepArtifacts: mergedConfig.analysis?.keepArtifacts ?? false,
},
userConfigMetadata: userConfigMetadata ?? {},
alias,
@@ -266,3 +276,48 @@ function resolveInternalZipConfig(
],
};
}
async function getUnimportOptions(
wxtDir: string,
logger: Logger,
config: InlineConfig,
): Promise<WxtResolvedUnimportOptions | false> {
if (config.imports === false) return false;
const enabledConfig = config.imports?.eslintrc?.enabled;
let enabled: boolean;
switch (enabledConfig) {
case undefined:
case 'auto':
enabled = await isModuleInstalled('eslint');
break;
default:
enabled = enabledConfig;
}
const defaultOptions: WxtResolvedUnimportOptions = {
debugLog: logger.debug,
imports: [
{ name: 'defineConfig', from: 'wxt' },
{ name: 'fakeBrowser', from: 'wxt/testing' },
],
presets: [
{ package: 'wxt/client' },
{ package: 'wxt/browser' },
{ package: 'wxt/sandbox' },
{ package: 'wxt/storage' },
],
warn: logger.warn,
dirs: ['components', 'composables', 'hooks', 'utils'],
eslintrc: {
enabled,
filePath: path.resolve(wxtDir, 'eslintrc-auto-import.json'),
globalsPropValue: true,
},
};
return defu<WxtResolvedUnimportOptions, [WxtResolvedUnimportOptions]>(
config.imports ?? {},
defaultOptions,
);
}
+4
View File
@@ -20,3 +20,7 @@ export async function getPackageJson(): Promise<
return {};
}
}
export function isModuleInstalled(name: string) {
return import(name).then(() => true).catch(() => false);
}
+17 -1
View File
@@ -216,7 +216,20 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
entrypointsDir: fakeDir(),
env: { browser, command, manifestVersion, mode },
fsCache: mock<FsCache>(),
imports: {},
imports: {
eslintrc: {
enabled: faker.datatype.boolean(),
filePath: fakeFile(),
globalsPropValue: faker.helpers.arrayElement([
true,
false,
'readable',
'readonly',
'writable',
'writeable',
] as const),
},
},
logger: mock(),
manifest: fakeUserManifest(),
manifestVersion,
@@ -237,6 +250,9 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
enabled: false,
template: 'treemap',
outputFile: fakeFile(),
outputDir: fakeDir(),
outputName: 'stats',
keepArtifacts: false,
},
zip: {
artifactTemplate: '{{name}}-{{version}}.zip',
-27
View File
@@ -1,27 +0,0 @@
import { UnimportOptions } from 'unimport';
import { ResolvedConfig } from '~/types';
import { defu } from 'defu';
export function getUnimportOptions(
config: Omit<ResolvedConfig, 'builder'>,
): Partial<UnimportOptions | false> {
if (config.imports === false) return false;
const defaultOptions: Partial<UnimportOptions> = {
debugLog: config.logger.debug,
imports: [
{ name: 'defineConfig', from: 'wxt' },
{ name: 'fakeBrowser', from: 'wxt/testing' },
],
presets: [
{ package: 'wxt/client' },
{ package: 'wxt/browser' },
{ package: 'wxt/sandbox' },
{ package: 'wxt/storage' },
],
warn: config.logger.warn,
dirs: ['components', 'composables', 'hooks', 'utils'],
};
return defu(config.imports, defaultOptions);
}
+71 -3
View File
@@ -77,7 +77,7 @@ export interface InlineConfig {
* })
* ```
*/
imports?: Partial<UnimportOptions> | false;
imports?: WxtUnimportOptions | false;
/**
* Explicitly set a browser to build for. This will override the default browser for each command,
* and can be overridden by the command line `--browser` option.
@@ -212,9 +212,24 @@ export interface InlineConfig {
/**
* Name of the output HTML file. Relative to the project's root directory.
*
* Changing the filename of the outputFile also effects the names of the artifacts generated
* when setting `keepArtifacts` to true:
* - "stats.html" => "stats-*.json"
* - "stats/bundle.html" => "bundle-*.json"
* - ".analysis/index.html" => "index-*.json"
*
* @default "stats.html"
*/
outputFile?: string;
/**
* By default, the `stats-*.json` artifacts generated during bundle analysis are deleted. Set to
* `true` to keep them.
*
* One stats file is output per build step.
*
* @default false
*/
keepArtifacts?: boolean;
};
/**
* Add additional paths to the `.wxt/tsconfig.json`. Use this instead of overwriting the `paths`
@@ -884,7 +899,7 @@ export interface ResolvedConfig {
manifestVersion: TargetManifestVersion;
env: ConfigEnv;
logger: Logger;
imports: false | Partial<UnimportOptions>;
imports: false | WxtResolvedUnimportOptions;
manifest: UserManifest;
fsCache: FsCache;
server?: WxtDevServer;
@@ -901,8 +916,13 @@ export interface ResolvedConfig {
analysis: {
enabled: boolean;
template: NonNullable<PluginVisualizerOptions['template']>;
/** Absolute file path */
/** Absolute file path to the `stats.html` file */
outputFile: string;
/** The directory where the final `stats.html` file is located */
outputDir: string;
/** Name of the `stats.html` file, minus ".html" */
outputName: string;
keepArtifacts: boolean;
};
userConfigMetadata: Omit<C12ResolvedConfig<UserConfig>, 'config'>;
/**
@@ -934,3 +954,51 @@ export type VirtualEntrypointType =
| 'content-script-isolated-world'
| 'background'
| 'unlisted-script';
export type EslintGlobalsPropValue =
| boolean
| 'readonly'
| 'readable'
| 'writable'
| 'writeable';
export interface Eslintrc {
/**
* When true, generates a file that can be used by ESLint to know which variables are valid globals.
*
* - `'auto'`: Check if eslint is installed, and if it is, generate the helper file
* - `true`: Generate the helper file
* - `false`: Don't generate the file
*
* @default 'auto'
*/
enabled?: boolean | 'auto';
/**
* File path to save the generated eslint config.
*
* @default './.wxt/eslintrc-auto-import.json'
*/
filePath?: string;
/**
* @default true
*/
globalsPropValue?: EslintGlobalsPropValue;
}
export interface ResolvedEslintrc {
enabled: boolean;
/** Absolute path */
filePath: string;
globalsPropValue: EslintGlobalsPropValue;
}
export type WxtUnimportOptions = Partial<UnimportOptions> & {
/**
* When eslint is installed,
*/
eslintrc?: Eslintrc;
};
export type WxtResolvedUnimportOptions = Partial<UnimportOptions> & {
eslintrc: ResolvedEslintrc;
};
+1
View File
@@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
.output
stats.html
stats-*.json
.wxt
web-ext.config.ts
+1
View File
@@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
.output
stats.html
stats-*.json
.wxt
web-ext.config.ts
+1
View File
@@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
.output
stats.html
stats-*.json
.wxt
web-ext.config.ts
+1
View File
@@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
.output
stats.html
stats-*.json
.wxt
web-ext.config.ts
+1
View File
@@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
.output
stats.html
stats-*.json
.wxt
web-ext.config.ts