Compare commits

..

10 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
21 changed files with 425 additions and 132 deletions
+33
View File
@@ -1,5 +1,38 @@
# 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)
+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,
};
}
-11
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
@@ -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);
@@ -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({
@@ -328,7 +319,6 @@ 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);
@@ -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
+18
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`.
+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
+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.6",
"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
+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: [],
});
+49 -1
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
@@ -115,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,
@@ -273,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);
}
+14 -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,
-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);
}
+50 -2
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.
@@ -899,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;
@@ -954,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;
};