Compare commits

...

20 Commits

Author SHA1 Message Date
GitHub Actions 1527f0e275 chore(release): v0.16.2 2024-02-03 21:02:28 +00:00
Aaron 66bd957ee0 fix: Don't crash background service worker when using import.meta.url (#402) 2024-02-03 14:57:34 -06:00
GitHub Actions 72e3c5baa2 chore(release): v0.16.1 2024-02-03 19:57:51 +00:00
Aaron Klinker 9318346ab3 fix: Don't require config to run wxt submit init 2024-02-03 13:52:15 -06:00
Aaron Klinker 1d6aceab0a Update changelog 2024-02-03 11:05:16 -06:00
Florian Metz c1b5a9d270 docs: Add premid extension to homepage (#399) 2024-02-02 22:46:46 -06:00
Aaron Klinker f0b2a12724 chore(templates): Upgrade to wxt ^0.16.0 2024-02-02 16:20:24 -06:00
Aaron Klinker c45677ed48 Update changelog 2024-02-02 16:19:33 -06:00
GitHub Actions 7a3f075e21 chore(release): v0.16.0 2024-02-02 16:55:11 +00:00
Aaron Klinker 1e12ce2a0d docs: Document how to opt into ESM 2024-02-02 10:48:00 -06:00
Aaron eca30296a7 feat!: ESM background support (#398)
BREAKING CHANGE: The build output has changed slightly. This isn't a huge deal, and no steps are required for users unless you're doing something weird with the output files after a build.

Before:

```
.output/
  <target>/
    chunks/
      popup-<hash>.js
    popup.html
```

After:

```
.output/
  <target>/
    popup.html
    popup.js
```

This applies for all HTML files, not just the popup.
2024-02-02 10:17:43 -06:00
dependabot[bot] 41fa320815 chore(deps-dev): bump @vitest/coverage-v8 from 1.0.1 to 1.2.2 (#397)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-02 07:07:50 -06:00
dependabot[bot] 873c24fa50 chore(deps-dev): bump p-map from 7.0.0 to 7.0.1 (#396)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-02 07:06:51 -06:00
dependabot[bot] d92db5ba8f chore(deps-dev): bump lint-staged from 15.2.0 to 15.2.1 (#395)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-02 07:06:31 -06:00
dependabot[bot] 1a6fe33f97 chore(deps): bump fast-glob from 3.3.1 to 3.3.2 (#394)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-02 07:05:42 -06:00
GitHub Actions 2db4ea77a9 chore(release): v0.15.4 2024-01-31 17:42:07 +00:00
Aaron ba5349ccaf fix(submit): Load .env.submit automatically when running wxt submit and wxt submit init (#391) 2024-01-31 11:38:11 -06:00
GitHub Actions addb689de9 chore(release): v0.15.3 2024-01-31 14:59:26 +00:00
Aaron 2af8d28b82 fix(dev): Reload <name>/index.html entrypoints properly on save (#390) 2024-01-31 08:50:23 -06:00
Aaron Klinker ddbc66813e Update changelog 2024-01-30 13:04:32 -06:00
24 changed files with 590 additions and 142 deletions
+88 -1
View File
@@ -1,5 +1,92 @@
# Changelog
## v0.16.2
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.1...v0.16.2)
### 🩹 Fixes
- Don't crash background service worker when using `import.meta.url` ([#402](https://github.com/wxt-dev/wxt/pull/402))
## v0.16.1
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.0...v0.16.1)
### 🩹 Fixes
- Don't require config to run `wxt submit init` ([9318346](https://github.com/wxt-dev/wxt/commit/9318346))
### 📖 Documentation
- Add premid extension to homepage ([#399](https://github.com/wxt-dev/wxt/pull/399))
### 🏡 Chore
- **templates:** Upgrade to wxt `^0.16.0` ([f0b2a12](https://github.com/wxt-dev/wxt/commit/f0b2a12))
### ❤️ Contributors
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
## v0.16.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.15.4...v0.16.0)
### 🚀 Enhancements
- ⚠️ ESM background support ([#398](https://github.com/wxt-dev/wxt/pull/398))
### 📖 Documentation
- Document how to opt into ESM ([1e12ce2](https://github.com/wxt-dev/wxt/commit/1e12ce2))
### 🏡 Chore
- **deps-dev:** Bump lint-staged from 15.2.0 to 15.2.1 ([#395](https://github.com/wxt-dev/wxt/pull/395))
- **deps-dev:** Bump p-map from 7.0.0 to 7.0.1 ([#396](https://github.com/wxt-dev/wxt/pull/396))
- **deps-dev:** Bump @vitest/coverage-v8 from 1.0.1 to 1.2.2 ([#397](https://github.com/wxt-dev/wxt/pull/397))
#### ⚠️ Breaking Changes
In [#398](https://github.com/wxt-dev/wxt/pull/398), HTML pages' JS entrypoints in the output directory have been moved. Unless you're doing some kind of post-build work referencing files, you don't have to make any changes.
- Before:
```
.output/
<target>/
chunks/
some-shared-chunk-<hash>.js
popup-<hash>.js
popup.html
```
- After:
```
.output/
<target>/
chunks/
some-shared-chunk-<hash>.js
popup.html
popup.js
```
This effects all HTML files, not just the Popup. The hash has been removed, and it's been moved to the root of the build target folder, not inside the `chunks/` directory. Moving files like this has not historically increased review times or triggered in-depth reviews when submitting updates to the stores.
## v0.15.4
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.15.3...v0.15.4)
### 🩹 Fixes
- **submit:** Load `.env.submit` automatically when running `wxt submit` and `wxt submit init` ([#391](https://github.com/wxt-dev/wxt/pull/391))
## v0.15.3
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.15.2...v0.15.3)
### 🩹 Fixes
- **dev:** Reload `<name>/index.html` entrypoints properly on save ([#390](https://github.com/wxt-dev/wxt/pull/390))
## v0.15.2
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.15.1...v0.15.2)
@@ -14,7 +101,7 @@
### ❤️ Contributors
- Nenad Novaković
- Nenad Novaković ([@dvlden](https://github.com/dvlden))
## v0.15.1
+35 -30
View File
@@ -1,37 +1,42 @@
import messages from 'public/_locales/en/messages.json';
export default defineBackground(() => {
console.log(browser.runtime.id);
logId();
console.log({
browser: import.meta.env.BROWSER,
chrome: import.meta.env.CHROME,
firefox: import.meta.env.FIREFOX,
manifestVersion: import.meta.env.MANIFEST_VERSION,
messages,
});
export default defineBackground({
// type: 'module',
// @ts-expect-error: should only accept entrypoints or public assets
browser.runtime.getURL('/');
browser.runtime.getURL('/background.js');
browser.runtime.getURL('/icon/128.png');
browser.runtime.getURL('/example.html#hash');
browser.runtime.getURL('/example.html?query=param');
// @ts-expect-error: should only allow hashes/query params on HTML files
browser.runtime.getURL('/icon-128.png?query=param');
main() {
console.log(browser.runtime.id);
logId();
console.log({
url: import.meta.url,
browser: import.meta.env.BROWSER,
chrome: import.meta.env.CHROME,
firefox: import.meta.env.FIREFOX,
manifestVersion: import.meta.env.MANIFEST_VERSION,
messages,
});
// @ts-expect-error: should only accept known message names
browser.i18n.getMessage('test');
browser.i18n.getMessage('prompt_for_name');
browser.i18n.getMessage('hello', 'Aaron');
browser.i18n.getMessage('bye', ['Aaron']);
browser.i18n.getMessage('@@extension_id');
// @ts-expect-error: should only accept entrypoints or public assets
browser.runtime.getURL('/');
browser.runtime.getURL('/background.js');
browser.runtime.getURL('/icon/128.png');
browser.runtime.getURL('/example.html#hash');
browser.runtime.getURL('/example.html?query=param');
// @ts-expect-error: should only allow hashes/query params on HTML files
browser.runtime.getURL('/icon-128.png?query=param');
console.log('WXT MODE:', {
MODE: import.meta.env.MODE,
DEV: import.meta.env.DEV,
PROD: import.meta.env.PROD,
});
// @ts-expect-error: should only accept known message names
browser.i18n.getMessage('test');
browser.i18n.getMessage('prompt_for_name');
browser.i18n.getMessage('hello', 'Aaron');
browser.i18n.getMessage('bye', ['Aaron']);
browser.i18n.getMessage('@@extension_id');
storage.setItem('session:startTime', Date.now());
console.log('WXT MODE:', {
MODE: import.meta.env.MODE,
DEV: import.meta.env.DEV,
PROD: import.meta.env.PROD,
});
storage.setItem('session:startTime', Date.now());
},
});
@@ -17,6 +17,7 @@ const chromeExtensionIds = [
'okifoaikfmpfcamplcfjkpdnhfodpkil', // Doozy: Ai Made Easy
'lknmjhcajhfbbglglccadlfdjbaiifig', // tl;dv - Record, Transcribe & ChatGPT for Google Meet
'youtube中文配音/oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
];
const { data } = useListExtensionDetails(chromeExtensionIds);
+1
View File
@@ -90,6 +90,7 @@ export default defineConfig({
{ text: 'Assets', link: '/guide/assets.md' },
{ text: 'Content Script UI', link: '/guide/content-script-ui.md' },
{ text: 'Multiple Browsers', link: '/guide/multiple-browsers.md' },
{ text: 'ES Modules', link: '/guide/esm.md' },
{ text: 'Auto-imports', link: '/guide/auto-imports.md' },
{ text: 'Vite', link: '/guide/vite.md' },
{ text: 'Remote Code', link: '/guide/remote-code.md' },
+38
View File
@@ -0,0 +1,38 @@
# ES Modules
Configure entrypoints to use ESM at runtime.
Currently, ESM entrypoints are opt-in, so you must configure each entrypoint with that in mind.
## HTML Pages <Badge type="warning" text="≥0.0.1" />
In general, you should always make HTML pages import ESM scripts, unless you need to support old browsers.
To make a script ESM, add `type="module"`:
<!-- prettier-ignore -->
```html
<script src="./main.ts"></script> <!-- [!code --] -->
<script src="./main.ts" type="module"></script> <!-- [!code ++] -->
```
## Background <Badge type="warning" text="≥0.16.0" />
In your background script, set `type: "module"`:
```ts
export default defineBackground({
type: 'module', // !code ++
main() {
// ...
},
});
```
:::warning
Only MV3 support ESM background scripts/service workers. When targetting MV2, the `type` option is ignored and the background is always bundled into a single file as IIFE.
:::
## Content Scripts
Coming soon. Follow [Content Script ESM Support #357](https://github.com/wxt-dev/wxt/issues/357) for updates.
+1
View File
@@ -145,5 +145,6 @@ If you're an experienced web extension developer and think the dev manifest look
You're ready to build your web extension!
- Learn how to [add entrypoints](./entrypoints) like the popup, options page, or content scripts
- Configure your entrypoints to [use ESM](./esm) at runtime
- [Configure WXT](./configuration) by creating a `wxt.config.ts` file
- Checkout [example projects](https://github.com/wxt-dev/wxt-examples) to see how to perfom common tasks with WXT
+165
View File
@@ -227,4 +227,169 @@ describe('Output Directory Structure', () => {
true,
);
});
it('should generate ESM background script when type=module', async () => {
const project = new TestProject();
project.addFile(
'utils/log.ts',
`export function logHello(name: string) {
console.log(\`Hello \${name}!\`);
}`,
);
project.addFile(
'entrypoints/background.ts',
`export default defineBackground({
type: "module",
main() {
logHello("background");
},
})`,
);
project.addFile(
'entrypoints/popup/index.html',
`<html>
<head>
<script type="module" src="./main.ts"></script>
</head>
</html>`,
);
project.addFile('entrypoints/popup/main.ts', `logHello('popup')`);
await project.build({
experimental: {
// Simplify the build output for comparison
includeBrowserPolyfill: false,
},
vite: () => ({
build: {
// Make output for snapshot readible
minify: false,
},
}),
});
expect(await project.serializeFile('.output/chrome-mv3/background.js'))
.toMatchInlineSnapshot(`
".output/chrome-mv3/background.js
----------------------------------------
import { l as logHello } from "./chunks/log-bezs0tt4.js";
function defineBackground(arg) {
if (typeof arg === "function")
return { main: arg };
return arg;
}
const definition = defineBackground({
type: "module",
main() {
logHello("background");
}
});
chrome;
function print(method, ...args) {
return;
}
var logger = {
debug: (...args) => print(console.debug, ...args),
log: (...args) => print(console.log, ...args),
warn: (...args) => print(console.warn, ...args),
error: (...args) => print(console.error, ...args)
};
try {
const res = definition.main();
if (res instanceof Promise) {
console.warn(
"The background's main() function return a promise, but it must be synchonous"
);
}
} catch (err) {
logger.error("The background crashed on startup!");
throw err;
}
"
`);
});
it('should generate IIFE background script when type=undefined', async () => {
const project = new TestProject();
project.addFile(
'utils/log.ts',
`export function logHello(name: string) {
console.log(\`Hello \${name}!\`);
}`,
);
project.addFile(
'entrypoints/background.ts',
`export default defineBackground({
main() {
logHello("background");
},
})`,
);
project.addFile(
'entrypoints/popup/index.html',
`<html>
<head>
<script type="module" src="./main.ts"></script>
</head>
</html>`,
);
project.addFile('entrypoints/popup/main.ts', `logHello('popup')`);
await project.build({
experimental: {
// Simplify the build output for comparison
includeBrowserPolyfill: false,
},
vite: () => ({
build: {
// Make output for snapshot readible
minify: false,
},
}),
});
expect(await project.serializeFile('.output/chrome-mv3/background.js'))
.toMatchInlineSnapshot(`
".output/chrome-mv3/background.js
----------------------------------------
(function() {
"use strict";
function defineBackground(arg) {
if (typeof arg === "function")
return { main: arg };
return arg;
}
function logHello(name) {
console.log(\`Hello \${name}!\`);
}
const definition = defineBackground({
main() {
logHello("background");
}
});
chrome;
function print(method, ...args) {
return;
}
var logger = {
debug: (...args) => print(console.debug, ...args),
log: (...args) => print(console.log, ...args),
warn: (...args) => print(console.warn, ...args),
error: (...args) => print(console.error, ...args)
};
try {
const res = definition.main();
if (res instanceof Promise) {
console.warn(
"The background's main() function return a promise, but it must be synchonous"
);
}
} catch (err) {
logger.error("The background crashed on startup!");
throw err;
}
})();
"
`);
});
});
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.15.2",
"version": "0.16.2",
"description": "Next gen framework for developing web extensions",
"engines": {
"node": ">=18",
@@ -132,7 +132,7 @@
"ora": "^7.0.1",
"picocolors": "^1.0.0",
"prompts": "^2.4.2",
"publish-browser-extension": "^2.0.0",
"publish-browser-extension": "^2.1.1",
"rollup-plugin-visualizer": "^5.9.2",
"unimport": "^3.4.0",
"vite": "^5.0.12",
+31 -43
View File
@@ -46,7 +46,7 @@ importers:
version: 0.19.8
fast-glob:
specifier: ^3.3.1
version: 3.3.1
version: 3.3.2
filesize:
specifier: ^10.0.8
version: 10.1.0
@@ -96,8 +96,8 @@ importers:
specifier: ^2.4.2
version: 2.4.2
publish-browser-extension:
specifier: ^2.0.0
version: 2.0.0
specifier: ^2.1.1
version: 2.1.1
rollup-plugin-visualizer:
specifier: ^5.9.2
version: 5.9.3
@@ -140,7 +140,7 @@ importers:
version: 2.4.9
'@vitest/coverage-v8':
specifier: ^1.0.1
version: 1.0.1(vitest@1.1.0)
version: 1.2.2(vitest@1.1.0)
execa:
specifier: ^8.0.1
version: 8.0.1
@@ -149,7 +149,7 @@ importers:
version: 12.10.3
lint-staged:
specifier: ^15.2.0
version: 15.2.0
version: 15.2.1
lodash.merge:
specifier: ^4.6.2
version: 4.6.2
@@ -158,7 +158,7 @@ importers:
version: 4.1.5
p-map:
specifier: ^7.0.0
version: 7.0.0
version: 7.0.1
prettier:
specifier: ^3.1.0
version: 3.1.1
@@ -399,20 +399,12 @@ packages:
js-tokens: 4.0.0
dev: false
/@babel/parser@7.23.5:
resolution: {integrity: sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.23.5
dev: true
/@babel/parser@7.23.6:
resolution: {integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.23.5
'@babel/types': 7.23.9
dev: true
/@babel/runtime@7.23.9:
@@ -422,8 +414,8 @@ packages:
regenerator-runtime: 0.14.0
dev: false
/@babel/types@7.23.5:
resolution: {integrity: sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==}
/@babel/types@7.23.9:
resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-string-parser': 7.23.4
@@ -1187,8 +1179,8 @@ packages:
vue: 3.4.3(typescript@5.3.2)
dev: true
/@vitest/coverage-v8@1.0.1(vitest@1.1.0):
resolution: {integrity: sha512-Z4a7ig4VjUCT/P+LRB3IZrBRXb9xWRUM8rSBH9cKgfrU1Oe01/K2WJKtGshOnQwXZoSfQtwCGpbnHmB/qJwjcw==}
/@vitest/coverage-v8@1.2.2(vitest@1.1.0):
resolution: {integrity: sha512-IHyKnDz18SFclIEEAHb9Y4Uxx0sPKC2VO1kdDCs1BF6Ip4S8rQprs971zIsooLUn7Afs71GRxWMWpkCGZpRMhw==}
peerDependencies:
vitest: ^1.0.0
dependencies:
@@ -1200,7 +1192,7 @@ packages:
istanbul-lib-source-maps: 4.0.1
istanbul-reports: 3.1.6
magic-string: 0.30.5
magicast: 0.3.2
magicast: 0.3.3
picocolors: 1.0.0
std-env: 3.6.0
test-exclude: 6.0.0
@@ -1279,7 +1271,7 @@ packages:
/@vue/compiler-sfc@3.3.10:
resolution: {integrity: sha512-xpcTe7Rw7QefOTRFFTlcfzozccvjM40dT45JtrE3onGm/jBLZ0JhpKu3jkV7rbDFLeeagR/5RlJ2Y9SvyS0lAg==}
dependencies:
'@babel/parser': 7.23.5
'@babel/parser': 7.23.6
'@vue/compiler-core': 3.3.10
'@vue/compiler-dom': 3.3.10
'@vue/compiler-ssr': 3.3.10
@@ -2243,10 +2235,6 @@ packages:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: false
/emoji-regex@10.2.1:
resolution: {integrity: sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==}
dev: false
/emoji-regex@10.3.0:
resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==}
@@ -2481,8 +2469,8 @@ packages:
- supports-color
dev: false
/fast-glob@3.3.1:
resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
/fast-glob@3.3.2:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'}
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -2759,7 +2747,7 @@ packages:
dependencies:
array-union: 2.1.0
dir-glob: 3.0.1
fast-glob: 3.3.1
fast-glob: 3.3.2
ignore: 5.2.4
merge2: 1.4.1
slash: 3.0.0
@@ -3403,8 +3391,8 @@ packages:
uhyphen: 0.2.0
dev: false
/lint-staged@15.2.0:
resolution: {integrity: sha512-TFZzUEV00f+2YLaVPWBWGAMq7So6yQx+GG8YRMDeOEIf95Zn5RyiLMsEiX4KTNl9vq/w+NqRJkLA1kPIo15ufQ==}
/lint-staged@15.2.1:
resolution: {integrity: sha512-dhwAPnM85VdshybV9FWI/9ghTvMLoQLEXgVMx+ua2DN7mdfzd/tRfoU2yhMcBac0RHkofoxdnnJUokr8s4zKmQ==}
engines: {node: '>=18.12.0'}
hasBin: true
dependencies:
@@ -3413,7 +3401,7 @@ packages:
debug: 4.3.4
execa: 8.0.1
lilconfig: 3.0.0
listr2: 8.0.0
listr2: 8.0.1
micromatch: 4.0.5
pidtree: 0.6.0
string-argv: 0.3.2
@@ -3422,8 +3410,8 @@ packages:
- supports-color
dev: true
/listr2@8.0.0:
resolution: {integrity: sha512-u8cusxAcyqAiQ2RhYvV7kRKNLgUvtObIbhOX2NCXqvp1UU32xIg5CT22ykS2TPKJXZWJwtK3IKLiqAGlGNE+Zg==}
/listr2@8.0.1:
resolution: {integrity: sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==}
engines: {node: '>=18.0.0'}
dependencies:
cli-truncate: 4.0.0
@@ -3543,11 +3531,11 @@ packages:
dependencies:
'@jridgewell/sourcemap-codec': 1.4.15
/magicast@0.3.2:
resolution: {integrity: sha512-Fjwkl6a0syt9TFN0JSYpOybxiMCkYNEeOTnOTNRbjphirLakznZXAqrXgj/7GG3D1dvETONNwrBfinvAbpunDg==}
/magicast@0.3.3:
resolution: {integrity: sha512-ZbrP1Qxnpoes8sz47AM0z08U+jW6TyRgZzcWy3Ma3vDhJttwMwAFDMMQFobwdBxByBD46JYmxRzeF7w2+wJEuw==}
dependencies:
'@babel/parser': 7.23.5
'@babel/types': 7.23.5
'@babel/parser': 7.23.6
'@babel/types': 7.23.9
source-map-js: 1.0.2
dev: true
@@ -3974,8 +3962,8 @@ packages:
dependencies:
yocto-queue: 1.0.0
/p-map@7.0.0:
resolution: {integrity: sha512-EZl03dLKv3RypkrjlevZoNwQMSy4bAblWcR18zhonktnN4fUs3asFQKSe0awn982omGxamvbejqQKQYDJYHCEg==}
/p-map@7.0.1:
resolution: {integrity: sha512-2wnaR0XL/FDOj+TgpDuRb2KTjLnu3Fma6b1ZUwGY7LcqenMcvP/YFpjpbPKY6WVGsbuJZRuoUz8iPrt8ORnAFw==}
engines: {node: '>=18'}
dev: true
@@ -4184,8 +4172,8 @@ packages:
sade: 1.8.1
dev: true
/publish-browser-extension@2.0.0:
resolution: {integrity: sha512-HzNfnsx2/yN1n288btH6xfmXzYA5e9FpjteJpR68qlevqKMQqbCw9AJOK2WdFKVBy1kuSil8XGuhpQWv3NcrnA==}
/publish-browser-extension@2.1.1:
resolution: {integrity: sha512-WqCnhHXbmiYn4vIbgcyKWVHiOvCoPjjpt3X+hfnU8rlOozU43TURpHUWZlQ0W49LhvJvBRsBfGRQxtRMGhqsxw==}
engines: {node: '18', pnpm: '8'}
hasBin: true
dependencies:
@@ -4739,7 +4727,7 @@ packages:
engines: {node: '>=16'}
dependencies:
eastasianwidth: 0.2.0
emoji-regex: 10.2.1
emoji-regex: 10.3.0
strip-ansi: 7.1.0
dev: false
@@ -5115,7 +5103,7 @@ packages:
dependencies:
'@rollup/pluginutils': 5.0.5
escape-string-regexp: 5.0.0
fast-glob: 3.3.1
fast-glob: 3.3.2
local-pkg: 0.4.3
magic-string: 0.30.5
mlly: 1.4.2
+30 -20
View File
@@ -63,6 +63,7 @@ export async function createViteBuilder(
wxtPlugins.noopBackground(),
wxtPlugins.globals(wxtConfig),
wxtPlugins.excludeBrowserPolyfill(wxtConfig),
wxtPlugins.defineImportMeta(),
);
if (wxtConfig.analysis.enabled) {
config.plugins.push(wxtPlugins.bundleAnalysis());
@@ -75,23 +76,7 @@ export async function createViteBuilder(
* 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 => {
let virtualEntrypointType: VirtualEntrypointType | undefined;
switch (entrypoint.type) {
case 'background':
case 'unlisted-script':
virtualEntrypointType = entrypoint.type;
break;
case 'content-script':
virtualEntrypointType =
entrypoint.options.world === 'MAIN'
? 'content-script-main-world'
: 'content-script-isolated-world';
break;
}
const entry = virtualEntrypointType
? `virtual:wxt-${virtualEntrypointType}?${entrypoint.inputPath}`
: entrypoint.inputPath;
const entry = getRollupEntry(entrypoint);
const plugins: NonNullable<vite.UserConfig['plugins']> = [
wxtPlugins.entrypointGroupGlobals(entrypoint),
];
@@ -157,14 +142,16 @@ export async function createViteBuilder(
build: {
rollupOptions: {
input: entrypoints.reduce<Record<string, string>>((input, entry) => {
input[entry.name] = entry.inputPath;
input[entry.name] = getRollupEntry(entry);
return input;
}, {}),
output: {
// Include a hash to prevent conflicts
chunkFileNames: 'chunks/[name]-[hash].js',
// Include a hash to prevent conflicts
entryFileNames: 'chunks/[name]-[hash].js',
// Place JS entrypoints in main directory without a hash. (popup.html & popup.js are
// next to each other). The unique entrypoint name requirement prevents conflicts with
// scripts of the same name
entryFileNames: '[name].js',
// We can't control the "name", so we need a hash to prevent conflicts
assetFileNames: 'assets/[name]-[hash].[ext]',
},
@@ -263,3 +250,26 @@ function getBuildOutputChunks(
if (Array.isArray(result)) return result.flatMap(({ output }) => output);
return result.output;
}
/**
* 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;
switch (entrypoint.type) {
case 'background':
case 'unlisted-script':
virtualEntrypointType = entrypoint.type;
break;
case 'content-script':
virtualEntrypointType =
entrypoint.options.world === 'MAIN'
? 'content-script-main-world'
: 'content-script-isolated-world';
break;
}
return virtualEntrypointType
? `virtual:wxt-${virtualEntrypointType}?${entrypoint.inputPath}`
: entrypoint.inputPath;
}
@@ -0,0 +1,19 @@
/**
* 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
*/
export function defineImportMeta() {
return {
name: 'wxt:define',
config() {
return {
define: {
// This works for all extension contexts, including background service worker
'import.meta.url': 'self.location.href',
},
};
},
};
}
+1
View File
@@ -12,3 +12,4 @@ export * from './globals';
export * from './webextensionPolyfillMock';
export * from './excludeBrowserPolyfill';
export * from './entrypointGroupGlobals';
export * from './defineImportMeta';
+30 -14
View File
@@ -8,7 +8,6 @@ import {
} from '~/types';
import {
getEntrypointBundlePath,
getEntrypointOutputFile,
resolvePerBrowserOption,
} from '~/core/utils/entrypoints';
import {
@@ -197,15 +196,6 @@ function createFileReloader(options: {
.join(', ')}`,
);
const rebuiltNames = changes.rebuildGroups
.flat()
.map((entry) => {
return pc.cyan(
relative(config.outDir, getEntrypointOutputFile(entry, '')),
);
})
.join(pc.dim(', '));
// Rebuild entrypoints on change
const allEntrypoints = await findEntrypoints(config);
const { output: newOutput } = await rebuild(
@@ -221,15 +211,24 @@ function createFileReloader(options: {
switch (changes.type) {
case 'extension-reload':
server.reloadExtension();
consola.success(`Reloaded extension`);
break;
case 'html-reload':
reloadHtmlPages(changes.rebuildGroups, server, config);
const { reloadedNames } = reloadHtmlPages(
changes.rebuildGroups,
server,
config,
);
consola.success(`Reloaded: ${getFilenameList(reloadedNames)}`);
break;
case 'content-script-reload':
reloadContentScripts(changes.changedSteps, config, server);
const rebuiltNames = changes.rebuildGroups
.flat()
.map((entry) => entry.name);
consola.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
break;
}
consola.success(`Reloaded: ${rebuiltNames}`);
});
};
}
@@ -279,9 +278,26 @@ function reloadHtmlPages(
groups: EntrypointGroup[],
server: WxtDevServer,
config: InternalConfig,
) {
groups.flat().forEach((entry) => {
): { reloadedNames: string[] } {
// groups might contain other files like background/content scripts, and we only care about the HTMl pages
const htmlEntries = groups
.flat()
.filter((entry) => entry.inputPath.endsWith('.html'));
htmlEntries.forEach((entry) => {
const path = getEntrypointBundlePath(entry, config.outDir, '.html');
server.reloadPage(path);
});
return {
reloadedNames: htmlEntries.map((entry) => entry.name),
};
}
function getFilenameList(names: string[]): string {
return names
.map((name) => {
return pc.cyan(name);
})
.join(pc.dim(', '));
}
@@ -167,9 +167,9 @@ describe('Detect Dev Changes', () => {
});
describe('HTML Pages', () => {
it('should rebuild then reload only the effected pages', async () => {
it('should detect changes to entrypoints/<name>.html files', async () => {
const config = fakeInternalConfig();
const changedPath = '/root/page1/index.html';
const changedPath = '/root/page1.html';
const htmlPage1 = fakePopupEntrypoint({
inputPath: changedPath,
});
@@ -184,16 +184,66 @@ describe('Detect Dev Changes', () => {
const step1: BuildStepOutput = {
entrypoints: [htmlPage1, htmlPage2],
chunks: [
fakeOutputChunk({
moduleIds: [fakeFile(), changedPath],
fakeOutputAsset({
fileName: 'page1.html',
}),
],
};
const step2: BuildStepOutput = {
entrypoints: [htmlPage3],
chunks: [
fakeOutputChunk({
moduleIds: [fakeFile(), fakeFile(), fakeFile()],
fakeOutputAsset({
fileName: 'page2.html',
}),
],
};
const currentOutput: BuildOutput = {
manifest: fakeManifest(),
publicAssets: [],
steps: [step1, step2],
};
const expected: DevModeChange = {
type: 'html-reload',
cachedOutput: {
...currentOutput,
steps: [step2],
},
rebuildGroups: [[htmlPage1, htmlPage2]],
};
const actual = detectDevChanges(config, [changedPath], currentOutput);
expect(actual).toEqual(expected);
});
it('should detect changes to entrypoints/<name>/index.html files', async () => {
const config = fakeInternalConfig();
const changedPath = '/root/page1/index.html';
const htmlPage1 = fakePopupEntrypoint({
inputPath: changedPath,
});
const htmlPage2 = fakeOptionsEntrypoint({
inputPath: '/root/page2/index.html',
});
const htmlPage3 = fakeGenericEntrypoint({
type: 'sandbox',
inputPath: '/root/page3/index.html',
});
const step1: BuildStepOutput = {
entrypoints: [htmlPage1, htmlPage2],
chunks: [
fakeOutputAsset({
fileName: 'page1.html',
}),
],
};
const step2: BuildStepOutput = {
entrypoints: [htmlPage3],
chunks: [
fakeOutputAsset({
fileName: 'page2.html',
}),
],
};
@@ -27,6 +27,7 @@ const readFileMock = vi.mocked(
describe('findEntrypoints', () => {
const config = fakeInternalConfig({
manifestVersion: 3,
root: '/',
entrypointsDir: resolve('/src/entrypoints'),
outDir: resolve('.output'),
@@ -254,6 +255,32 @@ describe('findEntrypoints', () => {
},
);
it('should remove type=module from MV2 background scripts', async () => {
const config = fakeInternalConfig({ manifestVersion: 2 });
const options: BackgroundEntrypoint['options'] = {
type: 'module',
};
globMock.mockResolvedValueOnce(['background.ts']);
importEntrypointFileMock.mockResolvedValue(options);
const entrypoints = await findEntrypoints(config);
expect(entrypoints[0].options).toEqual({});
});
it('should allow type=module for MV3 background service workers', async () => {
const config = fakeInternalConfig({ manifestVersion: 3 });
const options: BackgroundEntrypoint['options'] = {
type: 'module',
};
globMock.mockResolvedValueOnce(['background.ts']);
importEntrypointFileMock.mockResolvedValue(options);
const entrypoints = await findEntrypoints(config);
expect(entrypoints[0].options).toEqual(options);
});
it("should include a virtual background script so dev reloading works when there isn't a background entrypoint defined by the user", async () => {
globMock.mockResolvedValueOnce(['popup.html']);
@@ -1,6 +1,11 @@
import { describe, expect, it } from 'vitest';
import { Entrypoint } from '~/types';
import { groupEntrypoints } from '../group-entrypoints';
import {
fakeBackgroundEntrypoint,
fakeGenericEntrypoint,
fakePopupEntrypoint,
} from '../../testing/fake-objects';
const background: Entrypoint = {
type: 'background',
@@ -141,4 +146,29 @@ describe('groupEntrypoints', () => {
expect(actual).toEqual(expected);
});
it('should group ESM compatible scripts with extension pages', () => {
const background = fakeBackgroundEntrypoint({
options: {
type: 'module',
},
});
const popup = fakePopupEntrypoint();
const sandbox = fakeGenericEntrypoint({
inputPath: '/entrypoints/sandbox.html',
name: 'sandbox',
type: 'sandbox',
});
const actual = groupEntrypoints([background, popup, sandbox]);
expect(actual).toEqual([[background, popup], [sandbox]]);
});
it.todo(
'should group ESM compatible sandbox scripts with sandbox pages',
() => {
// Main world content scripts
},
);
});
@@ -123,11 +123,12 @@ function findEffectedSteps(
const changedPath = normalizePath(changedFile);
const isChunkEffected = (chunk: OutputFile): boolean =>
// If it's an HTML file with the same path, is is effected because HTML files need to be pre-rendered
// fileName is normalized, relative bundle path
(chunk.type === 'asset' && changedPath.endsWith(chunk.fileName)) ||
// If it's an HTML file with the same path, is is effected because HTML files need to be re-rendered
// - fileName is normalized, relative bundle path, "<entrypoint-name>.html"
(chunk.type === 'asset' &&
changedPath.replace('/index.html', '.html').endsWith(chunk.fileName)) ||
// If it's a chunk that depends on the changed file, it is effected
// moduleIds are absolute, normalized paths
// - moduleIds are absolute, normalized paths
(chunk.type === 'chunk' && chunk.moduleIds.includes(changedPath));
for (const step of currentOutput.steps) {
@@ -392,6 +392,11 @@ async function getBackgroundEntrypoint(
const { main: _, ...moduleOptions } = defaultExport;
options = moduleOptions;
}
if (config.manifestVersion !== 3) {
delete options.type;
}
return {
type: 'background',
name,
+20 -17
View File
@@ -11,8 +11,11 @@ export function groupEntrypoints(entrypoints: Entrypoint[]): EntrypointGroup[] {
const groups: EntrypointGroup[] = [];
for (const entry of entrypoints) {
const group = ENTRY_TYPE_TO_GROUP_MAP[entry.type];
if (group === 'no-group') {
let group = ENTRY_TYPE_TO_GROUP_MAP[entry.type];
if (entry.type === 'background' && entry.options.type === 'module') {
group = 'esm';
}
if (group === 'individual') {
groups.push(entry);
} else {
let groupIndex = groupIndexMap[group];
@@ -28,22 +31,22 @@ export function groupEntrypoints(entrypoints: Entrypoint[]): EntrypointGroup[] {
}
const ENTRY_TYPE_TO_GROUP_MAP: Record<Entrypoint['type'], Group> = {
sandbox: 'sandbox-page',
sandbox: 'sandboxed-esm',
popup: 'extension-page',
newtab: 'extension-page',
history: 'extension-page',
options: 'extension-page',
devtools: 'extension-page',
bookmarks: 'extension-page',
sidepanel: 'extension-page',
'unlisted-page': 'extension-page',
popup: 'esm',
newtab: 'esm',
history: 'esm',
options: 'esm',
devtools: 'esm',
bookmarks: 'esm',
sidepanel: 'esm',
'unlisted-page': 'esm',
background: 'no-group',
'content-script': 'no-group',
'unlisted-script': 'no-group',
'unlisted-style': 'no-group',
'content-script-style': 'no-group',
background: 'individual',
'content-script': 'individual',
'unlisted-script': 'individual',
'unlisted-style': 'individual',
'content-script-style': 'individual',
};
type Group = 'extension-page' | 'sandbox-page' | 'no-group';
type Group = 'esm' | 'sandboxed-esm' | 'individual';
+1 -1
View File
@@ -23,6 +23,6 @@
"@types/react-dom": "^18.2.18",
"@vitejs/plugin-react": "^4.2.1",
"typescript": "^5.3.3",
"wxt": "^0.15.0"
"wxt": "^0.16.0"
}
}
+1 -1
View File
@@ -20,6 +20,6 @@
"devDependencies": {
"typescript": "^5.3.3",
"vite-plugin-solid": "^2.8.0",
"wxt": "^0.15.0"
"wxt": "^0.16.0"
}
}
+1 -1
View File
@@ -20,6 +20,6 @@
"svelte-check": "^3.6.2",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"wxt": "^0.15.0"
"wxt": "^0.16.0"
}
}
+1 -1
View File
@@ -16,6 +16,6 @@
},
"devDependencies": {
"typescript": "^5.3.3",
"wxt": "^0.15.0"
"wxt": "^0.16.0"
}
}
+1 -1
View File
@@ -21,6 +21,6 @@
"@vitejs/plugin-vue": "^5.0.1",
"typescript": "^5.3.3",
"vue-tsc": "^1.8.27",
"wxt": "^0.15.0"
"wxt": "^0.16.0"
}
}