Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f6b6b8857d | |||
| 54eb118f8c | |||
| 75eb3de89e | |||
| e14f81d97a | |||
| cd714ea196 | |||
| 684197dbe3 | |||
| c51f0e055f | |||
| 7f3a1e8d91 | |||
| 37986bf230 | |||
| 13a713cc28 | |||
| a78aada96e | |||
| 3307fc42a0 | |||
| 8be53efd24 | |||
| f02abc5227 | |||
| 2dd7a99937 | |||
| a9af4d3b67 |
Binary file not shown.
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 150 KiB |
+36
-1
@@ -1,5 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## v0.0.2
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.0.1...v0.0.2)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Reload extension when source code is changed ([#17](https://github.com/aklinker1/wxt/pull/17))
|
||||
- Setup background script web socket/reload ([#22](https://github.com/aklinker1/wxt/pull/22))
|
||||
- Reload HTML files individually ([#23](https://github.com/aklinker1/wxt/pull/23))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Output chunks to a chunks directory ([2dd7a99](https://github.com/aklinker1/wxt/commit/2dd7a99))
|
||||
- Remove hash from content script css outputs ([#20](https://github.com/aklinker1/wxt/pull/20))
|
||||
- Overwrite files with the same name when renaming entrypoints in dev mode ([37986bf](https://github.com/aklinker1/wxt/commit/37986bf))
|
||||
- Separate template builds to prevent sharing chunks ([7f3a1e8](https://github.com/aklinker1/wxt/commit/7f3a1e8))
|
||||
- Show Vite warnings and errors ([c51f0e0](https://github.com/aklinker1/wxt/commit/c51f0e0))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Add milestone progress badge to README ([684197d](https://github.com/aklinker1/wxt/commit/684197d))
|
||||
- Fix milestone link in README ([e14f81d](https://github.com/aklinker1/wxt/commit/e14f81d))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Update changelog ([a9af4d3](https://github.com/aklinker1/wxt/commit/a9af4d3))
|
||||
- Refactor build output type ([#19](https://github.com/aklinker1/wxt/pull/19))
|
||||
- Refactor build outputs to support transpiled templates ([a78aada](https://github.com/aklinker1/wxt/commit/a78aada))
|
||||
- Rename `templates` to `virtual-modules` ([#24](https://github.com/aklinker1/wxt/pull/24))
|
||||
- Update cli screenshot ([54eb118](https://github.com/aklinker1/wxt/commit/54eb118))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
- Aaron
|
||||
|
||||
## v0.0.1
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.0.0...v0.0.1)
|
||||
@@ -73,4 +109,3 @@
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
- Aaron
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> [](https://github.com/aklinker1/wxt/milestone/1)
|
||||
>
|
||||
> WXT is not ready for use yet. Production builds work, but dev mode is incomplete.
|
||||
>
|
||||
> See the [Initial Release Candidate](https://github.com/aklinker1/wxt/milestone/1) milestone for more details.
|
||||
|
||||
@@ -4,7 +4,7 @@ export default defineContentScript({
|
||||
matches: ['*://*/*'],
|
||||
async main() {
|
||||
console.log(browser.runtime.id);
|
||||
|
||||
logId();
|
||||
mountContentScriptUi();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -191,21 +191,18 @@ describe('Manifest Content', () => {
|
||||
expect(manifest.content_scripts).toContainEqual({
|
||||
matches: ['*://duckduckgo.com/*'],
|
||||
run_at: 'document_end',
|
||||
css: [expect.stringContaining('assets/four-')],
|
||||
css: ['assets/four.css'],
|
||||
js: ['content-scripts/four.js'],
|
||||
});
|
||||
expect(manifest.content_scripts).toContainEqual({
|
||||
matches: ['*://google.com/*'],
|
||||
run_at: 'document_end',
|
||||
css: [
|
||||
expect.stringContaining('assets/three-'),
|
||||
expect.stringContaining('assets/two-'),
|
||||
],
|
||||
css: ['assets/three.css', 'assets/two.css'],
|
||||
js: ['content-scripts/three.js', 'content-scripts/two.js'],
|
||||
});
|
||||
expect(manifest.content_scripts).toContainEqual({
|
||||
matches: ['*://google.com/*'],
|
||||
css: [expect.stringContaining('assets/one-')],
|
||||
css: ['assets/one.css'],
|
||||
js: ['content-scripts/one.js'],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,33 +44,29 @@ describe('Output Directory Structure', () => {
|
||||
await project.build();
|
||||
|
||||
expect(await project.serializeOutput()).toMatchInlineSnapshot(`
|
||||
".output/chrome-mv3/assets/one-dedc7a05.css
|
||||
".output/chrome-mv3/assets/one.css
|
||||
----------------------------------------
|
||||
body{color:#00f}
|
||||
|
||||
================================================================================
|
||||
.output/chrome-mv3/assets/two-74d94aed.css
|
||||
.output/chrome-mv3/assets/two.css
|
||||
----------------------------------------
|
||||
body{color:red}
|
||||
|
||||
================================================================================
|
||||
.output/chrome-mv3/content-scripts/one.js
|
||||
----------------------------------------
|
||||
(function(){\\"use strict\\";function c(n){return n}const e=\\"\\",t={matches:[\\"*://*/*\\"],main:()=>{}};(async()=>{try{await t.main()}catch(n){console.error(\`The content script crashed on startup!
|
||||
|
||||
\`,n)}})()})();
|
||||
(function(){\\"use strict\\";function i(n){return n}const s=\\"\\",o={matches:[\\"*://*/*\\"],main:()=>{}};function t(n,...e){if(typeof e[0]==\\"string\\"){const c=e.shift();n(\`[wxt] \${c}\`,...e)}else n(\\"[wxt]\\",...e)}var r={debug:(...n)=>t(console.debug,...n),log:(...n)=>t(console.log,...n),warn:(...n)=>t(console.warn,...n),error:(...n)=>t(console.error,...n)};(async()=>{try{await o.main()}catch(n){r.error(\\"The content script crashed on startup!\\",n)}})()})();
|
||||
|
||||
================================================================================
|
||||
.output/chrome-mv3/content-scripts/two.js
|
||||
----------------------------------------
|
||||
(function(){\\"use strict\\";function c(n){return n}const e=\\"\\",t={matches:[\\"*://*/*\\"],main:()=>{}};(async()=>{try{await t.main()}catch(n){console.error(\`The content script crashed on startup!
|
||||
|
||||
\`,n)}})()})();
|
||||
(function(){\\"use strict\\";function i(n){return n}const s=\\"\\",o={matches:[\\"*://*/*\\"],main:()=>{}};function t(n,...e){if(typeof e[0]==\\"string\\"){const c=e.shift();n(\`[wxt] \${c}\`,...e)}else n(\\"[wxt]\\",...e)}var r={debug:(...n)=>t(console.debug,...n),log:(...n)=>t(console.log,...n),warn:(...n)=>t(console.warn,...n),error:(...n)=>t(console.error,...n)};(async()=>{try{await o.main()}catch(n){r.error(\\"The content script crashed on startup!\\",n)}})()})();
|
||||
|
||||
================================================================================
|
||||
.output/chrome-mv3/manifest.json
|
||||
----------------------------------------
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"content_scripts\\":[{\\"matches\\":[\\"*://*/*\\"],\\"css\\":[\\"assets/one-dedc7a05.css\\",\\"assets/two-74d94aed.css\\"],\\"js\\":[\\"content-scripts/one.js\\",\\"content-scripts/two.js\\"]}]}"
|
||||
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"content_scripts\\":[{\\"matches\\":[\\"*://*/*\\"],\\"css\\":[\\"assets/one.css\\",\\"assets/two.css\\"],\\"js\\":[\\"content-scripts/one.js\\",\\"content-scripts/two.js\\"]}]}"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
+12
-11
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.2",
|
||||
"description": "Next gen framework for developing web extensions",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -22,10 +22,9 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist",
|
||||
"templates"
|
||||
"dist"
|
||||
],
|
||||
"bin": "dist/cli/index.cjs",
|
||||
"bin": "dist/cli.cjs",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -36,17 +35,14 @@
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./client": {
|
||||
"require": "./dist/client/index.cjs",
|
||||
"import": "./dist/client/index.js",
|
||||
"types": "./dist/client/index.d.ts"
|
||||
"require": "./dist/client.cjs",
|
||||
"import": "./dist/client.js",
|
||||
"types": "./dist/client.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"wxt": "tsx src/cli/index.ts",
|
||||
"build": "run-s -s build:*",
|
||||
"build:js": "tsup src/index.ts --sourcemap --dts --format esm,cjs",
|
||||
"build:cli": "tsup src/cli/index.ts -d dist/cli --sourcemap",
|
||||
"build:client": "tsup src/client/index.ts -d dist/client --sourcemap --dts --format esm,cjs",
|
||||
"build": "tsx scripts/build.ts",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --write .",
|
||||
"compile": "tsc --noEmit",
|
||||
@@ -56,6 +52,7 @@
|
||||
"prepublish": "pnpm -s build"
|
||||
},
|
||||
"dependencies": {
|
||||
"async-mutex": "^0.4.0",
|
||||
"c12": "^1.4.2",
|
||||
"cac": "^6.7.14",
|
||||
"consola": "^3.1.0",
|
||||
@@ -73,12 +70,16 @@
|
||||
"webextension-polyfill": "^0.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@types/lodash.merge": "^4.6.7",
|
||||
"@types/node": "^20.3.1",
|
||||
"@types/picomatch": "^2.3.0",
|
||||
"@types/webextension-polyfill": "^0.10.0",
|
||||
"@vitest/coverage-v8": "^0.32.2",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"ora": "^6.3.1",
|
||||
"prettier": "^2.8.8",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"simple-git-hooks": "^2.8.1",
|
||||
|
||||
Generated
+157
-13
@@ -3,11 +3,14 @@ lockfileVersion: 5.4
|
||||
importers:
|
||||
.:
|
||||
specifiers:
|
||||
'@faker-js/faker': ^8.0.2
|
||||
'@types/fs-extra': ^11.0.1
|
||||
'@types/lodash.merge': ^4.6.7
|
||||
'@types/node': ^20.3.1
|
||||
'@types/picomatch': ^2.3.0
|
||||
'@types/webextension-polyfill': ^0.10.0
|
||||
'@vitest/coverage-v8': ^0.32.2
|
||||
async-mutex: ^0.4.0
|
||||
c12: ^1.4.2
|
||||
cac: ^6.7.14
|
||||
consola: ^3.1.0
|
||||
@@ -17,7 +20,9 @@ importers:
|
||||
jiti: ^1.18.2
|
||||
json5: ^2.2.3
|
||||
linkedom: ^0.14.26
|
||||
lodash.merge: ^4.6.2
|
||||
npm-run-all: ^4.1.5
|
||||
ora: ^6.3.1
|
||||
picocolors: ^1.0.0
|
||||
picomatch: ^2.3.1
|
||||
prettier: ^2.8.8
|
||||
@@ -32,6 +37,7 @@ importers:
|
||||
web-ext: ^7.6.2
|
||||
webextension-polyfill: ^0.10.0
|
||||
dependencies:
|
||||
async-mutex: 0.4.0
|
||||
c12: 1.4.2
|
||||
cac: 6.7.14
|
||||
consola: 3.1.0
|
||||
@@ -48,12 +54,16 @@ importers:
|
||||
web-ext: 7.6.2
|
||||
webextension-polyfill: 0.10.0
|
||||
devDependencies:
|
||||
'@faker-js/faker': 8.0.2
|
||||
'@types/fs-extra': 11.0.1
|
||||
'@types/lodash.merge': 4.6.7
|
||||
'@types/node': 20.3.1
|
||||
'@types/picomatch': 2.3.0
|
||||
'@types/webextension-polyfill': 0.10.0
|
||||
'@vitest/coverage-v8': 0.32.2_vitest@0.32.2
|
||||
lodash.merge: 4.6.2
|
||||
npm-run-all: 4.1.5
|
||||
ora: 6.3.1
|
||||
prettier: 2.8.8
|
||||
pretty-quick: 3.1.3_prettier@2.8.8
|
||||
simple-git-hooks: 2.8.1
|
||||
@@ -752,6 +762,14 @@ packages:
|
||||
engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 }
|
||||
dev: false
|
||||
|
||||
/@faker-js/faker/8.0.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Uo3pGspElQW91PCvKSIAXoEgAUlRnH29sX2/p89kg7sP1m2PzCufHINd0FhTXQf6DYGiUlVncdSPa2F9wxed2A==,
|
||||
}
|
||||
engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13' }
|
||||
dev: true
|
||||
|
||||
/@fluent/syntax/0.19.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -1003,6 +1021,22 @@ packages:
|
||||
'@types/node': 20.3.1
|
||||
dev: true
|
||||
|
||||
/@types/lodash.merge/4.6.7:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-OwxUJ9E50gw3LnAefSHJPHaBLGEKmQBQ7CZe/xflHkyy/wH2zVyEIAKReHvVrrn7zKdF58p16We9kMfh7v0RRQ==,
|
||||
}
|
||||
dependencies:
|
||||
'@types/lodash': 4.14.195
|
||||
dev: true
|
||||
|
||||
/@types/lodash/4.14.195:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==,
|
||||
}
|
||||
dev: true
|
||||
|
||||
/@types/minimatch/3.0.5:
|
||||
resolution:
|
||||
{
|
||||
@@ -1303,7 +1337,6 @@ packages:
|
||||
integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==,
|
||||
}
|
||||
engines: { node: '>=12' }
|
||||
dev: false
|
||||
|
||||
/ansi-styles/3.2.1:
|
||||
resolution:
|
||||
@@ -1436,6 +1469,15 @@ packages:
|
||||
}
|
||||
dev: true
|
||||
|
||||
/async-mutex/0.4.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==,
|
||||
}
|
||||
dependencies:
|
||||
tslib: 2.6.0
|
||||
dev: false
|
||||
|
||||
/async/3.2.4:
|
||||
resolution:
|
||||
{
|
||||
@@ -1499,7 +1541,6 @@ packages:
|
||||
{
|
||||
integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==,
|
||||
}
|
||||
dev: false
|
||||
|
||||
/bcrypt-pbkdf/1.0.2:
|
||||
resolution:
|
||||
@@ -1517,6 +1558,17 @@ packages:
|
||||
}
|
||||
engines: { node: '>=8' }
|
||||
|
||||
/bl/5.1.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==,
|
||||
}
|
||||
dependencies:
|
||||
buffer: 6.0.3
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
dev: true
|
||||
|
||||
/bluebird/3.7.2:
|
||||
resolution:
|
||||
{
|
||||
@@ -1610,7 +1662,6 @@ packages:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
dev: false
|
||||
|
||||
/bundle-require/4.0.1_esbuild@0.18.5:
|
||||
resolution:
|
||||
@@ -1779,7 +1830,6 @@ packages:
|
||||
integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==,
|
||||
}
|
||||
engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 }
|
||||
dev: false
|
||||
|
||||
/check-error/1.0.2:
|
||||
resolution:
|
||||
@@ -1875,6 +1925,24 @@ packages:
|
||||
engines: { node: '>=10' }
|
||||
dev: false
|
||||
|
||||
/cli-cursor/4.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==,
|
||||
}
|
||||
engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 }
|
||||
dependencies:
|
||||
restore-cursor: 4.0.0
|
||||
dev: true
|
||||
|
||||
/cli-spinners/2.9.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==,
|
||||
}
|
||||
engines: { node: '>=6' }
|
||||
dev: true
|
||||
|
||||
/cliui/8.0.1:
|
||||
resolution:
|
||||
{
|
||||
@@ -1893,7 +1961,6 @@ packages:
|
||||
integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==,
|
||||
}
|
||||
engines: { node: '>=0.8' }
|
||||
dev: false
|
||||
|
||||
/color-convert/1.9.3:
|
||||
resolution:
|
||||
@@ -2275,7 +2342,6 @@ packages:
|
||||
}
|
||||
dependencies:
|
||||
clone: 1.0.4
|
||||
dev: false
|
||||
|
||||
/defer-to-connect/2.0.1:
|
||||
resolution:
|
||||
@@ -3690,7 +3756,6 @@ packages:
|
||||
{
|
||||
integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==,
|
||||
}
|
||||
dev: false
|
||||
|
||||
/ignore/5.2.4:
|
||||
resolution:
|
||||
@@ -3931,6 +3996,14 @@ packages:
|
||||
is-path-inside: 3.0.3
|
||||
dev: false
|
||||
|
||||
/is-interactive/2.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==,
|
||||
}
|
||||
engines: { node: '>=12' }
|
||||
dev: true
|
||||
|
||||
/is-mergeable-object/1.1.1:
|
||||
resolution:
|
||||
{
|
||||
@@ -4063,6 +4136,14 @@ packages:
|
||||
}
|
||||
dev: false
|
||||
|
||||
/is-unicode-supported/1.3.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==,
|
||||
}
|
||||
engines: { node: '>=12' }
|
||||
dev: true
|
||||
|
||||
/is-utf8/0.2.1:
|
||||
resolution:
|
||||
{
|
||||
@@ -4538,7 +4619,6 @@ packages:
|
||||
{
|
||||
integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==,
|
||||
}
|
||||
dev: false
|
||||
|
||||
/lodash.sortby/4.7.0:
|
||||
resolution:
|
||||
@@ -4553,6 +4633,17 @@ packages:
|
||||
integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==,
|
||||
}
|
||||
|
||||
/log-symbols/5.1.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==,
|
||||
}
|
||||
engines: { node: '>=12' }
|
||||
dependencies:
|
||||
chalk: 5.2.0
|
||||
is-unicode-supported: 1.3.0
|
||||
dev: true
|
||||
|
||||
/loupe/2.3.6:
|
||||
resolution:
|
||||
{
|
||||
@@ -5157,6 +5248,24 @@ packages:
|
||||
word-wrap: 1.2.3
|
||||
dev: false
|
||||
|
||||
/ora/6.3.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==,
|
||||
}
|
||||
engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 }
|
||||
dependencies:
|
||||
chalk: 5.2.0
|
||||
cli-cursor: 4.0.0
|
||||
cli-spinners: 2.9.0
|
||||
is-interactive: 2.0.0
|
||||
is-unicode-supported: 1.3.0
|
||||
log-symbols: 5.1.0
|
||||
stdin-discarder: 0.1.0
|
||||
strip-ansi: 7.1.0
|
||||
wcwidth: 1.0.1
|
||||
dev: true
|
||||
|
||||
/os-locale/5.0.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -5774,6 +5883,18 @@ packages:
|
||||
util-deprecate: 1.0.2
|
||||
dev: false
|
||||
|
||||
/readable-stream/3.6.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==,
|
||||
}
|
||||
engines: { node: '>= 6' }
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
string_decoder: 1.1.1
|
||||
util-deprecate: 1.0.2
|
||||
dev: true
|
||||
|
||||
/readable-stream/4.4.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -5953,6 +6074,17 @@ packages:
|
||||
lowercase-keys: 3.0.0
|
||||
dev: false
|
||||
|
||||
/restore-cursor/4.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==,
|
||||
}
|
||||
engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 }
|
||||
dependencies:
|
||||
onetime: 5.1.2
|
||||
signal-exit: 3.0.7
|
||||
dev: true
|
||||
|
||||
/reusify/1.0.4:
|
||||
resolution:
|
||||
{
|
||||
@@ -6004,7 +6136,6 @@ packages:
|
||||
{
|
||||
integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==,
|
||||
}
|
||||
dev: false
|
||||
|
||||
/safe-buffer/5.2.1:
|
||||
resolution:
|
||||
@@ -6378,6 +6509,16 @@ packages:
|
||||
}
|
||||
dev: true
|
||||
|
||||
/stdin-discarder/0.1.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==,
|
||||
}
|
||||
engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 }
|
||||
dependencies:
|
||||
bl: 5.1.0
|
||||
dev: true
|
||||
|
||||
/stream-to-array/2.3.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -6476,7 +6617,6 @@ packages:
|
||||
}
|
||||
dependencies:
|
||||
safe-buffer: 5.1.2
|
||||
dev: false
|
||||
|
||||
/strip-ansi/6.0.1:
|
||||
resolution:
|
||||
@@ -6496,7 +6636,6 @@ packages:
|
||||
engines: { node: '>=12' }
|
||||
dependencies:
|
||||
ansi-regex: 6.0.1
|
||||
dev: false
|
||||
|
||||
/strip-bom-buf/2.0.0:
|
||||
resolution:
|
||||
@@ -6777,6 +6916,13 @@ packages:
|
||||
}
|
||||
dev: true
|
||||
|
||||
/tslib/2.6.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==,
|
||||
}
|
||||
dev: false
|
||||
|
||||
/tsup/7.0.0_typescript@5.1.3:
|
||||
resolution:
|
||||
{
|
||||
@@ -7052,7 +7198,6 @@ packages:
|
||||
{
|
||||
integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==,
|
||||
}
|
||||
dev: false
|
||||
|
||||
/uuid/3.4.0:
|
||||
resolution:
|
||||
@@ -7250,7 +7395,6 @@ packages:
|
||||
}
|
||||
dependencies:
|
||||
defaults: 1.0.4
|
||||
dev: false
|
||||
|
||||
/web-ext/7.6.2:
|
||||
resolution:
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import tsup from 'tsup';
|
||||
import glob from 'fast-glob';
|
||||
import pc from 'picocolors';
|
||||
import path from 'path';
|
||||
import { formatDuration } from '../src/core/utils/formatDuration';
|
||||
import ora from 'ora';
|
||||
import fs from 'fs-extra';
|
||||
import { consola } from 'consola';
|
||||
|
||||
const spinner = ora('Building WXT').start();
|
||||
|
||||
const startTime = Date.now();
|
||||
const outDir = 'dist';
|
||||
const virtualEntrypoints = ['background', 'content-script'];
|
||||
|
||||
await fs.rm(outDir, { recursive: true, force: true });
|
||||
|
||||
await Promise.all([
|
||||
tsup.build({
|
||||
entry: {
|
||||
index: 'src/index.ts',
|
||||
},
|
||||
format: ['cjs', 'esm'],
|
||||
sourcemap: true,
|
||||
dts: true,
|
||||
silent: true,
|
||||
}),
|
||||
tsup.build({
|
||||
entry: { cli: 'src/cli/index.ts' },
|
||||
format: ['cjs'],
|
||||
sourcemap: 'inline',
|
||||
silent: true,
|
||||
}),
|
||||
tsup.build({
|
||||
entry: { client: 'src/client/index.ts' },
|
||||
format: ['esm'],
|
||||
sourcemap: 'inline',
|
||||
dts: true,
|
||||
silent: true,
|
||||
}),
|
||||
...virtualEntrypoints.map((entryName) =>
|
||||
tsup.build({
|
||||
entry: {
|
||||
[`virtual-modules/${entryName}-entrypoint`]: `src/client/virtual-modules/${entryName}-entrypoint.ts`,
|
||||
},
|
||||
format: ['esm'],
|
||||
sourcemap: true,
|
||||
silent: true,
|
||||
external: [`virtual:user-${entryName}`],
|
||||
}),
|
||||
),
|
||||
tsup.build({
|
||||
entry: {
|
||||
'virtual-modules/reload-html': `src/client/virtual-modules/reload-html.ts`,
|
||||
},
|
||||
format: ['esm'],
|
||||
sourcemap: true,
|
||||
silent: true,
|
||||
}),
|
||||
]).catch((err) => {
|
||||
spinner.fail();
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
spinner.succeed();
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
const outFiles = await glob(`${outDir}/**`);
|
||||
outFiles.forEach((file, i) => {
|
||||
const color = file.endsWith('.map')
|
||||
? pc.dim
|
||||
: file.endsWith('.d.ts')
|
||||
? pc.blue
|
||||
: pc.cyan;
|
||||
const prefix = i === outFiles.length - 1 ? ' └─' : ' ├─';
|
||||
console.log(
|
||||
`${pc.gray(prefix)} ${pc.dim(outDir + path.sep)}${color(
|
||||
path.relative(outDir, file),
|
||||
)}`,
|
||||
);
|
||||
});
|
||||
consola.success(`Finished in ${formatDuration(duration)}`);
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
declare const __TEST_SEED__: number;
|
||||
@@ -0,0 +1,11 @@
|
||||
import browser from 'webextension-polyfill';
|
||||
|
||||
/**
|
||||
* https://developer.chrome.com/blog/longer-esw-lifetimes/
|
||||
*/
|
||||
export function keepServiceWorkerAlive() {
|
||||
setInterval(async () => {
|
||||
// Calling an async browser API resets the service worker's timeout
|
||||
await browser.runtime.getPlatformInfo();
|
||||
}, 5e3);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
function print(method: (...args: any[]) => void, ...args: any[]) {
|
||||
if (typeof args[0] === 'string') {
|
||||
const message = args.shift();
|
||||
method(`[wxt] ${message}`, ...args);
|
||||
} else {
|
||||
method('[wxt]', ...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around `console` with a "[wxt]" prefix
|
||||
*/
|
||||
export const logger = {
|
||||
debug: (...args: any[]) => print(console.debug, ...args),
|
||||
log: (...args: any[]) => print(console.log, ...args),
|
||||
warn: (...args: any[]) => print(console.warn, ...args),
|
||||
error: (...args: any[]) => print(console.error, ...args),
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { logger } from './logger';
|
||||
|
||||
interface WebSocketMessage {
|
||||
type: string;
|
||||
event: string;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the websocket and listen for messages.
|
||||
*
|
||||
* @param onMessage Optional callback that is called when a message is recieved and we've verified
|
||||
* it's structure is what we expect.
|
||||
*/
|
||||
export function setupWebSocket(
|
||||
onMessage?: (message: WebSocketMessage) => void,
|
||||
) {
|
||||
const serverUrl = `${__DEV_SERVER_PROTOCOL__}//${__DEV_SERVER_HOSTNAME__}:${__DEV_SERVER_PORT__}`;
|
||||
logger.debug('Connecting to dev server @', serverUrl);
|
||||
const ws = new WebSocket(serverUrl, 'vite-hmr');
|
||||
|
||||
ws.addEventListener('open', () => {
|
||||
logger.debug('Connected to dev server');
|
||||
});
|
||||
ws.addEventListener('close', () => {
|
||||
logger.debug('Disconnected from dev server');
|
||||
});
|
||||
ws.addEventListener('error', (event) => {
|
||||
logger.error('Failed to connect to dev server', event);
|
||||
});
|
||||
|
||||
ws.addEventListener('message', (e) => {
|
||||
try {
|
||||
const message = JSON.parse(e.data) as WebSocketMessage;
|
||||
if (message.type === 'custom' && message.event?.startsWith?.('wxt:')) {
|
||||
onMessage?.(message);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Failed to handle message', err);
|
||||
}
|
||||
});
|
||||
|
||||
return ws;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import definition from 'virtual:user-background';
|
||||
import { setupWebSocket } from '../utils/setupWebSocket';
|
||||
import { logger } from '../utils/logger';
|
||||
import browser from 'webextension-polyfill';
|
||||
import { keepServiceWorkerAlive } from '../utils/keepServiceWorkerAlive';
|
||||
|
||||
if (__COMMAND__ === 'serve') {
|
||||
try {
|
||||
setupWebSocket((message) => {
|
||||
if (message.event === 'wxt:reload-extension') browser.runtime.reload();
|
||||
});
|
||||
|
||||
// Web Socket will disconnect if the service worker is killed
|
||||
keepServiceWorkerAlive();
|
||||
} catch (err) {
|
||||
logger.error('Failed to setup web socket connection with dev server', err);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const res = definition.main();
|
||||
// @ts-expect-error: res shouldn't be a promise, but we're checking it anyways
|
||||
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 script crashed on startup!');
|
||||
throw err;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import definition from 'virtual:user-content-script';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await definition.main();
|
||||
} catch (err) {
|
||||
logger.error('The content script crashed on startup!', err);
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,20 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
import { logger } from '../utils/logger';
|
||||
import { setupWebSocket } from '../utils/setupWebSocket';
|
||||
|
||||
if (__COMMAND__ === 'serve') {
|
||||
try {
|
||||
setupWebSocket((message) => {
|
||||
if (message.event === 'wxt:reload-page') {
|
||||
// We need to remove the initial slash from the path to compare correctly
|
||||
// "popup.html" === "/popup.html".substring(1)
|
||||
if (message.data === location.pathname.substring(1)) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('Failed to setup web socket connection with dev server', err);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Types required to make the virtual modules happy.
|
||||
|
||||
declare module '*?raw' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module 'virtual:user-background' {
|
||||
const definition: import('../../').BackgroundScriptDefintition;
|
||||
export default definition;
|
||||
}
|
||||
|
||||
declare module 'virtual:user-content-script' {
|
||||
const definition: import('../../').ContentScriptDefinition;
|
||||
export default definition;
|
||||
}
|
||||
|
||||
// Globals defined by the vite-plugins/devServerGlobals.ts and utils/globals.ts
|
||||
declare const __COMMAND__: 'build' | 'serve';
|
||||
declare const __DEV_SERVER_PROTOCOL__: string;
|
||||
declare const __DEV_SERVER_HOSTNAME__: string;
|
||||
declare const __DEV_SERVER_PORT__: string;
|
||||
@@ -1,6 +1,11 @@
|
||||
import * as vite from 'vite';
|
||||
import { BuildOutput, Entrypoint, InternalConfig } from '../types';
|
||||
import { groupEntrypoints } from '../utils/groupEntrypoints';
|
||||
import {
|
||||
BuildOutput,
|
||||
BuildStepOutput,
|
||||
Entrypoint,
|
||||
EntrypointGroup,
|
||||
InternalConfig,
|
||||
} from '../types';
|
||||
import * as plugins from '../vite-plugins';
|
||||
import { removeEmptyDirs } from '../utils/removeEmptyDirs';
|
||||
import { getEntrypointBundlePath } from '../utils/entrypoints';
|
||||
@@ -9,25 +14,22 @@ import fs from 'fs-extra';
|
||||
import { dirname, resolve } from 'path';
|
||||
|
||||
export async function buildEntrypoints(
|
||||
entrypoints: Entrypoint[],
|
||||
groups: EntrypointGroup[],
|
||||
config: InternalConfig,
|
||||
): Promise<BuildOutput> {
|
||||
const groups = groupEntrypoints(entrypoints);
|
||||
|
||||
const outputs: BuildOutput[] = [];
|
||||
): Promise<Omit<BuildOutput, 'manifest'>> {
|
||||
const steps: BuildStepOutput[] = [];
|
||||
for (const group of groups) {
|
||||
const output = Array.isArray(group)
|
||||
const step = Array.isArray(group)
|
||||
? await buildMultipleEntrypoints(group, config)
|
||||
: await buildSingleEntrypoint(group, config);
|
||||
outputs.push(output);
|
||||
steps.push(step);
|
||||
}
|
||||
const publicOutput = await copyPublicDirectory(config);
|
||||
outputs.push(publicOutput);
|
||||
const publicAssets = await copyPublicDirectory(config);
|
||||
|
||||
// Remove any empty directories from moving outputs around
|
||||
await removeEmptyDirs(config.outDir);
|
||||
|
||||
return outputs.flat();
|
||||
return { publicAssets, steps };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,7 +38,7 @@ export async function buildEntrypoints(
|
||||
async function buildSingleEntrypoint(
|
||||
entrypoint: Entrypoint,
|
||||
config: InternalConfig,
|
||||
): Promise<BuildOutput> {
|
||||
): Promise<BuildStepOutput> {
|
||||
// Should this entrypoint be wrapped by the vite-plugins/virtualEntrypoint plugin?
|
||||
const isVirtual = ['background', 'content-script'].includes(entrypoint.type);
|
||||
const entry = isVirtual
|
||||
@@ -53,6 +55,8 @@ async function buildSingleEntrypoint(
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// There's only a single output for this build, so we use the desired bundle path for the
|
||||
// entry output (like "content-scripts/overlay.js")
|
||||
entryFileNames: getEntrypointBundlePath(
|
||||
entrypoint,
|
||||
config.outDir,
|
||||
@@ -61,7 +65,7 @@ async function buildSingleEntrypoint(
|
||||
// Output content script CSS to assets/ with a hash to prevent conflicts. Defaults to
|
||||
// "[name].[ext]" in lib mode, which usually results in "style.css". That means multiple
|
||||
// content scripts with styles would overwrite each other if it weren't changed below.
|
||||
assetFileNames: `assets/${entrypoint.name}-[hash].[ext]`,
|
||||
assetFileNames: `assets/${entrypoint.name}.[ext]`,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -72,7 +76,10 @@ async function buildSingleEntrypoint(
|
||||
) as vite.InlineConfig;
|
||||
|
||||
const result = await vite.build(entryConfig);
|
||||
return getBuildOutput(result);
|
||||
return {
|
||||
entrypoints: entrypoint,
|
||||
chunks: getBuildOutputChunks(result),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,7 +88,7 @@ async function buildSingleEntrypoint(
|
||||
async function buildMultipleEntrypoints(
|
||||
entrypoints: Entrypoint[],
|
||||
config: InternalConfig,
|
||||
): Promise<BuildOutput> {
|
||||
): Promise<BuildStepOutput> {
|
||||
const multiPage: vite.InlineConfig = {
|
||||
plugins: [plugins.multipageMove(entrypoints, config)],
|
||||
build: {
|
||||
@@ -90,6 +97,14 @@ async function buildMultipleEntrypoints(
|
||||
input[entry.name] = entry.inputPath;
|
||||
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',
|
||||
// We can't control the "name", so we need a hash to prevent conflicts
|
||||
assetFileNames: 'assets/[name]-[hash].[ext]',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -100,12 +115,15 @@ async function buildMultipleEntrypoints(
|
||||
) as vite.InlineConfig;
|
||||
|
||||
const result = await vite.build(entryConfig);
|
||||
return getBuildOutput(result);
|
||||
return {
|
||||
entrypoints,
|
||||
chunks: getBuildOutputChunks(result),
|
||||
};
|
||||
}
|
||||
|
||||
function getBuildOutput(
|
||||
function getBuildOutputChunks(
|
||||
result: Awaited<ReturnType<typeof vite.build>>,
|
||||
): BuildOutput {
|
||||
): BuildStepOutput['chunks'] {
|
||||
if ('on' in result) throw Error('wxt does not support vite watch mode.');
|
||||
if (Array.isArray(result)) return result.flatMap(({ output }) => output);
|
||||
return result.output;
|
||||
@@ -113,19 +131,19 @@ function getBuildOutput(
|
||||
|
||||
async function copyPublicDirectory(
|
||||
config: InternalConfig,
|
||||
): Promise<BuildOutput> {
|
||||
if (!(await fs.exists(config.publicDir))) return [];
|
||||
): Promise<BuildOutput['publicAssets']> {
|
||||
const publicAssets: BuildOutput['publicAssets'] = [];
|
||||
if (!(await fs.exists(config.publicDir))) return publicAssets;
|
||||
|
||||
const files = await glob('**/*', { cwd: config.publicDir });
|
||||
|
||||
const outputs: BuildOutput = [];
|
||||
for (const file of files) {
|
||||
const srcPath = resolve(config.publicDir, file);
|
||||
const outPath = resolve(config.outDir, file);
|
||||
|
||||
await fs.ensureDir(dirname(outPath));
|
||||
await fs.copyFile(srcPath, outPath);
|
||||
outputs.push({
|
||||
publicAssets.push({
|
||||
type: 'asset',
|
||||
fileName: file,
|
||||
name: file,
|
||||
@@ -133,5 +151,6 @@ async function copyPublicDirectory(
|
||||
source: await fs.readFile(srcPath),
|
||||
});
|
||||
}
|
||||
return outputs;
|
||||
|
||||
return publicAssets;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@ export async function printBuildSummary(
|
||||
output: BuildOutput,
|
||||
config: InternalConfig,
|
||||
) {
|
||||
const chunks = output.sort((l, r) => {
|
||||
const chunks = [
|
||||
...output.steps.flatMap((step) => step.chunks),
|
||||
...output.publicAssets,
|
||||
].sort((l, r) => {
|
||||
const lWeight =
|
||||
CHUNK_SORT_WEIGHTS[l.fileName] ??
|
||||
CHUNK_SORT_WEIGHTS[extname(l.fileName)] ??
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as vite from 'vite';
|
||||
import { Manifest } from 'webextension-polyfill';
|
||||
import { UnimportOptions } from 'unimport';
|
||||
import { EntrypointGroup } from '.';
|
||||
|
||||
export interface InlineConfig {
|
||||
root?: string;
|
||||
@@ -29,13 +30,46 @@ export interface WxtInlineViteConfig
|
||||
build?: Omit<vite.BuildOptions, 'outDir'>;
|
||||
}
|
||||
|
||||
export type BuildOutput = (vite.Rollup.OutputChunk | vite.Rollup.OutputAsset)[];
|
||||
export interface BuildOutput {
|
||||
manifest: Manifest.WebExtensionManifest;
|
||||
publicAssets: vite.Rollup.OutputAsset[];
|
||||
steps: BuildStepOutput[];
|
||||
}
|
||||
|
||||
export interface BuildStepOutput {
|
||||
entrypoints: EntrypointGroup;
|
||||
chunks: (vite.Rollup.OutputChunk | vite.Rollup.OutputAsset)[];
|
||||
}
|
||||
|
||||
export interface WxtDevServer extends vite.ViteDevServer {
|
||||
logger: Logger;
|
||||
/**
|
||||
* Ex: `3000`
|
||||
*/
|
||||
port: number;
|
||||
/**
|
||||
* Ex: `"localhost"`
|
||||
*/
|
||||
hostname: string;
|
||||
/**
|
||||
* Ex: `"http://localhost:3000"`
|
||||
*/
|
||||
origin: string;
|
||||
/**
|
||||
* Tell the extension to reload by running `browser.runtime.reload`.
|
||||
*/
|
||||
reloadExtension: () => void;
|
||||
/**
|
||||
* Tell an extension page to reload.
|
||||
*
|
||||
* The path is the bundle path, not the input paths, so if the input paths is
|
||||
* "src/options/index.html", you would pass "options.html" because that's where it is written to
|
||||
* in the dist directory, and where it's available at in the actual extension.
|
||||
*
|
||||
* @example
|
||||
* server.reloadPage("popup.html")
|
||||
* server.reloadPage("sandbox.html")
|
||||
*/
|
||||
reloadPage: (path: string) => void;
|
||||
}
|
||||
|
||||
export type TargetBrowser = 'chrome' | 'firefox' | 'safari' | 'edge' | 'opera';
|
||||
@@ -144,7 +178,7 @@ export interface ContentScriptDefinition {
|
||||
matchAboutBlank?: boolean;
|
||||
matchOriginAsFallback?: boolean;
|
||||
world?: 'ISOLATED' | 'MAIN';
|
||||
main(onStopped: OnContentScriptStopped): void | Promise<void>;
|
||||
main(): void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface BackgroundScriptDefintition {
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { DevModeChange, detectDevChanges } from '../detectDevChanges';
|
||||
import {
|
||||
fakeBackgroundEntrypoint,
|
||||
fakeContentScriptEntrypoint,
|
||||
fakeFile,
|
||||
fakeManifest,
|
||||
fakeRollupOutputAsset,
|
||||
fakeRollupOutputChunk,
|
||||
} from '../../../testing/fake-objects';
|
||||
import { BuildOutput, BuildStepOutput } from '../../types';
|
||||
|
||||
describe('Detect Dev Changes', () => {
|
||||
describe('No changes', () => {
|
||||
it("should return 'no-change' when a build hasn't finished", () => {
|
||||
const actual = detectDevChanges(
|
||||
[['unknown', '/path/to/file.ts']],
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(actual).toEqual({ type: 'no-change' });
|
||||
});
|
||||
|
||||
it("should return 'no-change' when the changed file isn't used by any of the entrypoints", () => {
|
||||
const change: [string, string] = ['unknown', '/some/path.ts'];
|
||||
const currentOutput: BuildOutput = {
|
||||
manifest: fakeManifest(),
|
||||
publicAssets: [],
|
||||
steps: [
|
||||
{
|
||||
entrypoints: fakeContentScriptEntrypoint(),
|
||||
chunks: [fakeRollupOutputChunk(), fakeRollupOutputChunk()],
|
||||
},
|
||||
{
|
||||
entrypoints: fakeContentScriptEntrypoint(),
|
||||
chunks: [
|
||||
fakeRollupOutputChunk(),
|
||||
fakeRollupOutputChunk(),
|
||||
fakeRollupOutputChunk(),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const actual = detectDevChanges([change], currentOutput);
|
||||
|
||||
expect(actual).toEqual({ type: 'no-change' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Public Assets', () => {
|
||||
it("should return 'extension-reload' without any groups to rebuild when the changed file is a public asset", () => {
|
||||
const change: [string, string] = [
|
||||
'unknown',
|
||||
'/root/src/public/image.svg',
|
||||
];
|
||||
const asset1 = fakeRollupOutputAsset({
|
||||
fileName: 'image.svg',
|
||||
});
|
||||
const asset2 = fakeRollupOutputAsset({
|
||||
fileName: 'some-other-image.svg',
|
||||
});
|
||||
const currentOutput: BuildOutput = {
|
||||
manifest: fakeManifest(),
|
||||
publicAssets: [asset1, asset2],
|
||||
steps: [],
|
||||
};
|
||||
const expected: DevModeChange = {
|
||||
type: 'extension-reload',
|
||||
rebuildGroups: [],
|
||||
cachedOutput: {
|
||||
...currentOutput,
|
||||
publicAssets: [asset2],
|
||||
},
|
||||
};
|
||||
|
||||
const actual = detectDevChanges([change], currentOutput);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Background', () => {
|
||||
it("should rebuild the background and reload the extension when the changed file in it's chunks' `moduleIds` field", () => {
|
||||
const changedPath = '/root/utils/shared.ts';
|
||||
const contentScript = fakeContentScriptEntrypoint({
|
||||
inputPath: '/root/overlay.content.ts',
|
||||
});
|
||||
const background = fakeBackgroundEntrypoint({
|
||||
inputPath: '/root/background.ts',
|
||||
});
|
||||
|
||||
const step1: BuildStepOutput = {
|
||||
entrypoints: contentScript,
|
||||
chunks: [
|
||||
fakeRollupOutputChunk({
|
||||
moduleIds: [fakeFile(), fakeFile()],
|
||||
}),
|
||||
],
|
||||
};
|
||||
const step2: BuildStepOutput = {
|
||||
entrypoints: background,
|
||||
chunks: [
|
||||
fakeRollupOutputChunk({
|
||||
moduleIds: [fakeFile(), changedPath, fakeFile()],
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
const currentOutput: BuildOutput = {
|
||||
manifest: fakeManifest(),
|
||||
publicAssets: [],
|
||||
steps: [step1, step2],
|
||||
};
|
||||
const expected: DevModeChange = {
|
||||
type: 'extension-reload',
|
||||
cachedOutput: {
|
||||
...currentOutput,
|
||||
steps: [step1],
|
||||
},
|
||||
rebuildGroups: [background],
|
||||
};
|
||||
|
||||
const actual = detectDevChanges(
|
||||
[['unknown', changedPath]],
|
||||
currentOutput,
|
||||
);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,157 @@
|
||||
import { BuildOutput, BuildStepOutput, EntrypointGroup } from '../types';
|
||||
import * as vite from 'vite';
|
||||
|
||||
/**
|
||||
* Compare the changed files vs the build output and determine what kind of reload needs to happen:
|
||||
*
|
||||
* - Do nothing
|
||||
* - CSS or JS file associated with an HTML page is changed - this is handled automatically by the
|
||||
* dev server
|
||||
* - Change isn't used by any of the entrypoints
|
||||
* - Reload Content script
|
||||
* - CSS or JS file associated with a content script
|
||||
* - Background script will be told to reload the content script
|
||||
* - Reload HTML file
|
||||
* - HTML file itself is saved - HMR doesn't handle this because the HTML pages are pre-rendered
|
||||
* - Chrome is OK reloading the page when the HTML file is changed without reloading the whole
|
||||
* extension. Not sure about firefox, this might need to change to an extension reload
|
||||
* - Reload extension
|
||||
* - Background script is changed
|
||||
* - Manifest is different
|
||||
* - Restart browser
|
||||
* - Config file changed (wxt.config.ts, .env, web-ext.config.ts, etc)
|
||||
*/
|
||||
export function detectDevChanges(
|
||||
changedFiles: [event: string, path: string][],
|
||||
currentOutput: BuildOutput | undefined,
|
||||
): DevModeChange {
|
||||
if (currentOutput == null) return { type: 'no-change' };
|
||||
|
||||
const changedSteps = new Set(
|
||||
changedFiles.flatMap((changedFile) =>
|
||||
findEffectedSteps(changedFile, currentOutput),
|
||||
),
|
||||
);
|
||||
if (changedSteps.size === 0) return { type: 'no-change' };
|
||||
|
||||
const unchangedOutput: BuildOutput = {
|
||||
manifest: currentOutput.manifest,
|
||||
steps: [],
|
||||
publicAssets: [],
|
||||
};
|
||||
const changedOutput: BuildOutput = {
|
||||
manifest: currentOutput.manifest,
|
||||
steps: [],
|
||||
publicAssets: [],
|
||||
};
|
||||
|
||||
for (const step of currentOutput.steps) {
|
||||
if (changedSteps.has(step)) {
|
||||
changedOutput.steps.push(step);
|
||||
} else {
|
||||
unchangedOutput.steps.push(step);
|
||||
}
|
||||
}
|
||||
for (const asset of currentOutput.publicAssets) {
|
||||
if (changedSteps.has(asset)) {
|
||||
changedOutput.publicAssets.push(asset);
|
||||
} else {
|
||||
unchangedOutput.publicAssets.push(asset);
|
||||
}
|
||||
}
|
||||
|
||||
const isOnlyHtmlChanges = !changedFiles.find(
|
||||
([_, file]) => !file.endsWith('.html'),
|
||||
);
|
||||
if (isOnlyHtmlChanges) {
|
||||
return {
|
||||
type: 'html-reload',
|
||||
cachedOutput: unchangedOutput,
|
||||
rebuildGroups: changedOutput.steps.map((step) => step.entrypoints),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'extension-reload',
|
||||
cachedOutput: unchangedOutput,
|
||||
rebuildGroups: changedOutput.steps.map((step) => step.entrypoints),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* For a single change, return all the step of the build output that were effected by it.
|
||||
*/
|
||||
function findEffectedSteps(
|
||||
changedFile: [event: string, path: string],
|
||||
currentOutput: BuildOutput,
|
||||
): DetectedChange[] {
|
||||
const changes: DetectedChange[] = [];
|
||||
const changedPath = changedFile[1];
|
||||
|
||||
const isChunkEffected = (
|
||||
chunk: vite.Rollup.OutputChunk | vite.Rollup.OutputAsset,
|
||||
): boolean =>
|
||||
// If it's an HTML file with the same path, is is effected because HTML files need to be pre-rendered
|
||||
// TODO: use bundle path to support `<name>/index.html`?
|
||||
(chunk.type === 'asset' && changedPath.endsWith(chunk.fileName)) ||
|
||||
// If it's a chunk that depends on the changed file, it is effected
|
||||
(chunk.type === 'chunk' && chunk.moduleIds.includes(changedPath));
|
||||
|
||||
for (const step of currentOutput.steps) {
|
||||
const effectedChunk = step.chunks.find((chunk) => isChunkEffected(chunk));
|
||||
if (effectedChunk) changes.push(step);
|
||||
}
|
||||
|
||||
const effectedAsset = currentOutput.publicAssets.find((chunk) =>
|
||||
isChunkEffected(chunk),
|
||||
);
|
||||
if (effectedAsset) changes.push(effectedAsset);
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains information about what files changed, what needs rebuilt, and the type of reload that is
|
||||
* required.
|
||||
*/
|
||||
export type DevModeChange = NoChange | HtmlReload | ExtensionReload;
|
||||
// | BrowserRestart
|
||||
// | ContentScriptReload
|
||||
|
||||
interface NoChange {
|
||||
type: 'no-change';
|
||||
}
|
||||
|
||||
interface RebuildChange {
|
||||
/**
|
||||
* The list of entrypoints that need rebuilt.
|
||||
*/
|
||||
rebuildGroups: EntrypointGroup[];
|
||||
/**
|
||||
* The previous output stripped of any files are going to change.
|
||||
*/
|
||||
cachedOutput: BuildOutput;
|
||||
}
|
||||
|
||||
interface HtmlReload extends RebuildChange {
|
||||
type: 'html-reload';
|
||||
}
|
||||
|
||||
interface ExtensionReload extends RebuildChange {
|
||||
type: 'extension-reload';
|
||||
}
|
||||
|
||||
// interface BrowserRestart extends RebuildChange {
|
||||
// type: 'browser-restart';
|
||||
// }
|
||||
|
||||
// interface ContentScriptReload extends RebuildChange {
|
||||
// type: 'content-script-reload';
|
||||
// }
|
||||
|
||||
/**
|
||||
* When figuring out what needs reloaded, this stores the step that was changed, or the public
|
||||
* directory asset that was changed. It doesn't know what type of change is required yet. Just an
|
||||
* intermediate type.
|
||||
*/
|
||||
type DetectedChange = BuildStepOutput | vite.Rollup.OutputAsset;
|
||||
@@ -94,7 +94,7 @@ export async function getInternalConfig(
|
||||
// Customize the default vite config
|
||||
finalConfig.vite.root = root;
|
||||
finalConfig.vite.configFile = false;
|
||||
finalConfig.vite.logLevel = 'silent';
|
||||
finalConfig.vite.logLevel = 'warn';
|
||||
|
||||
finalConfig.vite.build ??= {};
|
||||
finalConfig.vite.build.outDir = outDir;
|
||||
@@ -110,6 +110,7 @@ export async function getInternalConfig(
|
||||
finalConfig.vite.plugins.push(
|
||||
plugins.virtualEntrypoin('content-script', finalConfig),
|
||||
);
|
||||
finalConfig.vite.plugins.push(plugins.devServerGlobals(finalConfig));
|
||||
|
||||
finalConfig.vite.define ??= {};
|
||||
getGlobals(finalConfig).forEach((global) => {
|
||||
|
||||
@@ -39,5 +39,10 @@ export function getGlobals(
|
||||
value: config.browser === 'opera',
|
||||
type: `boolean`,
|
||||
},
|
||||
{
|
||||
name: '__COMMAND__',
|
||||
value: config.command,
|
||||
type: `"build" | "serve"`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { scanExports } from 'unimport';
|
||||
|
||||
export async function importTsFile<T>(root: string, path: string): Promise<T> {
|
||||
const clientImports = await scanExports(
|
||||
resolve(root, 'node_modules/wxt/dist/client/index.js'),
|
||||
resolve(root, 'node_modules/wxt/dist/client.js'),
|
||||
);
|
||||
const jiti = createJITI(__filename, {
|
||||
cache: false,
|
||||
|
||||
@@ -29,7 +29,7 @@ export async function writeManifest(
|
||||
await fs.ensureDir(config.outDir);
|
||||
await fs.writeFile(resolve(config.outDir, 'manifest.json'), str, 'utf-8');
|
||||
|
||||
output.unshift({
|
||||
output.publicAssets.unshift({
|
||||
type: 'asset',
|
||||
fileName: 'manifest.json',
|
||||
name: 'manifest',
|
||||
@@ -43,7 +43,7 @@ export async function writeManifest(
|
||||
*/
|
||||
export async function generateMainfest(
|
||||
entrypoints: Entrypoint[],
|
||||
buildOutput: BuildOutput,
|
||||
buildOutput: Omit<BuildOutput, 'manifest'>,
|
||||
config: InternalConfig,
|
||||
): Promise<Manifest.WebExtensionManifest> {
|
||||
const pkg = await getPackageJson(config);
|
||||
@@ -100,7 +100,7 @@ function simplifyVersion(versionName: string): string {
|
||||
function addEntrypoints(
|
||||
manifest: Manifest.WebExtensionManifest,
|
||||
entrypoints: Entrypoint[],
|
||||
buildOutput: BuildOutput,
|
||||
buildOutput: Omit<BuildOutput, 'manifest'>,
|
||||
config: InternalConfig,
|
||||
): void {
|
||||
const entriesByType = entrypoints.reduce<
|
||||
@@ -351,14 +351,16 @@ function addDevModeCsp(
|
||||
*/
|
||||
function getContentScriptCssFiles(
|
||||
contentScripts: ContentScriptEntrypoint[],
|
||||
buildOutput: BuildOutput,
|
||||
buildOutput: Omit<BuildOutput, 'manifest'>,
|
||||
): string[] | undefined {
|
||||
const css: string[] = [];
|
||||
|
||||
const allChunks = buildOutput.steps.flatMap((step) => step.chunks);
|
||||
|
||||
contentScripts.forEach((script) => {
|
||||
const cssRegex = new RegExp(`^assets/${script.name}-[a-f0-9]{8}.css$`);
|
||||
const relatedCss = buildOutput.find((chunk) =>
|
||||
chunk.fileName.match(cssRegex),
|
||||
// TODO: optimize and remove loop with a map
|
||||
const relatedCss = allChunks.find(
|
||||
(chunk) => chunk.fileName === `assets/${script.name}.css`,
|
||||
);
|
||||
if (relatedCss) css.push(relatedCss.fileName);
|
||||
});
|
||||
|
||||
@@ -11,6 +11,21 @@ export function devHtmlPrerender(config: InternalConfig): vite.Plugin {
|
||||
return {
|
||||
apply: 'build',
|
||||
name: 'wxt:dev-html-prerender',
|
||||
config(userConfig) {
|
||||
return vite.mergeConfig(
|
||||
{
|
||||
resolve: {
|
||||
alias: {
|
||||
'@wxt/reload-html': resolve(
|
||||
config.root,
|
||||
'node_modules/wxt/dist/virtual-modules/reload-html.js',
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
userConfig,
|
||||
);
|
||||
},
|
||||
async transform(html, id) {
|
||||
const server = config.server;
|
||||
if (config.command !== 'serve' || server == null || !id.endsWith('.html'))
|
||||
@@ -43,6 +58,12 @@ export function devHtmlPrerender(config: InternalConfig): vite.Plugin {
|
||||
pointToDevServer('script[type=module]', 'src');
|
||||
pointToDevServer('link[rel=stylesheet]', 'href');
|
||||
|
||||
// Add a script to add page reloading
|
||||
const reloader = document.createElement('script');
|
||||
reloader.src = '@wxt/reload-html';
|
||||
reloader.type = 'module';
|
||||
document.head.appendChild(reloader);
|
||||
|
||||
const newHtml = document.toString();
|
||||
config.logger.debug('Transformed ' + id);
|
||||
config.logger.debug('Old HTML:\n' + html);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Plugin } from 'vite';
|
||||
import { InternalConfig } from '../types';
|
||||
|
||||
/**
|
||||
* Defines global constants about the dev server. Helps scripts connect to the server's web socket.
|
||||
*/
|
||||
export function devServerGlobals(internalConfig: InternalConfig): Plugin {
|
||||
return {
|
||||
name: 'wxt:dev-server-globals',
|
||||
config(config) {
|
||||
if (internalConfig.server == null || internalConfig.command == 'build')
|
||||
return;
|
||||
|
||||
config.define ??= {};
|
||||
config.define.__DEV_SERVER_PROTOCOL__ = JSON.stringify('ws:');
|
||||
config.define.__DEV_SERVER_HOSTNAME__ = JSON.stringify(
|
||||
internalConfig.server.hostname,
|
||||
);
|
||||
config.define.__DEV_SERVER_PORT__ = JSON.stringify(
|
||||
internalConfig.server.port,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './download';
|
||||
export * from './unimport';
|
||||
export * from './multipageMove';
|
||||
export * from './devHtmlPrerender';
|
||||
export * from './devServerGlobals';
|
||||
export * from './download';
|
||||
export * from './multipageMove';
|
||||
export * from './unimport';
|
||||
export * from './virtualEntrypoint';
|
||||
|
||||
@@ -53,7 +53,7 @@ export function multipageMove(
|
||||
const oldAbsPath = resolve(config.outDir, oldBundlePath);
|
||||
const newAbsPath = resolve(config.outDir, newBundlePath);
|
||||
await ensureDir(dirname(newAbsPath));
|
||||
await fs.move(oldAbsPath, newAbsPath);
|
||||
await fs.move(oldAbsPath, newAbsPath, { overwrite: true });
|
||||
|
||||
const renamedChunk = {
|
||||
...bundle[oldBundlePath],
|
||||
|
||||
@@ -29,10 +29,13 @@ export function virtualEntrypoin(
|
||||
|
||||
const inputPath = id.replace(resolvedVirtualId, '');
|
||||
const template = await fs.readFile(
|
||||
resolve(config.root, `node_modules/wxt/templates/virtual-${type}.ts`),
|
||||
resolve(
|
||||
config.root,
|
||||
`node_modules/wxt/dist/virtual-modules/${type}-entrypoint.js`,
|
||||
),
|
||||
'utf-8',
|
||||
);
|
||||
return template.replaceAll('{{moduleId}}', inputPath);
|
||||
return template.replace(`virtual:user-${type}`, inputPath);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
+120
-10
@@ -3,6 +3,7 @@ import {
|
||||
WxtDevServer,
|
||||
InlineConfig,
|
||||
InternalConfig,
|
||||
EntrypointGroup,
|
||||
} from './core/types';
|
||||
import { getInternalConfig } from './core/utils/getInternalConfig';
|
||||
import { findEntrypoints } from './core/build/findEntrypoints';
|
||||
@@ -16,6 +17,16 @@ import * as vite from 'vite';
|
||||
import { findOpenPort } from './core/utils/findOpenPort';
|
||||
import { formatDuration } from './core/utils/formatDuration';
|
||||
import { createWebExtRunner } from './core/runners/createWebExtRunner';
|
||||
import { groupEntrypoints } from './core/utils/groupEntrypoints';
|
||||
import { Manifest } from 'webextension-polyfill';
|
||||
import { detectDevChanges } from './core/utils/detectDevChanges';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import { consola } from 'consola';
|
||||
import { relative } from 'node:path';
|
||||
import {
|
||||
getEntrypointBundlePath,
|
||||
getEntrypointOutputFile,
|
||||
} from './core/utils/entrypoints';
|
||||
|
||||
export { version } from '../package.json';
|
||||
export * from './core/types/external';
|
||||
@@ -41,13 +52,77 @@ export async function createServer(
|
||||
origin,
|
||||
},
|
||||
};
|
||||
const internalConfig = await getInternalConfig(
|
||||
let internalConfig = await getInternalConfig(
|
||||
vite.mergeConfig(serverConfig, config ?? {}),
|
||||
'serve',
|
||||
);
|
||||
const runner = createWebExtRunner();
|
||||
|
||||
let hasBuiltOnce = false;
|
||||
let currentOutput: BuildOutput | undefined;
|
||||
const fileChangedMutex = new Mutex();
|
||||
const changeQueue: Array<[string, string]> = [];
|
||||
|
||||
const viteServer = await vite.createServer(internalConfig.vite);
|
||||
viteServer.watcher.on('all', async (event, path, stats) => {
|
||||
if (!hasBuiltOnce || path.startsWith(internalConfig.outBaseDir)) return;
|
||||
changeQueue.push([event, path]);
|
||||
|
||||
await fileChangedMutex.runExclusive(async () => {
|
||||
const fileChanges = changeQueue.splice(0, changeQueue.length);
|
||||
const changes = detectDevChanges(fileChanges, currentOutput);
|
||||
|
||||
if (changes.type === 'no-change') return;
|
||||
|
||||
// Log the entrypoints that were effected
|
||||
consola.info(
|
||||
`Changed: ${Array.from(new Set(fileChanges.map((change) => change[1])))
|
||||
.map((file) => pc.dim(relative(internalConfig.root, file)))
|
||||
.join(', ')}`,
|
||||
);
|
||||
const rebuiltNames = changes.rebuildGroups
|
||||
.flat()
|
||||
.map((entry) => {
|
||||
return pc.cyan(
|
||||
relative(internalConfig.outDir, getEntrypointOutputFile(entry, '')),
|
||||
);
|
||||
})
|
||||
.join(pc.dim(', '));
|
||||
|
||||
// Get latest config and Rebuild groups with changes
|
||||
internalConfig = await getInternalConfig(
|
||||
vite.mergeConfig(serverConfig, config ?? {}),
|
||||
'serve',
|
||||
);
|
||||
internalConfig.server = server;
|
||||
const { output: newOutput } = await rebuild(
|
||||
internalConfig,
|
||||
// TODO: this excludes new entrypoints, so they're not built until the dev command is restarted
|
||||
changes.rebuildGroups,
|
||||
changes.cachedOutput,
|
||||
);
|
||||
currentOutput = newOutput;
|
||||
|
||||
// Perform reloads
|
||||
switch (changes.type) {
|
||||
case 'extension-reload':
|
||||
server.reloadExtension();
|
||||
consola.success(`Reloaded extension: ${rebuiltNames}`);
|
||||
break;
|
||||
case 'html-reload':
|
||||
changes.rebuildGroups.flat().forEach((entry) => {
|
||||
const path = getEntrypointBundlePath(
|
||||
entry,
|
||||
internalConfig.outDir,
|
||||
'.html',
|
||||
);
|
||||
server.reloadPage(path);
|
||||
});
|
||||
consola.success(`Reloaded pages: ${rebuiltNames}`);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
const server: WxtDevServer = {
|
||||
...viteServer,
|
||||
async listen(port, isRestart) {
|
||||
@@ -63,15 +138,23 @@ export async function createServer(
|
||||
|
||||
return res;
|
||||
},
|
||||
logger: internalConfig.logger,
|
||||
port,
|
||||
hostname,
|
||||
origin,
|
||||
reloadExtension: () => {
|
||||
server.ws.send('wxt:reload-extension');
|
||||
},
|
||||
reloadPage: (path) => {
|
||||
// Can't use Vite's built-in "full-reload" event because it doesn't like our paths, it expects
|
||||
// paths ending in "/index.html"
|
||||
server.ws.send('wxt:reload-page', path);
|
||||
},
|
||||
};
|
||||
internalConfig.logger.info('Created dev server');
|
||||
|
||||
internalConfig.server = server;
|
||||
await buildInternal(internalConfig);
|
||||
currentOutput = await buildInternal(internalConfig);
|
||||
hasBuiltOnce = true;
|
||||
|
||||
return server;
|
||||
}
|
||||
@@ -90,14 +173,9 @@ async function buildInternal(config: InternalConfig): Promise<BuildOutput> {
|
||||
await fs.rm(config.outDir, { recursive: true, force: true });
|
||||
await fs.ensureDir(config.outDir);
|
||||
|
||||
// Build
|
||||
const entrypoints = await findEntrypoints(config);
|
||||
await generateTypesDir(entrypoints, config);
|
||||
const output = await buildEntrypoints(entrypoints, config);
|
||||
|
||||
// Write manifest
|
||||
const manifest = await generateMainfest(entrypoints, output, config);
|
||||
await writeManifest(manifest, output, config);
|
||||
const groups = groupEntrypoints(entrypoints);
|
||||
const { output } = await rebuild(config, groups);
|
||||
|
||||
// Post-build
|
||||
config.logger.success(
|
||||
@@ -107,3 +185,35 @@ async function buildInternal(config: InternalConfig): Promise<BuildOutput> {
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
export async function rebuild(
|
||||
config: InternalConfig,
|
||||
entrypointGroups: EntrypointGroup[],
|
||||
existingOutput: Omit<BuildOutput, 'manifest'> = {
|
||||
steps: [],
|
||||
publicAssets: [],
|
||||
},
|
||||
): Promise<{ output: BuildOutput; manifest: Manifest.WebExtensionManifest }> {
|
||||
// Build
|
||||
const allEntrypoints = await findEntrypoints(config);
|
||||
await generateTypesDir(allEntrypoints, config);
|
||||
const buildOutput = await buildEntrypoints(entrypointGroups, config);
|
||||
|
||||
const manifest = await generateMainfest(allEntrypoints, buildOutput, config);
|
||||
const output: BuildOutput = {
|
||||
manifest,
|
||||
...buildOutput,
|
||||
};
|
||||
|
||||
// Write manifest
|
||||
await writeManifest(manifest, output, config);
|
||||
|
||||
return {
|
||||
output: {
|
||||
manifest,
|
||||
steps: [...existingOutput.steps, ...output.steps],
|
||||
publicAssets: [...existingOutput.publicAssets, ...output.publicAssets],
|
||||
},
|
||||
manifest,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* SHOULD ONLY BE IMPORTED IN TESTS.
|
||||
*/
|
||||
import { resolve } from 'path';
|
||||
import {
|
||||
BackgroundEntrypoint,
|
||||
ContentScriptEntrypoint,
|
||||
GenericEntrypoint,
|
||||
} from '..';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import merge from 'lodash.merge';
|
||||
import { Rollup } from 'vite';
|
||||
import { Manifest } from 'webextension-polyfill';
|
||||
|
||||
faker.seed(__TEST_SEED__);
|
||||
|
||||
type DeepPartial<T> = { [key in keyof T]+?: Partial<T[key]> };
|
||||
function fakeObjectCreator<T>(base: () => T) {
|
||||
return (overrides?: DeepPartial<T>): T => merge(base(), overrides);
|
||||
}
|
||||
|
||||
export function fakeFileName(): string {
|
||||
return faker.string.alphanumeric() + '.' + faker.string.alpha({ length: 3 });
|
||||
}
|
||||
|
||||
export function fakeFile(root = process.cwd()): string {
|
||||
return resolve(root, fakeFileName());
|
||||
}
|
||||
|
||||
export function fakeDir(root = process.cwd()): string {
|
||||
return resolve(root, faker.string.alphanumeric());
|
||||
}
|
||||
|
||||
export const fakeContentScriptEntrypoint =
|
||||
fakeObjectCreator<ContentScriptEntrypoint>(() => ({
|
||||
type: 'content-script',
|
||||
inputPath: fakeFile('src'),
|
||||
name: faker.string.alpha(),
|
||||
options: {
|
||||
matches: [],
|
||||
matchAboutBlank: faker.helpers.arrayElement([true, false, undefined]),
|
||||
matchOriginAsFallback: faker.helpers.arrayElement([
|
||||
true,
|
||||
false,
|
||||
undefined,
|
||||
]),
|
||||
runAt: faker.helpers.arrayElement([
|
||||
'document_start',
|
||||
'document_end',
|
||||
'document_idle',
|
||||
undefined,
|
||||
]),
|
||||
},
|
||||
outputDir: fakeDir('.output'),
|
||||
}));
|
||||
|
||||
export const fakeBackgroundEntrypoint = fakeObjectCreator<BackgroundEntrypoint>(
|
||||
() => ({
|
||||
type: 'background',
|
||||
inputPath: fakeFile('src'),
|
||||
name: faker.string.alpha(),
|
||||
options: {
|
||||
persistent: faker.helpers.arrayElement([true, false, undefined]),
|
||||
type: faker.helpers.maybe(() => 'module'),
|
||||
},
|
||||
outputDir: fakeDir('.output'),
|
||||
}),
|
||||
);
|
||||
|
||||
export const fakeUnlistedScriptEntrypoint =
|
||||
fakeObjectCreator<GenericEntrypoint>(() => ({
|
||||
type: 'unlisted-script',
|
||||
inputPath: fakeFile('src'),
|
||||
name: faker.string.alpha(),
|
||||
outputDir: fakeDir('.output'),
|
||||
}));
|
||||
|
||||
export const fakeRollupOutputChunk = fakeObjectCreator<Rollup.OutputChunk>(
|
||||
() => ({
|
||||
type: 'chunk',
|
||||
code: '',
|
||||
dynamicImports: [],
|
||||
exports: [],
|
||||
facadeModuleId: faker.helpers.arrayElement([null, fakeFile()]),
|
||||
fileName: faker.string.alphanumeric(),
|
||||
implicitlyLoadedBefore: [],
|
||||
importedBindings: {},
|
||||
imports: [],
|
||||
isDynamicEntry: faker.datatype.boolean(),
|
||||
isEntry: faker.datatype.boolean(),
|
||||
isImplicitEntry: faker.datatype.boolean(),
|
||||
map: null,
|
||||
moduleIds: [],
|
||||
modules: {},
|
||||
name: faker.string.alpha(),
|
||||
referencedFiles: [],
|
||||
viteMetadata: {
|
||||
importedAssets: new Set(),
|
||||
importedCss: new Set(),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const fakeRollupOutputAsset = fakeObjectCreator<Rollup.OutputAsset>(
|
||||
() => ({
|
||||
type: 'asset',
|
||||
fileName: fakeFileName(),
|
||||
name: faker.string.alpha(),
|
||||
needsCodeReference: faker.datatype.boolean(),
|
||||
source: '',
|
||||
}),
|
||||
);
|
||||
|
||||
export function fakeRollupOutput(): Rollup.OutputAsset | Rollup.OutputChunk {
|
||||
return faker.helpers.arrayElement([
|
||||
fakeRollupOutputAsset(),
|
||||
fakeRollupOutputChunk(),
|
||||
]);
|
||||
}
|
||||
|
||||
export const fakeManifest = fakeObjectCreator<Manifest.WebExtensionManifest>(
|
||||
() => ({
|
||||
manifest_version: faker.helpers.arrayElement([2, 3]),
|
||||
name: faker.string.alphanumeric(),
|
||||
version: `${faker.number.int()}.${faker.number.int()}.${faker.number.int()}`,
|
||||
}),
|
||||
);
|
||||
|
||||
export function fakeArray<T>(createItem: () => T, count = 3): T[] {
|
||||
const array: T[] = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
array.push(createItem());
|
||||
}
|
||||
return array;
|
||||
}
|
||||
Vendored
-11
@@ -1,11 +0,0 @@
|
||||
// Types required to make templates happy.
|
||||
|
||||
declare module '*?raw' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '{{moduleId}}' {
|
||||
const variable: any;
|
||||
export default variable;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import definition from '{{moduleId}}';
|
||||
|
||||
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) {
|
||||
console.error('The background script crashed on startup!');
|
||||
throw err;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import definition from '{{moduleId}}';
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await definition.main();
|
||||
} catch (err) {
|
||||
console.error('The content script crashed on startup!\n\n', err);
|
||||
}
|
||||
})();
|
||||
@@ -1,6 +1,21 @@
|
||||
import { defineWorkspace } from 'vitest/config';
|
||||
import fs from 'fs-extra';
|
||||
import pc from 'picocolors';
|
||||
import { Plugin } from 'vite';
|
||||
|
||||
const seed = Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||
console.info('Test seed: ' + pc.cyan(seed));
|
||||
|
||||
// config.define doesn't work with workspaces, so we have to set it inisde a plugin
|
||||
const testSeed = (): Plugin => ({
|
||||
name: 'test-seed',
|
||||
config(config) {
|
||||
config.define ??= {};
|
||||
config.define.__TEST_SEED__ = JSON.stringify(seed);
|
||||
},
|
||||
});
|
||||
|
||||
// Clear e2e test projects
|
||||
await fs.rm('e2e/project', { recursive: true, force: true });
|
||||
|
||||
export default defineWorkspace([
|
||||
@@ -11,6 +26,7 @@ export default defineWorkspace([
|
||||
mockReset: true,
|
||||
restoreMocks: true,
|
||||
},
|
||||
plugins: [testSeed()],
|
||||
},
|
||||
{
|
||||
test: {
|
||||
@@ -18,5 +34,6 @@ export default defineWorkspace([
|
||||
dir: 'e2e',
|
||||
singleThread: true,
|
||||
},
|
||||
plugins: [testSeed()],
|
||||
},
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user