Compare commits
653 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 95da4fb054 | |||
| a917f32a0e | |||
| 6ca376749c | |||
| 792e3032b9 | |||
| 38a5a2c440 | |||
| 21106ce188 | |||
| ec7263e7de | |||
| e7c3fc2a5e | |||
| f808cb7526 | |||
| c4795e7479 | |||
| b44a96ab81 | |||
| 3bfdc5c755 | |||
| acb75543ff | |||
| 25b45b9c09 | |||
| efd5c40043 | |||
| e47519fb76 | |||
| 171e32c272 | |||
| 54e5ed0bb4 | |||
| a94d673f31 | |||
| b7f7222c26 | |||
| aa5a9d4fa9 | |||
| 77da2d6ade | |||
| 3119c04e10 | |||
| c09994263e | |||
| 2a4fa9ae5c | |||
| c833d3b15b | |||
| d6e095bfce | |||
| 825160a3cc | |||
| e29ca33f82 | |||
| ca5a52c6f1 | |||
| 982c69f8ae | |||
| 6fc7d9254a | |||
| 2e0e104d48 | |||
| 02430fd7b8 | |||
| 2e6d35d193 | |||
| 7cf412ac1f | |||
| 9398cbb596 | |||
| ca9b96142e | |||
| db57c8e079 | |||
| 7e37e11caf | |||
| bb90029c54 | |||
| d3b14afbce | |||
| ba2197a609 | |||
| 4c558d3eb6 | |||
| dcd175444a | |||
| 16fc58404a | |||
| 28c7e7d9ed | |||
| 5a3b42a4b0 | |||
| c9c5d721ec | |||
| 1556b9ad73 | |||
| ca15b1f321 | |||
| 448436cb08 | |||
| c9ddc3d0f2 | |||
| 6b72f883b6 | |||
| cd3446bd7f | |||
| 829b371bba | |||
| 36b399dd49 | |||
| 08f32aa685 | |||
| 05db264e02 | |||
| de5a01834c | |||
| 1d260dbacb | |||
| 53c63b4556 | |||
| 816878bc15 | |||
| e087a0d6ef | |||
| 56b71491a6 | |||
| 121b521157 | |||
| 16c2e3218b | |||
| d9cba55d34 | |||
| 832cd5d640 | |||
| bb95e08d71 | |||
| 35269dad9e | |||
| 64d1498460 | |||
| 0d540a6df9 | |||
| b47c150daa | |||
| 38345481df | |||
| da92449e0f | |||
| 23b3980645 | |||
| 81b4b629c9 | |||
| 4f7e9cb050 | |||
| b226d9b947 | |||
| aabba41e86 | |||
| f19e6908b0 | |||
| 5f651cc3fe | |||
| 8a6acbaaf2 | |||
| 13ab8e4d01 | |||
| bd039dcbab | |||
| 9cd4e8342b | |||
| c9633cefec | |||
| 1dd1370ed7 | |||
| 9ff90d0acf | |||
| b754435589 | |||
| 195d2cceaf | |||
| 420d78b638 | |||
| b35e499355 | |||
| 41c3d6d015 | |||
| 64610bad41 | |||
| 45b7d7c67f | |||
| 0bb2746869 | |||
| f9b0aa45f8 | |||
| 6f6ce33bbd | |||
| e1c6020997 | |||
| 012bd7e67f | |||
| 35778f70cb | |||
| cb4f9aa752 | |||
| f80fb42483 | |||
| 02c79ca244 | |||
| 50e8c86ce9 | |||
| 6fbfc15be7 | |||
| c793d3cf6b | |||
| b5f4d8cc1b | |||
| 77e6d1f5a4 | |||
| 3f149ac4fa | |||
| c0dbffaa82 | |||
| 707f03a3eb | |||
| 8372462041 | |||
| 3fbbe2ccde | |||
| e7880fcde0 | |||
| ee5e76e4b1 | |||
| 1ae8d6bc46 | |||
| de7dd09f60 | |||
| 1649785826 | |||
| 91fc41cb15 | |||
| 9c80ad832b | |||
| 86b7162d99 | |||
| b8f8acd33d | |||
| d8905d23a4 | |||
| 5d2d124e5a | |||
| 4fda203def | |||
| 905bfc7a38 | |||
| 7c6973f0e6 | |||
| 969e39c541 | |||
| b256939cc4 | |||
| c46137644b | |||
| 1bd7ef56c4 | |||
| c258b99cec | |||
| d3abb32cb4 | |||
| 223ede9ff9 | |||
| 9d31a4eba1 | |||
| ea0e40c269 | |||
| bc24ea45a4 | |||
| 86d0c9b397 | |||
| c275ccf0da | |||
| 447a48f306 | |||
| 70b5cbc8db | |||
| 3459de0375 | |||
| c6817479bd | |||
| f02aae21f6 | |||
| 87a62a1791 | |||
| a0d1643021 | |||
| 856a946fa7 | |||
| 9d97164c20 | |||
| 6d526e7043 | |||
| 946072fc5a | |||
| 6b8dfdf02d | |||
| ba85fdf470 | |||
| 295f8601e3 | |||
| a38ccd8624 | |||
| b2dcec2f8a | |||
| e8355029d9 | |||
| 2b374b98d2 | |||
| d14d76a0c8 | |||
| d0f1c7065c | |||
| 3edb66d76d | |||
| bba8cdace1 | |||
| 22f82a67ee | |||
| b32c60ca73 | |||
| c93620cba5 | |||
| dd05ed4d4c | |||
| 21636defda | |||
| bb022646e3 | |||
| 37ad2c7ceb | |||
| b992631e00 | |||
| 35a5cc6717 | |||
| 310b56fe5d | |||
| 148234382e | |||
| c1a97b77b0 | |||
| 557ccb4854 | |||
| 83e7e8aab9 | |||
| 7bf490c59d | |||
| 9670babdbb | |||
| 42dfc2d5cf | |||
| 1527f0e275 | |||
| 66bd957ee0 | |||
| 72e3c5baa2 | |||
| 9318346ab3 | |||
| 1d6aceab0a | |||
| c1b5a9d270 | |||
| f0b2a12724 | |||
| c45677ed48 | |||
| 7a3f075e21 | |||
| 1e12ce2a0d | |||
| eca30296a7 | |||
| 41fa320815 | |||
| 873c24fa50 | |||
| d92db5ba8f | |||
| 1a6fe33f97 | |||
| 2db4ea77a9 | |||
| ba5349ccaf | |||
| addb689de9 | |||
| 2af8d28b82 | |||
| ddbc66813e | |||
| 0c8fc013a8 | |||
| 26e30aaae9 | |||
| 6557dfe430 | |||
| 9cee9eefb4 | |||
| f9cd7aaf05 | |||
| 9049804413 | |||
| d027a496f5 | |||
| dd7abddb7c | |||
| 98d2792e02 | |||
| 39b76d3ec0 | |||
| bc29bfd434 | |||
| 5e60f37ec5 | |||
| 6f901b32eb | |||
| 927aca612e | |||
| 2dcdae91eb | |||
| eb758bdeb8 | |||
| 5acf51af9d | |||
| f9718a1b71 | |||
| cf7f23d4c3 | |||
| 62ecb6f41e | |||
| fc45c37f22 | |||
| 68874e607e | |||
| 35865adcb2 | |||
| 3b63a51475 | |||
| d32e59e38f | |||
| dab8efa140 | |||
| c298d8552b | |||
| 87267347f3 | |||
| 326e84b667 | |||
| 4b005b450e | |||
| 3bf40fc4ed | |||
| 9ace395e11 | |||
| 64f42064ae | |||
| a525d786f1 | |||
| 948ee810da | |||
| 8687b2980e | |||
| 6f0814bc97 | |||
| 148c0da51f | |||
| ba09f3e0f1 | |||
| 54e24652da | |||
| d6516142d2 | |||
| 460667e3c0 | |||
| 6ed61d0766 | |||
| 971daf2784 | |||
| 89d8ef9a50 | |||
| a329e24818 | |||
| 249cf63da8 | |||
| 70a496108a | |||
| 214889e052 | |||
| b6ab7a9567 | |||
| 1ceb9e2e80 | |||
| 15ea81514f | |||
| 21d4e6dede | |||
| 5343430c0f | |||
| 8c834835c4 | |||
| 9a2cc18f64 | |||
| 8683bd4b77 | |||
| dca52864bb | |||
| 0c5643f648 | |||
| b7cdf1597c | |||
| 2770974261 | |||
| ab6a34afb6 | |||
| b0eb7da9b4 | |||
| 4f649fc872 | |||
| 1af64aa02f | |||
| c35acff259 | |||
| 995d4f8080 | |||
| b7e2aa3d8f | |||
| e347f4529f | |||
| 3d9683418d | |||
| bdce363eed | |||
| e7775f291a | |||
| 59f6a28c65 | |||
| f7534464bc | |||
| 01445fb1da | |||
| 1360eb7c01 | |||
| c99a281169 | |||
| 76e63e24c0 | |||
| f5ba1173da | |||
| 12079e6ffc | |||
| ff6740d429 | |||
| e6657cb533 | |||
| 2ad099bf7e | |||
| 6421ab34a4 | |||
| 8919c4015a | |||
| 712f2ed4ac | |||
| d1b9e5ded6 | |||
| 86182044d1 | |||
| e1f181356c | |||
| 5f241cf407 | |||
| b4a2cf4cb2 | |||
| aea137a3a1 | |||
| ad0e8e4144 | |||
| 7fd5752ef1 | |||
| 1a8b7ff635 | |||
| fe3ca0fcbd | |||
| f49ee9f005 | |||
| 961440c0ac | |||
| 9b562b0ca8 | |||
| 7b2563b2dc | |||
| 63f507ecf5 | |||
| 0a88955394 | |||
| 291d25b675 | |||
| 6f902cc598 | |||
| fde488ac82 | |||
| 0873c24ad8 | |||
| 7c02497148 | |||
| 25441a3b97 | |||
| f1cf866fe1 | |||
| d92a126489 | |||
| bcb93afe4e | |||
| 21cf3642cb | |||
| 2ef28ec0c4 | |||
| 59094c9af3 | |||
| ba41691296 | |||
| 8786aa91b3 | |||
| 0623d10653 | |||
| 85174d4994 | |||
| 4e2dd5d618 | |||
| 1a39a0d153 | |||
| f922648dd7 | |||
| e6142e3608 | |||
| ed73451fd0 | |||
| 8a25a2f60f | |||
| 397e9a8b45 | |||
| 7cef7680de | |||
| 0c57375758 | |||
| 2c6af84165 | |||
| 58e1371701 | |||
| 0d8e7463b8 | |||
| 77eeacaf0f | |||
| e80c134150 | |||
| 03c8ab4d6f | |||
| df13b9705c | |||
| 762ba0080d | |||
| 317b1b6dcc | |||
| 93175a6477 | |||
| 60d6707b11 | |||
| 97cbda3dab | |||
| 1611c1dba6 | |||
| b59252284a | |||
| 742b99657a | |||
| e2997a43e0 | |||
| 21dead60fd | |||
| 3d1bc0a12c | |||
| 4b24bee1fb | |||
| 82ed821eb5 | |||
| 60625280c2 | |||
| 083792b5f0 | |||
| 3da3e07bda | |||
| 64d61eb3f1 | |||
| aa6009e476 | |||
| d2cb8f9416 | |||
| 046a4809d3 | |||
| 207b750d4e | |||
| 077fa9939e | |||
| 44d715a29e | |||
| 3ab9fe4ae3 | |||
| 91b28c2cb9 | |||
| d1b523061f | |||
| c69ea3967f | |||
| 41527a2200 | |||
| 281f28192d | |||
| 67ffa44f9c | |||
| 421c0e412d | |||
| d21ee08eb6 | |||
| 7fa150dfae | |||
| 7ba52b18fd | |||
| 66e5079b84 | |||
| f1e8084be8 | |||
| 609ae2ae37 | |||
| c81dfff37c | |||
| 9c27820add | |||
| 22f8e10918 | |||
| 96d41f8b7c | |||
| c4ce44e001 | |||
| 6641ffaeed | |||
| 450dc0975f | |||
| d343555f41 | |||
| 7993908c6c | |||
| 94e1dbce24 | |||
| d14215946a | |||
| 345406f02a | |||
| 6b689bab23 | |||
| fcdf0dcbe9 | |||
| bd35acdae1 | |||
| 21c35ee593 | |||
| 0f09cbee0e | |||
| 183bb02e29 | |||
| 0bf4ea0897 | |||
| cd4d00e23e | |||
| 5de18e5371 | |||
| af823418ee | |||
| 0b8d101c33 | |||
| b587849126 | |||
| 2a35ce0a1e | |||
| c390b70f76 | |||
| c9fd739128 | |||
| 0806c06042 | |||
| 5f74a544a4 | |||
| 121778894c | |||
| 102c72a03c | |||
| a0507866db | |||
| f3874da780 | |||
| cc5d24ec92 | |||
| 08760015da | |||
| 5368371020 | |||
| 08d62a437a | |||
| 37e2348d79 | |||
| 6a30dc46fa | |||
| b75c553e51 | |||
| a56face03d | |||
| 19756c61ce | |||
| 5f54b4de17 | |||
| ea8935c47c | |||
| e37f73880d | |||
| 2e24b9e18a | |||
| f8a0fb37a2 | |||
| dd26b99027 | |||
| d9e9b43f8d | |||
| d580083727 | |||
| 5a70d9e57d | |||
| 1b1af245bd | |||
| 874a531a62 | |||
| a3d409f250 | |||
| b6758ca9fa | |||
| 2672308946 | |||
| 7ac171ed3b | |||
| 6a93f20bb3 | |||
| 921af6a5a1 | |||
| 21ebeacd20 | |||
| 527600031f | |||
| 91a804c492 | |||
| 54f3785063 | |||
| 44e4bc5295 | |||
| f464d7d33f | |||
| d0672739f0 | |||
| 44464f914f | |||
| 8940c41bdb | |||
| 446f265b6c | |||
| 7a465684c0 | |||
| 0369316463 | |||
| 8b74291d18 | |||
| 739b738100 | |||
| 95442dd39b | |||
| 4f82645c08 | |||
| d200c376f8 | |||
| b58fb02016 | |||
| e1bab6c746 | |||
| d10c22fbd9 | |||
| da1f7f3ea5 | |||
| 2659272f8a | |||
| 868fd27804 | |||
| d9fdcb5b33 | |||
| 580793158f | |||
| f58d69dc5f | |||
| c9028dd335 | |||
| 6342f59c5e | |||
| 2d91898452 | |||
| 4acefd601c | |||
| 9a2e71b481 | |||
| 6e578f3156 | |||
| 474de83e28 | |||
| 65fcfc0064 | |||
| 2d4983e88b | |||
| 4150e42c05 | |||
| 8d7150653a | |||
| 61e57b7b7c | |||
| 83add72312 | |||
| 3c723d2c92 | |||
| 1eb35c7207 | |||
| 725ecf7c64 | |||
| 3847092df4 | |||
| 19b11c236f | |||
| 4b2012c489 | |||
| 0591050f31 | |||
| b15dc7fc11 | |||
| 89d15babc2 | |||
| c616125689 | |||
| e97071b7d7 | |||
| 1fa049c5a7 | |||
| b48cee9715 | |||
| 24e69fe1bf | |||
| 41e154992a | |||
| 55707932a9 | |||
| 08115a40ae | |||
| 96be879918 | |||
| 1fc4ada66d | |||
| d27f299641 | |||
| 7183114370 | |||
| 7bd940450f | |||
| cf5a7d1411 | |||
| 5482b2f934 | |||
| c4a6ff928d | |||
| 455e7f3765 | |||
| 25b6ab92f5 | |||
| 50f6289ac5 | |||
| a0e1b4741e | |||
| ab672e9dbd | |||
| 7b3ea52e02 | |||
| e0929a68ba | |||
| 4c430725a5 | |||
| 7d745f6ec7 | |||
| 10091d7769 | |||
| 2e142b3038 | |||
| 361bb2dcc8 | |||
| 26fca5c0a2 | |||
| 30a61f4384 | |||
| 8e0a189d77 | |||
| cae44c9732 | |||
| fba8f0d017 | |||
| 3f260ee777 | |||
| 1f6a931b02 | |||
| 07891d028b | |||
| 1f448d1bf0 | |||
| 7722986537 | |||
| 4b73168618 | |||
| 16cebc538f | |||
| e4aaba98ab | |||
| c190d8c44c | |||
| 5801f96f79 | |||
| 92039b855e | |||
| f145e00cc9 | |||
| 87e8df90f5 | |||
| 419fab8193 | |||
| 71743b3bf3 | |||
| 8e3169eb1e | |||
| 4ec904ea3c | |||
| b284305605 | |||
| 234bd39a11 | |||
| b5acea106d | |||
| 33ed04ff6b | |||
| f51bbc0a68 | |||
| 6ee5b22e6c | |||
| b4de93d2e6 | |||
| 2570debc5c | |||
| e6a2c15d3c | |||
| dbd84c8815 | |||
| fe966b6ca8 | |||
| e2350fecc6 | |||
| b33b6631a5 | |||
| 5f15f9ccec | |||
| beafa6afc4 | |||
| 46a5036482 | |||
| eaa15e0d57 | |||
| 2c22948d2e | |||
| 3e701e22c9 | |||
| 6ee1fa17a1 | |||
| b76bf2831b | |||
| ac7cbfce1f | |||
| 06f027b889 | |||
| e996686082 | |||
| 1bb3474a91 | |||
| 79426671cf | |||
| 89b34403ee | |||
| 749bc38234 | |||
| e086374828 | |||
| 2c3721e5f4 | |||
| c2946aa1eb | |||
| 4dd6320eae | |||
| a8285fff78 | |||
| d89be2544d | |||
| f234c85357 | |||
| 93bfee0578 | |||
| 2c7922ca4c | |||
| 72ae812c42 | |||
| 2a0842bb62 | |||
| b301ee324e | |||
| 4ef3eeaca1 | |||
| e803bfecb5 | |||
| 0dcf540393 | |||
| 062e25d834 | |||
| b58a15e783 | |||
| 5f5f1d90f8 | |||
| 83e62a1d63 | |||
| b428a62a90 | |||
| 1fe4eedc9a | |||
| 39d3192904 | |||
| de30509d94 | |||
| d5ae2332ca | |||
| f8b367dd7b | |||
| 6bcb71baa0 | |||
| 54bdf54839 | |||
| 83f457f40f | |||
| f9df2d0809 | |||
| 2b079bb7f6 | |||
| 0194448af4 | |||
| b409def211 | |||
| 62bbef3487 | |||
| 87013bc616 | |||
| 4599bf2aa0 | |||
| d629acf518 | |||
| 2bde9175b7 | |||
| 8d5f4f5201 | |||
| d05f126eb3 | |||
| e164bd51fe | |||
| d66293c56e | |||
| 6aa827438c | |||
| 6ee8a43677 | |||
| d54d6111e6 | |||
| aefc8d3167 | |||
| 7ccff533ff | |||
| edfa075030 | |||
| ef140dc7f8 | |||
| 39467d10f3 | |||
| 97f0938c99 | |||
| 18741eae8e | |||
| 3f3ef37c96 | |||
| 323045a7c3 | |||
| 9b34180467 | |||
| 7f15305037 | |||
| 35cc1c94bf | |||
| 547c1850ac | |||
| 798f02f626 | |||
| d5948abfe5 | |||
| ec01f048ed | |||
| 8751c55063 | |||
| dfe424f35d | |||
| 25677ba445 | |||
| 390f65cf39 | |||
| 310f994ccb | |||
| 23e4295e08 | |||
| abdef08aeb | |||
| 38d4f9c879 | |||
| 709b61a174 | |||
| 4184b0529d | |||
| d3ff4c6afe | |||
| 16de4da27f | |||
| 936d83bc08 | |||
| aea866c9cd | |||
| 3107d27184 | |||
| 3a336eba89 | |||
| 044a24fd6a | |||
| 5b269f4369 | |||
| 7d55faff20 | |||
| 386f8db5db | |||
| 94a1097df5 | |||
| be95a778ab | |||
| d3b1536f39 | |||
| 9f2b989a2f | |||
| e621aa8f8c | |||
| 488d7885ca | |||
| 3a9fd3909f | |||
| 2c70246af5 | |||
| a82a66ec37 | |||
| 19c0948d95 | |||
| 04e5400a46 | |||
| 33c1c171db | |||
| 9cc464f48b | |||
| 58a84ec253 | |||
| 609223566c | |||
| 0aebb67b73 |
@@ -0,0 +1,8 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
informational: true
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
@@ -1,2 +1,5 @@
|
||||
* text=auto eol=lf
|
||||
pnpm-lock.yaml linguist-generated
|
||||
package-lock.json linguist-generated
|
||||
bun.lockb linguist-generated
|
||||
yarn.lock linguist-generated
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report an issue with WXT
|
||||
title: ''
|
||||
labels: triage
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
### Describe the bug
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
### To Reproduce
|
||||
|
||||
Share minimal reproduction. Examples of a minimal reproduction:
|
||||
|
||||
- PR with a failing test case
|
||||
- ZIP file containing a minimal WXT project (be sure to include your lockfile!!)
|
||||
|
||||
> **_⚠️ If you don't upload a minimal reproduction, your issue will be closed until a reproduction is added._**
|
||||
>
|
||||
> Why? https://antfu.me/posts/why-reproductions-are-required
|
||||
|
||||
Steps to reproduce the bug using the reproduction:
|
||||
|
||||
1. Install dependencies: `pnpm i`
|
||||
2. Start dev mode: `pnpm dev`
|
||||
3. Click this...
|
||||
4. Do that...
|
||||
5. Etc...
|
||||
|
||||
### Expected behavior
|
||||
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
### Screenshots
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
### Environment
|
||||
|
||||
<!--- Run `npx envinfo --system --browsers --binaries --npmPackages wxt,vite` and paste the output below -->
|
||||
|
||||
```
|
||||
Paste output here
|
||||
```
|
||||
|
||||
### Additional context
|
||||
|
||||
Add any other context about the problem here, otherwise you can delete this section.
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for WXT
|
||||
title: ''
|
||||
labels: feature
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
### Feature Request
|
||||
|
||||
Please describe your feature, be clear and concise. If you have a proposal for required type or API changes, list them here.
|
||||
|
||||
#### Is your feature request related to a bug?
|
||||
|
||||
If so, add a link here. If not, write "N/A"
|
||||
|
||||
### What are the alternatives?
|
||||
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
### Additional context
|
||||
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,17 @@
|
||||
name: Basic Setup
|
||||
description: Install PNPM, Node, and dependencies
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: pnpm
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
run: pnpm install
|
||||
@@ -0,0 +1,15 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'monthly'
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'monthly'
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Publish Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Docker Image Tag
|
||||
required: true
|
||||
default: latest
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: https://${{ secrets.DOCKER_REGISTRY_HOSTNAME }}
|
||||
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
|
||||
- run: pnpm docs:build
|
||||
- run: docker build docs/.vitepress -t ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
|
||||
- run: docker push ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
|
||||
- run: curl -X POST -i ${{ secrets.UPDATE_DOCS_WEBHOOK }}
|
||||
@@ -5,33 +5,18 @@ on:
|
||||
jobs:
|
||||
validate:
|
||||
uses: './.github/workflows/validate.yml'
|
||||
secrets: inherit
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- validate
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pnpm install --ignore-scripts
|
||||
pnpm build
|
||||
pnpm install
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Bump and Tag
|
||||
run: |
|
||||
@@ -40,11 +25,14 @@ jobs:
|
||||
pnpx changelogen@latest --release
|
||||
git push
|
||||
git push --tags
|
||||
working-directory: packages/wxt
|
||||
|
||||
- name: NPM
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
|
||||
pnpm publish
|
||||
working-directory: packages/wxt
|
||||
|
||||
- name: GitHub Release
|
||||
run: pnpx changelogen@latest gh release --token ${{ secrets.GITHUB_TOKEN }}
|
||||
working-directory: packages/wxt
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Sync Releases
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- CHANGELOG.md
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: pnpm
|
||||
- run: pnpm sync-releases all --token ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -7,89 +7,62 @@ on:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
wxt:
|
||||
name: WXT
|
||||
runs-on: ubuntu-20.04
|
||||
checks:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pnpm install --ignore-scripts
|
||||
pnpm build
|
||||
pnpm install
|
||||
|
||||
- name: Formatting
|
||||
run: pnpm format:check
|
||||
|
||||
- name: Type Check
|
||||
run: pnpm compile
|
||||
|
||||
- name: Build Demo
|
||||
run: |
|
||||
pnpm build:all:chrome-mv2
|
||||
pnpm build:all:chrome-mv3
|
||||
pnpm build:all:firefox-mv2
|
||||
pnpm build:all:firefox-mv3
|
||||
working-directory: demo
|
||||
|
||||
- name: Tests
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm check
|
||||
build-demo:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm build:all
|
||||
working-directory: packages/wxt-demo
|
||||
- run: pnpm wxt zip
|
||||
working-directory: packages/wxt-demo
|
||||
tests:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- name: pnpm test:coverage
|
||||
run: pnpm test:coverage --reporter=default --reporter=hanging-process
|
||||
|
||||
project-templates:
|
||||
name: Project Templates
|
||||
runs-on: ubuntu-20.04
|
||||
- uses: codecov/codecov-action@v4
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
windows-tests:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Validate Vanilla
|
||||
working-directory: templates/vanilla
|
||||
run: |
|
||||
npm i
|
||||
npm run build
|
||||
npm run compile
|
||||
|
||||
- name: Validate Vue
|
||||
working-directory: templates/vue
|
||||
run: |
|
||||
npm i
|
||||
npm run build
|
||||
npm run compile
|
||||
|
||||
- name: Validate React
|
||||
working-directory: templates/react
|
||||
run: |
|
||||
npm i
|
||||
npm run build
|
||||
npm run compile
|
||||
|
||||
- name: Validate Svelte
|
||||
working-directory: templates/svelte
|
||||
run: |
|
||||
npm i
|
||||
npm run build
|
||||
npm run check
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm test run
|
||||
template:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
template:
|
||||
- react
|
||||
- solid
|
||||
- svelte
|
||||
- vanilla
|
||||
- vue
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm pack
|
||||
working-directory: packages/wxt
|
||||
- run: npm i
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: npm i -D ../../packages/wxt/wxt-*.tgz
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: pnpm compile
|
||||
if: matrix.template != 'svelte'
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: pnpm check
|
||||
if: matrix.template == 'svelte'
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: pnpm build
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
/docs/.vitepress/cache
|
||||
coverage
|
||||
dist
|
||||
e2e/project
|
||||
node_modules
|
||||
TODOs.md
|
||||
web-ext.config.js
|
||||
@@ -18,3 +17,6 @@ web-ext.config.ts
|
||||
templates/*/pnpm-lock.yaml
|
||||
templates/*/yarn.lock
|
||||
templates/*/package-lock.json
|
||||
docs/api
|
||||
stats.html
|
||||
.tool-versions
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.output
|
||||
coverage
|
||||
dist
|
||||
e2e/project
|
||||
.wxt
|
||||
docs/.vitepress/cache
|
||||
pnpm-lock.yaml
|
||||
CHANGELOG.md
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
singleQuote: true
|
||||
trailingComma: all
|
||||
endOfLine: lf
|
||||
|
||||
@@ -1,356 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## v0.2.5
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.2.4...v0.2.5)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Auto-import from subdirectories ([547fee0](https://github.com/aklinker1/wxt/commit/547fee0))
|
||||
- Include background script in dev mode if user doesn't define one ([ca20a21](https://github.com/aklinker1/wxt/commit/ca20a21))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Don't crash when generating types in dev mode ([d8c1903](https://github.com/aklinker1/wxt/commit/d8c1903))
|
||||
- Properly load entrypoints that reference `import.meta` ([54b18cc](https://github.com/aklinker1/wxt/commit/54b18cc))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Update templates to wxt@0.2 ([9d00eb2](https://github.com/aklinker1/wxt/commit/9d00eb2))
|
||||
|
||||
### 🤖 CI
|
||||
|
||||
- Validate project templates ([9ac756f](https://github.com/aklinker1/wxt/commit/9ac756f))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
|
||||
## v0.2.4
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.2.3...v0.2.4)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add `wxt zip` command ([#47](https://github.com/aklinker1/wxt/pull/47))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron
|
||||
|
||||
## v0.2.3
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.2.2...v0.2.3)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Correctly lookup open port ([#45](https://github.com/aklinker1/wxt/pull/45))
|
||||
- Read boolean maniest options from meta tags correctly ([495c5c8](https://github.com/aklinker1/wxt/commit/495c5c8))
|
||||
- Some fields cannot be overridden from `config.manifest` ([#46](https://github.com/aklinker1/wxt/pull/46))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
|
||||
## v0.2.2
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.2.1...v0.2.2)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Register content scripts correctly in dev mode ([2fb5a54](https://github.com/aklinker1/wxt/commit/2fb5a54))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
|
||||
## v0.2.1
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.2.0...v0.2.1)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Support all content script options ([6f5bf89](https://github.com/aklinker1/wxt/commit/6f5bf89))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Remove HMR log ([90fa6bf](https://github.com/aklinker1/wxt/commit/90fa6bf))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
|
||||
## v0.2.0
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.1.6...v0.2.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- ⚠️ Rename `defineBackgroundScript` to `defineBackground` ([5b48ae9](https://github.com/aklinker1/wxt/commit/5b48ae9))
|
||||
- Recongize unnamed content scripts (`content.ts` and `content/index.ts`) ([3db5cec](https://github.com/aklinker1/wxt/commit/3db5cec))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Update templates ([f28a29e](https://github.com/aklinker1/wxt/commit/f28a29e))
|
||||
- Add docs for each type of entrypoint ([77cbfc1](https://github.com/aklinker1/wxt/commit/77cbfc1))
|
||||
- Add inline JSDoc for public types ([375a2a6](https://github.com/aklinker1/wxt/commit/375a2a6))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Run `wxt prepare` on `postinstall` ([c1ea9ba](https://github.com/aklinker1/wxt/commit/c1ea9ba))
|
||||
- Don't format lockfile ([5c7e041](https://github.com/aklinker1/wxt/commit/5c7e041))
|
||||
|
||||
#### ⚠️ Breaking Changes
|
||||
|
||||
- ⚠️ Rename `defineBackgroundScript` to `defineBackground` ([5b48ae9](https://github.com/aklinker1/wxt/commit/5b48ae9))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
|
||||
## v0.1.6
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.1.5...v0.1.6)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Resolve tsconfig paths in vite ([ea92a27](https://github.com/aklinker1/wxt/commit/ea92a27))
|
||||
- Add logs when a hot reload happens ([977246f](https://github.com/aklinker1/wxt/commit/977246f))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- React and Vue starter templates ([#33](https://github.com/aklinker1/wxt/pull/33))
|
||||
- Svelte template ([#34](https://github.com/aklinker1/wxt/pull/34))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
- Aaron
|
||||
|
||||
## v0.1.5
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.1.4...v0.1.5)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Include `vite/client` types ([371be99](https://github.com/aklinker1/wxt/commit/371be99))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
|
||||
## v0.1.4
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.1.3...v0.1.4)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Fix regression where manifest was not listed first in build summary ([fa2b656](https://github.com/aklinker1/wxt/commit/fa2b656))
|
||||
- Fix config hook implementations for vite plugins ([49965e7](https://github.com/aklinker1/wxt/commit/49965e7))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Update CLI screenshot ([0a26673](https://github.com/aklinker1/wxt/commit/0a26673))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Update prettier ignore ([68611ae](https://github.com/aklinker1/wxt/commit/68611ae))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
|
||||
## v0.1.3
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.1.2...v0.1.3)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add tsconfig path aliases ([#32](https://github.com/aklinker1/wxt/pull/32))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Merge `manifest` option from both inline and user config ([05ca998](https://github.com/aklinker1/wxt/commit/05ca998))
|
||||
- Cleanup build summary with sourcemaps ([ac0b28e](https://github.com/aklinker1/wxt/commit/ac0b28e))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Create documentation site ([#31](https://github.com/aklinker1/wxt/pull/31))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Upgrade to pnpm 8 ([0ce7c9d](https://github.com/aklinker1/wxt/commit/0ce7c9d))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
|
||||
## v0.1.2
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.1.1...v0.1.2)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Accept a function for `config.manifest` ([ee49837](https://github.com/aklinker1/wxt/commit/ee49837))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Add missing types for `webextension-polyfill` and the `manifest` option ([636aa48](https://github.com/aklinker1/wxt/commit/636aa48))
|
||||
- Only add imports to JS files ([b29c3c6](https://github.com/aklinker1/wxt/commit/b29c3c6))
|
||||
- Generate valid type for `EntrypointPath` when there are no entrypoints ([6e7184d](https://github.com/aklinker1/wxt/commit/6e7184d))
|
||||
|
||||
### 🌊 Types
|
||||
|
||||
- Change `config.vite` to `UserConfig` ([ef6001e](https://github.com/aklinker1/wxt/commit/ef6001e))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
|
||||
## v0.1.1
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.1.0...v0.1.1)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Allow dashes in entrypoint names ([2e51e73](https://github.com/aklinker1/wxt/commit/2e51e73))
|
||||
- Unable to read entrypoint options ([#28](https://github.com/aklinker1/wxt/pull/28))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
|
||||
## v0.1.0
|
||||
|
||||
Initial release of WXT. Full support for production builds and initial toolkit for development:
|
||||
|
||||
- HMR support when HTML page dependencies change
|
||||
- Reload extension when background changes
|
||||
- Reload HTML pages when saving them directly
|
||||
- Re-register and reload tabs when content scripts change
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.0.2...v0.1.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Content scripts reloading ([#25](https://github.com/aklinker1/wxt/pull/25))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Update feature list ([0255028](https://github.com/aklinker1/wxt/commit/0255028))
|
||||
|
||||
### 🤖 CI
|
||||
|
||||
- Create github release ([b7c078f](https://github.com/aklinker1/wxt/commit/b7c078f))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
|
||||
## 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
|
||||
|
||||
- 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
|
||||
|
||||
## v0.0.1
|
||||
|
||||
[compare changes](https://github.com/aklinker1/wxt/compare/v0.0.0...v0.0.1)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add logger to config ([232ff7a](https://github.com/aklinker1/wxt/commit/232ff7a))
|
||||
- Export and bootstrap the `/client` package ([5b07c95](https://github.com/aklinker1/wxt/commit/5b07c95))
|
||||
- Resolve entrypoints based on filesystem ([a63f061](https://github.com/aklinker1/wxt/commit/a63f061))
|
||||
- Separate output directories for each browser/manifest version ([f09ffbb](https://github.com/aklinker1/wxt/commit/f09ffbb))
|
||||
- Build entrypoints and output `manfiest.json` ([1e7c738](https://github.com/aklinker1/wxt/commit/1e7c738))
|
||||
- Automatically add CSS files to content scripts ([047ce04](https://github.com/aklinker1/wxt/commit/047ce04))
|
||||
- Download and bundle remote URL imports ([523c7df](https://github.com/aklinker1/wxt/commit/523c7df))
|
||||
- Generate type declarations and config for project types and auto-imports ([21debad](https://github.com/aklinker1/wxt/commit/21debad))
|
||||
- Good looking console output ([e2cc995](https://github.com/aklinker1/wxt/commit/e2cc995))
|
||||
- Dev server working and a valid extension is built ([505e419](https://github.com/aklinker1/wxt/commit/505e419))
|
||||
- Virtualized content script entrypoint ([ca29537](https://github.com/aklinker1/wxt/commit/ca29537))
|
||||
- Provide custom, typed globals defined by Vite ([8c59a1c](https://github.com/aklinker1/wxt/commit/8c59a1c))
|
||||
- Copy public directory to outputs ([1a25f2b](https://github.com/aklinker1/wxt/commit/1a25f2b))
|
||||
- Support browser and chrome styles for mv2 popups ([7945c94](https://github.com/aklinker1/wxt/commit/7945c94))
|
||||
- Support browser and chrome styles for mv2 popups ([7abb577](https://github.com/aklinker1/wxt/commit/7abb577))
|
||||
- Support more CLI flags for `build` and `dev` ([#9](https://github.com/aklinker1/wxt/pull/9))
|
||||
- Add more supported browser types ([f114c5b](https://github.com/aklinker1/wxt/commit/f114c5b))
|
||||
- Open browser when starting dev server ([#11](https://github.com/aklinker1/wxt/pull/11))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Support `srcDir` config ([739d19f](https://github.com/aklinker1/wxt/commit/739d19f))
|
||||
- Root path customization now works ([4faa3b3](https://github.com/aklinker1/wxt/commit/4faa3b3))
|
||||
- Print durations as ms/s based on total time ([3e37de9](https://github.com/aklinker1/wxt/commit/3e37de9))
|
||||
- Don't print error twice when background crashes ([407627c](https://github.com/aklinker1/wxt/commit/407627c))
|
||||
- Load package.json from root not cwd ([3ca16ee](https://github.com/aklinker1/wxt/commit/3ca16ee))
|
||||
- Only allow a single entrypoint with a given name ([8eb4e86](https://github.com/aklinker1/wxt/commit/8eb4e86))
|
||||
- Respect the mv2 popup type ([0f37ceb](https://github.com/aklinker1/wxt/commit/0f37ceb))
|
||||
- Respect background type and persistent manifest options ([573ef80](https://github.com/aklinker1/wxt/commit/573ef80))
|
||||
- Make content script array orders consistent ([f380378](https://github.com/aklinker1/wxt/commit/f380378))
|
||||
- Firefox manifest warnings in dev mode ([50bb845](https://github.com/aklinker1/wxt/commit/50bb845))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Update README ([785ea54](https://github.com/aklinker1/wxt/commit/785ea54))
|
||||
- Update README ([99ccadb](https://github.com/aklinker1/wxt/commit/99ccadb))
|
||||
- Update description ([07a262e](https://github.com/aklinker1/wxt/commit/07a262e))
|
||||
- Update README ([58a0ef4](https://github.com/aklinker1/wxt/commit/58a0ef4))
|
||||
- Update README ([23ed6f7](https://github.com/aklinker1/wxt/commit/23ed6f7))
|
||||
- Add initial release milestone link to README ([b400e54](https://github.com/aklinker1/wxt/commit/b400e54))
|
||||
- Fix typo in README ([5590c9d](https://github.com/aklinker1/wxt/commit/5590c9d))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Refactor cli files into their own directory ([e6c0d84](https://github.com/aklinker1/wxt/commit/e6c0d84))
|
||||
- Simplify `BuildOutput` type ([1f6c4a0](https://github.com/aklinker1/wxt/commit/1f6c4a0))
|
||||
- Move `.exvite` directory into `srcDir` instead of `root` ([53fb805](https://github.com/aklinker1/wxt/commit/53fb805))
|
||||
- Refactor CLI commands ([b8952b6](https://github.com/aklinker1/wxt/commit/b8952b6))
|
||||
- Improve build summary sorting ([ec57e8c](https://github.com/aklinker1/wxt/commit/ec57e8c))
|
||||
- Remove comments ([e3e9c0d](https://github.com/aklinker1/wxt/commit/e3e9c0d))
|
||||
- Refactor internal config creation ([7c634f4](https://github.com/aklinker1/wxt/commit/7c634f4))
|
||||
- Check virtual entrypoints feature in README ([70208f4](https://github.com/aklinker1/wxt/commit/70208f4))
|
||||
- Add E2E tests and convert to vitest workspace ([5813302](https://github.com/aklinker1/wxt/commit/5813302))
|
||||
- Rename package to wxt ([51a1072](https://github.com/aklinker1/wxt/commit/51a1072))
|
||||
- Fix header log's timestamp ([8ca5657](https://github.com/aklinker1/wxt/commit/8ca5657))
|
||||
- Fix demo global usage ([1ecfedd](https://github.com/aklinker1/wxt/commit/1ecfedd))
|
||||
- Refactor folder structure ([9ab3953](https://github.com/aklinker1/wxt/commit/9ab3953))
|
||||
- Fix release workflow ([2e94f2a](https://github.com/aklinker1/wxt/commit/2e94f2a))
|
||||
|
||||
### 🤖 CI
|
||||
|
||||
- Create validation workflow ([#12](https://github.com/aklinker1/wxt/pull/12))
|
||||
- Create release workflow ([#13](https://github.com/aklinker1/wxt/pull/13))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron Klinker
|
||||
@@ -0,0 +1,114 @@
|
||||
# Contributing
|
||||
|
||||
Everyone is welcome to contribute to WXT!
|
||||
|
||||
If you are changing the docs or fixing a bug, feel free to fork and open a PR.
|
||||
|
||||
If you want to add a new feature, please create an issue or discussion first so we can decide if the feature is inline with the vision for WXT.
|
||||
|
||||
## Conventional Commits
|
||||
|
||||
This project uses [Conventional Commits](https://www.conventionalcommits.org/en) to automate versioning. If you're a new contributor, don't worry about this. When you open a PR, a maintainer will change the PR's title so it's in the style of conventional commits, but that's all.
|
||||
|
||||
Maintainers, commits to the `main` branch (either directly or via PRs) must be valid conventional commits.
|
||||
|
||||
## Setup
|
||||
|
||||
WXT uses `pnpm`, so make sure you have it installed.
|
||||
|
||||
```sh
|
||||
corepack enable
|
||||
```
|
||||
|
||||
Then, simply run the install command:
|
||||
|
||||
```sh
|
||||
pnpm i
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Here are some helpful commands:
|
||||
|
||||
```sh
|
||||
# Build WXT package
|
||||
pnpm build
|
||||
```
|
||||
|
||||
```sh
|
||||
# Build WXT package, then build demo extension
|
||||
cd demo
|
||||
pnpm build
|
||||
```
|
||||
|
||||
```sh
|
||||
# Build WXT package, then start the demo extension in dev mode
|
||||
cd demo
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
```sh
|
||||
# Run unit and E2E tests
|
||||
pnpm test
|
||||
```
|
||||
|
||||
```sh
|
||||
# Start the docs website locally
|
||||
pnpm docs:dev
|
||||
```
|
||||
|
||||
## Updating Docs
|
||||
|
||||
Documentation is written with VitePress, and is located in the `docs/` directory.
|
||||
|
||||
The API reference is generated from JSDoc comments in the source code. If there's a typo or change you want to make in there, you'll need to update the source code instead of a file in the `docs/` directory.
|
||||
|
||||
## Testing
|
||||
|
||||
WXT has unit and E2E tests. When making a change or adding a feature, make sure to update the tests or add new ones, if they exist.
|
||||
|
||||
> If they don't exist, feel free to create them, but that's a lot for a one-time contributor. A maintainer might add them to your PR though.
|
||||
|
||||
To run tests for a specific file, add the filename at the end of the test command:
|
||||
|
||||
```sh
|
||||
pnpm test manifest-contents
|
||||
```
|
||||
|
||||
Unit and E2E tests are ran together via [Vitest workspaces](https://vitest.dev/guide/#workspaces-support).
|
||||
|
||||
If you want to manually test a change, you can modify the demo project for your test, but please don't leave those changes committed once you open a PR.
|
||||
|
||||
## Templates
|
||||
|
||||
Each directory inside `templates/` is it's own standalone project. Simply `cd` into the directory you're updating, install dependencies with `npm` (NOT `pnpm`), and run the relevant commands
|
||||
|
||||
```sh
|
||||
cd templates/vue
|
||||
npm i
|
||||
npm run dev
|
||||
npm run build
|
||||
```
|
||||
|
||||
Note that templates are hardcoded to a specific version of `wxt` from NPM, they do not use the local version. PR checks will test your changes against the templates, but if you want to manually do it, update the package.json dependency:
|
||||
|
||||
```diff
|
||||
"devDependencies": {
|
||||
"typescript": "^5.3.2",
|
||||
"vite-plugin-solid": "^2.7.0",
|
||||
- "wxt": "^0.8.0"
|
||||
+ "wxt": "../.."
|
||||
}
|
||||
```
|
||||
|
||||
Then run `npm i` again.
|
||||
|
||||
### Adding Templates
|
||||
|
||||
To add a template, copy the vanilla template and give it a new name.
|
||||
|
||||
```sh
|
||||
cp -r templates/vanilla templates/<new-template-name>
|
||||
```
|
||||
|
||||
That's it. Once your template is merged, it will be available inside `wxt init` immediately. You don't need to release a new version of WXT to release a new template.
|
||||
@@ -1,32 +0,0 @@
|
||||
<h1 align="center">WXT</h1>
|
||||
|
||||
<p align="center"><i>Next gen framework for developing web extensions.<br/>Powered by <a href="https://vitejs.dev/" target="_blank">Vite</a>. Inspired by <a href="https://nuxt.com/" target="_blank">Nuxt</a>.</i></p>
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- 🌐 Supports all browsers
|
||||
- ✅ Supports both MV2 and MV3
|
||||
- ⚡ Dev mode with HMR & auto-reload
|
||||
- 📂 File based entrypoints
|
||||
- 🚔 TypeScript
|
||||
- 🦾 Auto-imports
|
||||
- ⬇️ Download and bundle remote URL imports
|
||||
- 🎨 Frontend framework agnostic: works with Vue, React, Svelte, etc
|
||||
|
||||
### Todo
|
||||
|
||||
- 🖍️ Quickly bootstrap a new project
|
||||
- 📏 Bundle analysis
|
||||
- 🤖 Automated publishing
|
||||
|
||||
## Get Started
|
||||
|
||||
Checkout the [installation guide](https://wxt.dev/get-started/installation.html) to get started with WXT.
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/aklinker1/wxt/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=aklinker1/wxt" />
|
||||
</a>
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "WXT Demo",
|
||||
"version": "1.0.0",
|
||||
"description": "Demo extension for WXT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "pnpm -w build && wxt",
|
||||
"build": "pnpm -w build && wxt build",
|
||||
"build:all": "pnpm -w build && run-s -s build:all:*",
|
||||
"build:all:chrome-mv3": "wxt build",
|
||||
"build:all:chrome-mv2": "wxt build --mv2",
|
||||
"build:all:firefox-mv3": "wxt build -b firefox --mv3",
|
||||
"build:all:firefox-mv2": "wxt build -b firefox",
|
||||
"zip": "pnpm -w build && wxt zip",
|
||||
"postinstall": "pnpm -w build && wxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"webextension-polyfill": "^0.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/webextension-polyfill": "^0.10.0",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export default defineBackground(() => {
|
||||
console.log(browser.runtime.id);
|
||||
logId();
|
||||
console.log({
|
||||
browser: __BROWSER__,
|
||||
chrome: __IS_CHROME__,
|
||||
firefox: __IS_FIREFOX__,
|
||||
manifestVersion: __MANIFEST_VERSION__,
|
||||
});
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
import 'url:https://stats.aklinker1.io/umami.js';
|
||||
|
||||
console.log(browser.runtime.id);
|
||||
logId();
|
||||
console.log(2);
|
||||
@@ -1,10 +0,0 @@
|
||||
import '../../common/style.css';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['*://*/*'],
|
||||
async main() {
|
||||
console.log(browser.runtime.id);
|
||||
logId();
|
||||
mountContentScriptUi();
|
||||
},
|
||||
});
|
||||
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 698 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": ["../tsconfig.base.json", "./src/.wxt/tsconfig.json"]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
srcDir: 'src',
|
||||
storeIds: {
|
||||
chrome: '123',
|
||||
},
|
||||
manifest: {
|
||||
icons: {
|
||||
'16': 'icon/16.png',
|
||||
'32': 'icon/32.png',
|
||||
'48': 'icon/48.png',
|
||||
'96': 'icon/96.png',
|
||||
'128': 'icon/128.png',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
FROM lipanski/docker-static-website:latest
|
||||
COPY dist .
|
||||
@@ -0,0 +1,40 @@
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps<{
|
||||
patterns: Array<[intput: string, output: string]>;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table class="no-vertical-dividers">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Input Pattern</th>
|
||||
<th></th>
|
||||
<th>Output Path</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="pattern of patterns">
|
||||
<td style="white-space: nowrap">
|
||||
<code>entrypoints/{{ pattern[0] }}</code>
|
||||
</td>
|
||||
<td style="padding: 6px; opacity: 50%">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M4 11v2h12l-5.5 5.5l1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5L16 11H4Z"
|
||||
/>
|
||||
</svg>
|
||||
</td>
|
||||
<td style="white-space: nowrap">
|
||||
<code>/{{ pattern[1] }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
tag?: string;
|
||||
}>();
|
||||
|
||||
const examples = ref();
|
||||
onMounted(async () => {
|
||||
const res = await fetch(
|
||||
'https://raw.githubusercontent.com/wxt-dev/wxt-examples/main/examples.json',
|
||||
);
|
||||
examples.value = await res.json();
|
||||
});
|
||||
|
||||
const filteredExamples = computed(() => {
|
||||
if (props.tag == null) return examples.value;
|
||||
|
||||
return examples.value.filter((example) => {
|
||||
return example.tags?.includes(props.tag);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<li v-if="examples == null">Loading...</li>
|
||||
<template v-else>
|
||||
<li v-for="example of filteredExamples">
|
||||
<a :href="example.url" target="_blank">{{ example.name }}</a>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
@@ -1,17 +1,19 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
name: string;
|
||||
icon?: string;
|
||||
}>();
|
||||
|
||||
const src = computed(() => {
|
||||
if (props.icon) return props.icon;
|
||||
return `https://raw.githubusercontent.com/PKief/vscode-material-icon-theme/main/icons/${props.name.toLowerCase()}.svg`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img
|
||||
:src="`https://raw.githubusercontent.com/PKief/vscode-material-icon-theme/main/icons/${
|
||||
name?.toLowerCase() ?? icon
|
||||
}.svg`"
|
||||
:alt="`${name} Logo`"
|
||||
/>
|
||||
<img :src="src" :alt="`${name} Logo`" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import useListExtensionDetails, {
|
||||
ChromeExtension,
|
||||
} from '../composables/useListExtensionDetails';
|
||||
|
||||
// Add extension IDs here. Order doesn't matter, will be sorted by weekly active users
|
||||
// During the transition from chrome.google.com/webstore to
|
||||
// chromewebstore.google.com, queue.wxt.dev might return null for your
|
||||
// extension. If it does, use "<slug>/<id>" instead of just the ID. The slug
|
||||
// can be retrieved from the URL of the item on chromewebstore.google.com
|
||||
const chromeExtensionIds = [
|
||||
'ocfdgncpifmegplaglcnglhioflaimkd', // GitHub: Better Line Counts
|
||||
'mgmdkjcljneegjfajchedjpdhbadklcf', // Anime Skip Player
|
||||
'bfbnagnphiehemkdgmmficmjfddgfhpl', // UltraWideo
|
||||
'elfaihghhjjoknimpccccmkioofjjfkf', // StayFree - Website Blocker & Web Analytics
|
||||
'okifoaikfmpfcamplcfjkpdnhfodpkil', // Doozy: Ai Made Easy
|
||||
'lknmjhcajhfbbglglccadlfdjbaiifig', // tl;dv - Record, Transcribe & ChatGPT for Google Meet
|
||||
'youtube中文配音/oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
|
||||
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
|
||||
'aiakblgmlabokilgljkglggnpflljdgp', // Markdown Sticky Notes
|
||||
'nomnkbngkijpffepcgbbofhcnafpkiep', // DocVersionRedirector
|
||||
'ceicccfeikoipigeghddpocceifjelph', // Plex Skipper
|
||||
'aelkipgppclpfimeamgmlonimflbhlgf', // GitHub Custom Notifier
|
||||
'djnlaiohfaaifbibleebjggkghlmcpcj', // Fluent Read
|
||||
'nhclljcpfmmaiojbhhnkpjcfmacfcian', // Facebook Video Controls
|
||||
'mblkhbaakhbhiimkbcnmeciblfhmafna', // ElemSnap - Quick capture of webpage elements and conversion to images,
|
||||
'oajalfneblkfiejoadecnmodfpnaeblh', // MS Edge TTS (Text to Speech)
|
||||
];
|
||||
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
const sortedExtensions = computed(() => {
|
||||
if (!data.value?.length) return [];
|
||||
|
||||
return [...data.value]
|
||||
.filter((item) => item != null)
|
||||
.map((item) => ({
|
||||
...item,
|
||||
// Sort based on the user count weighted by the rating
|
||||
sortKey: ((item.rating ?? 5) / 5) * item.weeklyActiveUsers,
|
||||
}))
|
||||
.filter((item) => !!item)
|
||||
.sort((l, r) => r.sortKey - l.sortKey);
|
||||
});
|
||||
|
||||
function getStoreUrl(extension: ChromeExtension) {
|
||||
const url = new URL(extension.storeUrl);
|
||||
url.searchParams.set('utm_source', 'wxt.dev');
|
||||
return url.href;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="vp-doc">
|
||||
<div class="container">
|
||||
<h2 id="whos-using-wxt">Who's Using WXT?</h2>
|
||||
<p>
|
||||
Battle tested and ready for production. Explore chrome extensions made
|
||||
with WXT.
|
||||
</p>
|
||||
<p v-if="isLoading" style="text-align: center; opacity: 50%">
|
||||
Loading...
|
||||
</p>
|
||||
<p
|
||||
v-else-if="err || sortedExtensions.length === 0"
|
||||
style="text-align: center; opacity: 50%"
|
||||
>
|
||||
Failed to load extension details.
|
||||
</p>
|
||||
<ul v-else>
|
||||
<li
|
||||
v-for="extension of sortedExtensions"
|
||||
:key="extension.id"
|
||||
class="relative"
|
||||
>
|
||||
<img
|
||||
:src="extension.iconUrl"
|
||||
:alt="`${extension.name} icon`"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<div class="relative">
|
||||
<a
|
||||
:href="getStoreUrl(extension)"
|
||||
target="_blank"
|
||||
:title="extension.name"
|
||||
class="extension-name"
|
||||
>{{ extension.name }}</a
|
||||
>
|
||||
<p class="description" :title="extension.shortDescription">
|
||||
{{ extension.shortDescription }}
|
||||
</p>
|
||||
</div>
|
||||
<p class="user-count">
|
||||
<span>{{ extension.weeklyActiveUsers.toLocaleString() }} users</span
|
||||
><template v-if="extension.rating != null"
|
||||
>,
|
||||
<span>{{ extension.rating }} stars</span>
|
||||
</template>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="centered pr">
|
||||
<a
|
||||
href="https://github.com/wxt-dev/wxt/edit/main/docs/.vitepress/components/UsingWxtSection.vue"
|
||||
target="_blank"
|
||||
>Open a PR</a
|
||||
>
|
||||
to add your extension to the list!
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vp-doc {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.vp-doc {
|
||||
padding: 0 48px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.vp-doc {
|
||||
padding: 0 64px;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1152px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
li img {
|
||||
width: 116px;
|
||||
height: 116px;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
ul {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
align-items: stretch;
|
||||
gap: 16px;
|
||||
list-style: none;
|
||||
margin: 16px 0;
|
||||
padding: 0;
|
||||
}
|
||||
@media (min-width: 960px) {
|
||||
ul {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 0 !important;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
border-radius: 12px;
|
||||
flex: 1;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
li a,
|
||||
li .user-count,
|
||||
li .description {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
li .user-count {
|
||||
opacity: 70%;
|
||||
font-size: small;
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
right: 16px;
|
||||
}
|
||||
|
||||
li a {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
li a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
li div {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
li .description {
|
||||
opacity: 90%;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
li .extension-name {
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
.pr {
|
||||
opacity: 70%;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,59 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
export interface ChromeExtension {
|
||||
id: string;
|
||||
name: string;
|
||||
iconUrl: string;
|
||||
weeklyActiveUsers: number;
|
||||
shortDescription: string;
|
||||
storeUrl: string;
|
||||
rating: number | undefined;
|
||||
}
|
||||
|
||||
const operationName = 'WxtDocsUsedBy';
|
||||
const query = `query ${operationName}($ids:[String!]!) {
|
||||
chromeExtensions(ids: $ids) {
|
||||
id
|
||||
name
|
||||
iconUrl
|
||||
weeklyActiveUsers
|
||||
shortDescription
|
||||
storeUrl
|
||||
rating
|
||||
}
|
||||
}`;
|
||||
|
||||
export default function (ids: string[]) {
|
||||
const data = ref<ChromeExtension[]>();
|
||||
const err = ref<unknown>();
|
||||
const isLoading = ref(true);
|
||||
|
||||
fetch('https://queue.wxt.dev/api', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
operationName,
|
||||
query,
|
||||
variables: { ids },
|
||||
}),
|
||||
})
|
||||
.then(async (res) => {
|
||||
isLoading.value = false;
|
||||
const {
|
||||
data: { chromeExtensions },
|
||||
} = await res.json();
|
||||
data.value = chromeExtensions;
|
||||
err.value = undefined;
|
||||
})
|
||||
.catch((error) => {
|
||||
isLoading.value = false;
|
||||
console.error(error);
|
||||
data.value = undefined;
|
||||
err.value = error;
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
err,
|
||||
isLoading,
|
||||
};
|
||||
}
|
||||
@@ -1,72 +1,155 @@
|
||||
import { defineConfig } from 'vitepress';
|
||||
import { DefaultTheme, defineConfig } from 'vitepress';
|
||||
import { generateCliDocs } from './plugins/generate-cli-docs';
|
||||
import typedocSidebar from '../api/typedoc-sidebar.json';
|
||||
|
||||
const filteredTypedocSidebar = typedocSidebar.filter(
|
||||
(item) => item.text !== 'API',
|
||||
);
|
||||
// Typedoc's markdown theme adds collapse: true to all our items, event ones without any children,
|
||||
// so they need to be removed.
|
||||
function removeCollapsedWithNoItems(items: DefaultTheme.SidebarItem[]) {
|
||||
for (const item of items) {
|
||||
if (item.items) removeCollapsedWithNoItems(item.items);
|
||||
else delete item.collapsed;
|
||||
}
|
||||
}
|
||||
removeCollapsedWithNoItems(filteredTypedocSidebar);
|
||||
|
||||
const title = 'Next-gen Web Extension Framework';
|
||||
const titleSuffix = ' – WXT';
|
||||
|
||||
const description =
|
||||
"WXT provides the best developer experience, making it quick, easy, and fun to develop chrome extensions for all browsers. With built-in utilities for building, zipping, and publishing your extension, it's easy to get started.";
|
||||
const ogTitle = `${title}${titleSuffix}`;
|
||||
const ogUrl = 'https://wxt.dev';
|
||||
const ogImage = 'https://wxt.dev/social-preview.png';
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
titleTemplate: `:title${titleSuffix}`,
|
||||
title: 'WXT',
|
||||
description: 'Next gen framework for developing web extensions',
|
||||
description,
|
||||
vite: {
|
||||
clearScreen: false,
|
||||
plugins: [generateCliDocs()],
|
||||
},
|
||||
lastUpdated: true,
|
||||
sitemap: {
|
||||
hostname: 'https://wxt.dev',
|
||||
},
|
||||
|
||||
head: [
|
||||
['meta', { property: 'og:type', content: 'website' }],
|
||||
['meta', { property: 'og:title', content: ogTitle }],
|
||||
['meta', { property: 'og:image', content: ogImage }],
|
||||
['meta', { property: 'og:url', content: ogUrl }],
|
||||
['meta', { property: 'og:description', content: description }],
|
||||
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
|
||||
[
|
||||
'script',
|
||||
{
|
||||
async: '',
|
||||
'data-website-id': 'c1840c18-a12c-4a45-a848-55ae85ef7915',
|
||||
src: 'https://umami.aklinker1.io/script.js',
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
// logo: '/logo.svg',
|
||||
logo: {
|
||||
src: '/logo.svg',
|
||||
alt: 'WXT logo',
|
||||
},
|
||||
editLink: {
|
||||
pattern: 'https://github.com/aklinker1/wxt/edit/main/docs/:path',
|
||||
pattern: 'https://github.com/wxt-dev/wxt/edit/main/docs/:path',
|
||||
},
|
||||
search: {
|
||||
provider: 'local',
|
||||
},
|
||||
|
||||
nav: [
|
||||
{ text: 'Get Started', link: '/get-started/installation.md' },
|
||||
{ text: 'Guide', link: '/guide/auto-imports.md' },
|
||||
{ text: 'Config', link: '/config.md' },
|
||||
{ text: 'API', link: '/api.md' },
|
||||
{ text: 'Guide', link: '/guide/installation.md' },
|
||||
{ text: 'Entrypoints', link: '/entrypoints/background.md' },
|
||||
{ text: 'Examples', link: '/examples.md' },
|
||||
{ text: 'API', link: '/api/cli.md' },
|
||||
],
|
||||
|
||||
sidebar: {
|
||||
'/get-started/': [
|
||||
{
|
||||
text: 'Get Started',
|
||||
items: [
|
||||
{ text: 'Introduction', link: '/get-started/introduction.md' },
|
||||
{ text: 'Installation', link: '/get-started/installation.md' },
|
||||
{ text: 'Configuration', link: '/get-started/configuration.md' },
|
||||
{ text: 'Entrypoints', link: '/get-started/entrypoints.md' },
|
||||
{ text: 'Assets', link: '/get-started/assets.md' },
|
||||
{ text: 'Build Targets', link: '/get-started/build-targets.md' },
|
||||
{ text: 'Publishing', link: '/get-started/publishing.md' },
|
||||
{ text: 'Testing', link: '/get-started/testing.md' },
|
||||
],
|
||||
},
|
||||
],
|
||||
'/guide/': [
|
||||
{
|
||||
text: 'Guide',
|
||||
items: [
|
||||
{ text: 'Auto-imports', link: '/guide/auto-imports.md' },
|
||||
{ text: 'Introduction', link: '/guide/introduction.md' },
|
||||
{ text: 'Installation', link: '/guide/installation.md' },
|
||||
{ text: 'Configuration', link: '/guide/configuration.md' },
|
||||
{ text: 'Entrypoints', link: '/guide/entrypoints.md' },
|
||||
{ text: 'Manifest.json', link: '/guide/manifest.md' },
|
||||
{ text: 'Extension APIs', link: '/guide/extension-apis.md' },
|
||||
{ text: 'Storage', link: '/guide/storage.md' },
|
||||
{ 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' },
|
||||
{ text: 'Publishing', link: '/guide/publishing.md' },
|
||||
{ text: 'Handling Updates', link: '/guide/handling-updates.md' },
|
||||
{ text: 'Development', link: '/guide/development.md' },
|
||||
{ text: 'Testing', link: '/guide/testing.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Other',
|
||||
items: [
|
||||
{ text: 'Migrate to WXT', link: '/guide/migrate-to-wxt.md' },
|
||||
{ text: 'Compare', link: '/guide/compare.md' },
|
||||
],
|
||||
},
|
||||
],
|
||||
'/entrypoints/': [
|
||||
{
|
||||
text: 'Entrypoints',
|
||||
items: [
|
||||
{ text: 'Background', link: '/guide/background.md' },
|
||||
{ text: 'Bookmarks', link: '/guide/bookmarks.md' },
|
||||
{ text: 'Content Scripts', link: '/guide/content-scripts.md' },
|
||||
{ text: 'Devtools', link: '/guide/devtools.md' },
|
||||
{ text: 'History', link: '/guide/history.md' },
|
||||
{ text: 'Newtab', link: '/guide/newtab.md' },
|
||||
{ text: 'Options', link: '/guide/options.md' },
|
||||
{ text: 'Popup', link: '/guide/popup.md' },
|
||||
{ text: 'Sandbox', link: '/guide/sandbox.md' },
|
||||
{ text: 'Unlisted Pages', link: '/guide/unlisted-pages.md' },
|
||||
{ text: 'Unlisted Scripts', link: '/guide/unlisted-scripts.md' },
|
||||
{ text: 'Background', link: '/entrypoints/background.md' },
|
||||
{ text: 'Bookmarks', link: '/entrypoints/bookmarks.md' },
|
||||
{
|
||||
text: 'Content Scripts',
|
||||
link: '/entrypoints/content-scripts.md',
|
||||
},
|
||||
{ text: 'CSS', link: '/entrypoints/css.md' },
|
||||
{ text: 'Devtools', link: '/entrypoints/devtools.md' },
|
||||
{ text: 'History', link: '/entrypoints/history.md' },
|
||||
{ text: 'Newtab', link: '/entrypoints/newtab.md' },
|
||||
{ text: 'Options', link: '/entrypoints/options.md' },
|
||||
{ text: 'Popup', link: '/entrypoints/popup.md' },
|
||||
{ text: 'Sandbox', link: '/entrypoints/sandbox.md' },
|
||||
{ text: 'Side Panel', link: '/entrypoints/sidepanel.md' },
|
||||
{ text: 'Unlisted Pages', link: '/entrypoints/unlisted-pages.md' },
|
||||
{
|
||||
text: 'Unlisted Scripts',
|
||||
link: '/entrypoints/unlisted-scripts.md',
|
||||
},
|
||||
].sort((l, r) => l.text.localeCompare(r.text)),
|
||||
},
|
||||
],
|
||||
'/api/': [
|
||||
{
|
||||
items: [
|
||||
{ text: 'CLI', link: '/api/cli.md' },
|
||||
{
|
||||
text: 'Modules',
|
||||
items: filteredTypedocSidebar,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
socialLinks: [{ icon: 'github', link: 'https://github.com/aklinker1/wxt' }],
|
||||
socialLinks: [
|
||||
{ icon: 'discord', link: 'https://discord.gg/ZFsZqGery9' },
|
||||
{ icon: 'github', link: 'https://github.com/wxt-dev/wxt' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# CLI Reference
|
||||
|
||||
> Reference generated from `wxt <command> --help`
|
||||
|
||||
{{ DOCS }}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { relative, resolve } from 'node:path';
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { format } from 'prettier';
|
||||
import consola from 'consola';
|
||||
import { execaCommand } from 'execa';
|
||||
|
||||
let hasGenerated = false;
|
||||
|
||||
const cliDir = resolve('packages/wxt/src/cli/commands');
|
||||
const cliDirGlob = resolve(cliDir, '**');
|
||||
const cliTemplatePath = resolve('docs/.vitepress/plugins/cli.tpl.md');
|
||||
const cliPath = resolve('docs/api/cli.md');
|
||||
|
||||
const PREFACE = `<!--
|
||||
DO NOT EDIT
|
||||
Generated by \`wxt/docs/.vitepress/plugins/generate-config-docs.ts\`
|
||||
To make changes to the config reference, update the JSDoc in \`src/core/types/external.ts\`.
|
||||
-->`;
|
||||
|
||||
export function generateCliDocs() {
|
||||
writeFileSync(cliPath, '');
|
||||
|
||||
const generateDocs = async () => {
|
||||
consola.info(`Generating ${relative(process.cwd(), cliPath)}`);
|
||||
try {
|
||||
const res = await execaCommand('pnpm -s wxt --help', {
|
||||
cwd: 'packages/wxt',
|
||||
});
|
||||
const dev = splitInfo(res.stdout);
|
||||
const lines: Array<string | string[]> = [
|
||||
`## \`wxt\``,
|
||||
'```sh',
|
||||
dev.rest,
|
||||
'```',
|
||||
];
|
||||
|
||||
const commands = await Promise.allSettled(
|
||||
extractCommands(dev.info).map(async (command) => {
|
||||
const res = await execaCommand(`pnpm -s wxt ${command} --help`, {
|
||||
cwd: 'packages/wxt',
|
||||
});
|
||||
const { rest: docs } = splitInfo(res.stdout);
|
||||
return [`## \`wxt ${command}\``, '```sh', docs, '```'];
|
||||
}),
|
||||
);
|
||||
lines.push(
|
||||
...commands
|
||||
.filter((res) => res.status === 'fulfilled')
|
||||
.map(
|
||||
(res) => (res as unknown as PromiseFulfilledResult<string>).value,
|
||||
),
|
||||
);
|
||||
|
||||
const text = await format(
|
||||
PREFACE +
|
||||
'\n\n' +
|
||||
readFileSync(cliTemplatePath, 'utf-8').replace(
|
||||
'{{ DOCS }}',
|
||||
lines.flat().join('\n'),
|
||||
),
|
||||
{ parser: 'markdown' },
|
||||
);
|
||||
|
||||
writeFileSync(cliPath, text);
|
||||
consola.success(`Generated ${relative(process.cwd(), cliPath)}`);
|
||||
} catch (err) {
|
||||
consola.fail(`Failed to generate ${relative(process.cwd(), cliPath)}`);
|
||||
consola.error(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
name: 'docs:generate-cli-docs',
|
||||
async config() {
|
||||
if (!hasGenerated) {
|
||||
hasGenerated = true;
|
||||
await generateDocs();
|
||||
}
|
||||
},
|
||||
configureServer(server: any) {
|
||||
server.watcher.add(cliDirGlob);
|
||||
},
|
||||
async handleHotUpdate(ctx: { file: string }) {
|
||||
if (ctx.file === cliTemplatePath || ctx.file.includes(cliDir)) {
|
||||
await generateDocs();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function splitInfo(text: string): { info: string; rest: string } {
|
||||
const infoStart = text.indexOf('For more info,');
|
||||
const infoEnd = text.indexOf('\n\nOptions:');
|
||||
|
||||
if (infoStart === -1 || infoEnd === -1) {
|
||||
return { info: '', rest: text };
|
||||
}
|
||||
|
||||
const info = text.substring(infoStart, infoEnd).trim();
|
||||
const rest = text.replace(info, '').trim().replace('\n\n\n\n', '\n\n');
|
||||
|
||||
return { info, rest };
|
||||
}
|
||||
|
||||
function extractCommands(info: string): string[] {
|
||||
const commands: string[] = [];
|
||||
|
||||
// Split the info by line and iterate through each line
|
||||
info.split('\n').forEach((line) => {
|
||||
// Use regex to capture the command after "$ wxt " and before "--help"
|
||||
const match = line.match(/\$ wxt (\w+) --help/);
|
||||
if (match && match[1]) {
|
||||
commands.push(match[1]);
|
||||
}
|
||||
});
|
||||
|
||||
return commands;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/* Colors */
|
||||
:root {
|
||||
--wxt-c-green: #53bc4a;
|
||||
--wxt-c-green-1: #67d45e;
|
||||
--wxt-c-green-2: #4fa048;
|
||||
--wxt-c-green-3: #447e3f;
|
||||
}
|
||||
|
||||
/* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css */
|
||||
|
||||
:root {
|
||||
--vp-c-brand: var(--wxt-c-green);
|
||||
--vp-c-brand-1: var(--wxt-c-green-1);
|
||||
--vp-c-brand-2: var(--wxt-c-green-2);
|
||||
--vp-c-brand-3: var(--wxt-c-green-3);
|
||||
|
||||
--vp-button-brand-bg: var(--wxt-c-green);
|
||||
--vp-button-brand-hover-bg: var(--wxt-c-green-2);
|
||||
--vp-button-brand-active-bg: var(--wxt-c-green-3);
|
||||
|
||||
--vp-code-link-color: var(--wxt-c-green);
|
||||
|
||||
/* --vp-c-text-1: var(--wxt-c-green-1); */
|
||||
|
||||
--vp-button-brand-text: var(--vp-c-black);
|
||||
--vp-button-brand-hover-text: var(--vp-c-black);
|
||||
--vp-button-brand-active-text: var(--vp-c-black);
|
||||
|
||||
--vp-custom-block-tip-border: var(--wxt-c-green);
|
||||
--vp-custom-block-tip-text: var(--wxt-c-green);
|
||||
|
||||
/* --vp-code-block-bg: #222422;
|
||||
--vp-code-copy-code-bg: #313431;
|
||||
--vp-code-copy-code-hover-bg: #3c403c; */
|
||||
|
||||
--vp-custom-block-tip-bg: var(--vp-code-block-bg);
|
||||
--vp-custom-block-info-bg: var(--vp-code-block-bg);
|
||||
|
||||
--vp-code-color: #476582;
|
||||
}
|
||||
|
||||
.vp-doc a {
|
||||
color: var(--wxt-c-green);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-c-bg: #131413;
|
||||
|
||||
--vp-c-bg-soft: #1a1b1a;
|
||||
--vp-c-bg-soft-up: #1f201f;
|
||||
--vp-c-bg-soft-down: #262926;
|
||||
--vp-c-bg-soft-mute: #242424;
|
||||
|
||||
--vp-c-bg-alt: #171817;
|
||||
|
||||
--vp-c-default: #313136;
|
||||
--vp-c-default-1: #3a3a3c;
|
||||
--vp-c-default-2: #505053;
|
||||
--vp-c-default-3: #2c2c30;
|
||||
--vp-c-default-soft: #252529;
|
||||
|
||||
--vp-code-block-bg: #191a19;
|
||||
--vp-code-copy-code-bg: #212321;
|
||||
--vp-code-copy-code-hover-bg: #292d29;
|
||||
|
||||
--vp-custom-block-info-bg: #191a19;
|
||||
|
||||
--vp-code-color: #c9def1;
|
||||
}
|
||||
|
||||
.vp-doc .no-vertical-dividers th,
|
||||
.vp-doc .no-vertical-dividers td {
|
||||
border: none;
|
||||
}
|
||||
.vp-doc .no-vertical-dividers tr {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
@@ -1,9 +1,16 @@
|
||||
import DefaultTheme from 'vitepress/theme';
|
||||
import Icon from '../components/Icon.vue';
|
||||
import EntrypointPatterns from '../components/EntrypointPatterns.vue';
|
||||
import UsingWxtSection from '../components/UsingWxtSection.vue';
|
||||
import ExampleList from '../components/ExampleList.vue';
|
||||
import './custom.css';
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
enhanceApp(ctx) {
|
||||
ctx.app.component('Icon', Icon);
|
||||
ctx.app.component('EntrypointPatterns', EntrypointPatterns);
|
||||
ctx.app.component('UsingWxtSection', UsingWxtSection);
|
||||
ctx.app.component('ExampleList', ExampleList);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# API Reference
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
This documentation does not exist yet.
|
||||
:::
|
||||
|
Before Width: | Height: | Size: 415 KiB After Width: | Height: | Size: 238 KiB |
@@ -1,5 +0,0 @@
|
||||
# Config
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
This documentation does not exist yet.
|
||||
:::
|
||||
@@ -6,12 +6,17 @@ For MV2, the background is added as a script to the background page. For MV3, th
|
||||
|
||||
## Filenames
|
||||
|
||||
`entrypoints/background.ts` is the only recoginzed filename for the background script.
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['background.[jt]s', 'background.js'],
|
||||
['background/index.[jt]s', 'background.js'],
|
||||
]"
|
||||
/>
|
||||
|
||||
## Definition
|
||||
|
||||
:::warning
|
||||
The main function of the background **_CANNOT BE ASYNC_**. Event listeners must be added syncronously on background startup. If your main function returns a promise, WXT will log an error.
|
||||
The main function of the background **_CANNOT BE ASYNC_**. Event listeners must be added synchronously on background startup. If your main function returns a promise, WXT will log an error.
|
||||
:::
|
||||
|
||||
```ts
|
||||
@@ -28,6 +33,10 @@ export default defineBackground({
|
||||
persistent: undefined | true | false,
|
||||
type: undefined | 'module',
|
||||
|
||||
// Set include/exclude if the background should be removed from some builds
|
||||
include: undefined | string[],
|
||||
exclude: undefined | string[],
|
||||
|
||||
// Executed when background is loaded
|
||||
main() {
|
||||
// ...
|
||||
@@ -4,20 +4,25 @@
|
||||
|
||||
## Filenames
|
||||
|
||||
- `entrypoints/bookmarks.html`
|
||||
- `entrypoints/bookmarks/index.html`
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['bookmarks.html', 'bookmarks.html'],
|
||||
['bookmarks/index.html', 'bookmarks.html'],
|
||||
]"
|
||||
/>
|
||||
|
||||
## Definition
|
||||
|
||||
Plain old HTML file.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
@@ -0,0 +1,107 @@
|
||||
# Content Scripts
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/content_scripts/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts)
|
||||
|
||||
When creating content script entrypoints, they are automatically included in the `manifest.json` along with any CSS files they import.
|
||||
|
||||
## Filenames
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['content.[jt]sx?', 'content-scripts/content.js'],
|
||||
['content/index.[jt]sx?', 'content-scripts/content.js'],
|
||||
['<name>.content.[jt]sx?', 'content-scripts/<name>.js'],
|
||||
['<name>.content/index.[jt]sx?', 'content-scripts/<name>.js'],
|
||||
]"
|
||||
/>
|
||||
|
||||
## Definition
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
// Set manifest options
|
||||
matches: string[],
|
||||
excludeMatches: undefined | [],
|
||||
includeGlobs: undefined | [],
|
||||
excludeGlobs: undefined | [],
|
||||
allFrames: undefined | [],
|
||||
runAt: undefined | 'document_start' | 'document_end' | 'document_idle',
|
||||
matchAboutBlank: undefined | true | false,
|
||||
matchOriginAsFallback: undefined | true | false,
|
||||
world: undefined | 'ISOLATED' | 'MAIN',
|
||||
|
||||
// Set include/exclude if the background should be removed from some builds
|
||||
include: undefined | string[],
|
||||
exclude: undefined | string[],
|
||||
|
||||
// Configure how CSS is injected onto the page
|
||||
cssInjectionMode: undefined | "manifest" | "manual" | "ui",
|
||||
|
||||
// Configure how/when content script will be registered
|
||||
registration: undefined | "manifest" | "runtime",
|
||||
|
||||
main(ctx: ContentScriptContext) {
|
||||
// Executed when content script is loaded
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
> All manifest options default to `undefined`.
|
||||
|
||||
When defining multiple content scripts, content script entrypoints that have the same set of options will be merged into a single `content_script` item in the manifest.
|
||||
|
||||
## CSS
|
||||
|
||||
To include CSS with your content script, import the CSS file at the top of your entrypoint.
|
||||
|
||||
```
|
||||
|
||||
<srcDir>/
|
||||
└─ entrypoints/
|
||||
└─ overlay.content/
|
||||
├─ index.ts
|
||||
└─ style.css
|
||||
```
|
||||
|
||||
```ts
|
||||
// entrypoints/overlay.content/index.ts
|
||||
import './style.css';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
|
||||
|
||||
main(ctx) {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Any styles imported in your content script will be added to that content script's `css` array in your `manifest.json`:
|
||||
|
||||
```json
|
||||
// .output/chrome-mv3/manifest.json
|
||||
{
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["*://google.com/*", "*://duckduckgo.com/*"],
|
||||
"js": ["content-scripts/overlay.js"],
|
||||
"css": ["content-scripts/overlay.css"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
To disable this behavior, set `cssInjectionMode` to `"manual"` or `"ui"`.
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
|
||||
cssInjectionMode: 'manual',
|
||||
|
||||
main(ctx) {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
See [Content Script UI](/guide/content-script-ui) for more info on creating UIs and including CSS in content scripts.
|
||||
@@ -0,0 +1,44 @@
|
||||
# CSS
|
||||
|
||||
WXT can build CSS entrypoints individually. CSS entrypoints are always unlisted.
|
||||
|
||||
See [Content Script CSS](/entrypoints/content-scripts#css) documentation for the recommended approach to include CSS with a content script.
|
||||
|
||||
:::info
|
||||
If the recommended approach doesn't work for your use case, you can use any of the filename patterns below to build the styles separate from the JS and use the [`transformManifest` hook](/api/wxt/interfaces/InlineConfig#transformmanifest) to manually add your CSS file to the manifest.
|
||||
:::
|
||||
|
||||
## Filenames
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['<name>.(css|scss|sass|less|styl|stylus)', '<name>.css'],
|
||||
['<name>/index.(css|scss|sass|less|styl|stylus)', '<name>.css'],
|
||||
['content.(css|scss|sass|less|styl|stylus)', 'content-scripts/content.css'],
|
||||
['content/index.(css|scss|sass|less|styl|stylus)', 'content-scripts/content.css'],
|
||||
['<name>.content.(css|scss|sass|less|styl|stylus)', 'content-scripts/<name>.css'],
|
||||
['<name>.content/index.(css|scss|sass|less|styl|stylus)', 'content-scripts/<name>.css'],
|
||||
]"
|
||||
/>
|
||||
|
||||
## Definition
|
||||
|
||||
```css
|
||||
body {
|
||||
/* Plain CSS file */
|
||||
}
|
||||
```
|
||||
|
||||
Follow Vite's guide to setup a preprocessor: https://vitejs.dev/guide/features.html#css-pre-processors
|
||||
|
||||
```sh
|
||||
pnpm i sass
|
||||
```
|
||||
|
||||
```scss
|
||||
body {
|
||||
h1 {
|
||||
/* ...*/
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
# Devtools
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/devtools/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/devtools_page)
|
||||
|
||||
## Filenames
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['devtools.html', 'devtools.html'],
|
||||
['devtools/index.html', 'devtools.html'],
|
||||
]"
|
||||
/>
|
||||
|
||||
## Definition
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Adding UI Elements
|
||||
|
||||
Chrome extensions allow you to add panels and side panes to the devtools window.
|
||||
|
||||

|
||||
|
||||
See the WXT's examples for a full walkthrough of extending the devtools window:
|
||||
|
||||
<ExampleList tag="devtools" />
|
||||
@@ -4,20 +4,25 @@
|
||||
|
||||
## Filenames
|
||||
|
||||
- `entrypoints/history.html`
|
||||
- `entrypoints/history/index.html`
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['history.html', 'history.html'],
|
||||
['history/index.html', 'history.html'],
|
||||
]"
|
||||
/>
|
||||
|
||||
## Definition
|
||||
|
||||
Plain old HTML file.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
@@ -4,20 +4,25 @@
|
||||
|
||||
## Filenames
|
||||
|
||||
- `entrypoints/newtab.html`
|
||||
- `entrypoints/newtab/index.html`
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['newtab.html', 'newtab.html'],
|
||||
['newtab/index.html', 'newtab.html'],
|
||||
]"
|
||||
/>
|
||||
|
||||
## Definition
|
||||
|
||||
Plain old HTML file.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
@@ -4,15 +4,17 @@
|
||||
|
||||
## Filenames
|
||||
|
||||
- `entrypoints/options.html`
|
||||
- `entrypoints/options/index.html`
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['options.html', 'options.html'],
|
||||
['options/index.html', 'options.html'],
|
||||
]"
|
||||
/>
|
||||
|
||||
## Definition
|
||||
|
||||
Plain old HTML file.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -21,6 +23,9 @@ Plain old HTML file.
|
||||
<meta name="manifest.open_in_tab" content="true|false" />
|
||||
<meta name="manifest.chrome_style" content="true|false" />
|
||||
<meta name="manifest.browser_style" content="true|false" />
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
@@ -4,15 +4,17 @@
|
||||
|
||||
## Filenames
|
||||
|
||||
- `entrypoints/popup.html`
|
||||
- `entrypoints/popup/index.html`
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['popup.html', 'popup.html'],
|
||||
['popup/index.html', 'popup.html'],
|
||||
]"
|
||||
/>
|
||||
|
||||
## Definition
|
||||
|
||||
Plain old HTML file.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -27,6 +29,10 @@ Plain old HTML file.
|
||||
}"
|
||||
/>
|
||||
<meta name="manifest.type" content="page_action|browser_action" />
|
||||
<meta name="manifest.browser_style" content="true|false" />
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
@@ -0,0 +1,37 @@
|
||||
# Sandbox
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/sandbox/)
|
||||
|
||||
:::tip Chromium Only
|
||||
Firefox does not support sandboxed pages.
|
||||
:::
|
||||
|
||||
## Filenames
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['sandbox.html', 'sandbox.html'],
|
||||
['sandbox/index.html', 'sandbox.html'],
|
||||
['<name>.sandbox.html', '<name>.html` '],
|
||||
['<name>.sandbox/index.html', '<name>.html` '],
|
||||
]"
|
||||
/>
|
||||
|
||||
## Definition
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,49 @@
|
||||
# Side Panel
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/sidePanel/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Sidebars)
|
||||
|
||||
In Chrome, side panels use the "side_panel" API, while Firefox uses the "sidebar_action" API.
|
||||
|
||||
:::warning
|
||||
Chrome added support for sidepanels in Manifest V3, they are not available in Manifest V2.
|
||||
:::
|
||||
|
||||
## Filenames
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['sidepanel.html', 'sidepanel.html'],
|
||||
['sidepanel/index.html', 'sidepanel.html'],
|
||||
['<name>.sidepanel.html', '<name>.html` '],
|
||||
['<name>.sidepanel/index.html', '<name>.html` '],
|
||||
]"
|
||||
/>
|
||||
|
||||
## Definition
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Default Side Panel Title</title>
|
||||
<meta
|
||||
name="manifest.default_icon"
|
||||
content="{
|
||||
16: '/icon-16.png',
|
||||
24: '/icon-24.png',
|
||||
...
|
||||
}"
|
||||
/>
|
||||
<meta name="manifest.open_at_install" content="true|false" />
|
||||
<meta name="manifest.browser_style" content="true|false" />
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,49 @@
|
||||
# Unlisted Pages
|
||||
|
||||
HTML pages that are bundled and shipped with the extension, but are not included in the manifest.
|
||||
|
||||
If you plan on using the page in an iframe, don't forget to add the page to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources).
|
||||
|
||||
### Examples
|
||||
|
||||
- Onboarding
|
||||
- Dashboard
|
||||
- FAQ
|
||||
- Help
|
||||
- Changelog
|
||||
|
||||
## Filenames
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['<name>.html', '<name>.html'],
|
||||
['<name>/index.html', '<name>.html'],
|
||||
]"
|
||||
/>
|
||||
|
||||
Pages are accessible at `'/<name>.html'`:
|
||||
|
||||
```ts
|
||||
const url = browser.runtime.getURL('/<name>.html');
|
||||
|
||||
console.log(url); // "chrome-extension://<id>/<name>.html"
|
||||
```
|
||||
|
||||
## Definition
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,37 @@
|
||||
# Unlisted Scripts
|
||||
|
||||
TypeScript files that are bundled and shipped with the extension, but are not included in the manifest.
|
||||
|
||||
You are responsible for loading/running these scripts where needed. If necessary, don't forget to add the script and/or any related stylesheets to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources).
|
||||
|
||||
## Filenames
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['<name>.[jt]sx?', '<name>.js'],
|
||||
['<name>/index.[jt]sx?', '<name>.js'],
|
||||
]"
|
||||
/>
|
||||
|
||||
## Definition
|
||||
|
||||
```ts
|
||||
export default defineUnlistedScript(() => {
|
||||
// Executed when script is loaded
|
||||
});
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```ts
|
||||
export default defineUnlistedScript({
|
||||
// Set include/exclude if the script should be removed from some builds
|
||||
include: undefined | string[],
|
||||
exclude: undefined | string[],
|
||||
|
||||
// Executed when script is loaded
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
# Examples
|
||||
|
||||
Simple walkthroughs to accomplish common tasks or patterns with WXT.
|
||||
|
||||
<ExampleList />
|
||||
|
||||
> Full code available at [`wxt-dev/wxt-examples`](https://github.com/wxt-dev/wxt-examples)
|
||||
@@ -1,46 +0,0 @@
|
||||
# Build Targets
|
||||
|
||||
You can build an extension for any combination of browser and manifest verison. Different browsers and manifest versions support different APIs and entrypoints, so be sure to check that your extension functions as expected for each target.
|
||||
|
||||
Separate build targets are written to their own output directories:
|
||||
|
||||
```
|
||||
<root>
|
||||
└─ .output
|
||||
├─ chrome-mv3
|
||||
├─ firefox-mv2
|
||||
├─ edge-mv3
|
||||
└─ ...
|
||||
```
|
||||
|
||||
## Target Browser
|
||||
|
||||
To build for a specific browser, pass the `-b --browser` flag from the CLI:
|
||||
|
||||
```
|
||||
|
||||
wxt --browser firefox
|
||||
wxt build --browser firefox
|
||||
|
||||
```
|
||||
|
||||
By default, it will build for `chrome`. When excluding the [`--mv2` or `--mv3` flags](#target-manifest-version), it will default to the commonly accepted manifest version used with that browser.
|
||||
|
||||
| Browser | Default Manifest Version |
|
||||
| ---------------- | :----------------------: |
|
||||
| `chrome` | 3 |
|
||||
| `firefox` | 2 |
|
||||
| `safari` | 2 |
|
||||
| `edge` | 3 |
|
||||
| Any other string | 3 |
|
||||
|
||||
## Target Manifest Version
|
||||
|
||||
To build for a specific manifest version, pass either the `--mv2` flag or `--mv3` flag from the CLI.
|
||||
|
||||
```sh
|
||||
wxt --mv2
|
||||
wxt build --mv2
|
||||
```
|
||||
|
||||
When the `-b --browser` flag is not passed, it defaults to `chrome`. So here, we're targetting MV2 for Chrome.
|
||||
@@ -1,89 +0,0 @@
|
||||
# Defining Entrypoints
|
||||
|
||||
Entrypoints are any HTML, JS, or CSS file that needs to be bundled and included with the extension.
|
||||
|
||||
They may or may not be listed in the extension's `manifest.json`.
|
||||
|
||||
## `/entrypoints` Directory
|
||||
|
||||
In WXT, entrypoints are defined by adding a file to the `entrypoints/` directory.
|
||||
|
||||
For example, a project that looks like this:
|
||||
|
||||
```
|
||||
<root>
|
||||
├─ entrypoints/
|
||||
│ ├─ background.ts
|
||||
│ ├─ content.ts
|
||||
│ ├─ injected.ts
|
||||
│ └─ popup.html
|
||||
└─ wxt.config.ts
|
||||
```
|
||||
|
||||
would result in the following `manifest.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
// ...
|
||||
"manifest_version": 3,
|
||||
"action": {
|
||||
// ...
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
"background": {
|
||||
// ...
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
// ...
|
||||
"js": ["content-scripts/content.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If a file uses a [special name recognized by WXT](/get-started/entrypoints.md), it will be added to the manifest. In this case:
|
||||
|
||||
- `popup.html` → `action.default_popup`
|
||||
- `content.ts` → `content_scripts.*.js`
|
||||
- `background.ts` → `background.service_worker`
|
||||
|
||||
But not all entrypoints are added to the `manifest.json`. If they have a name that is not recognized by WXT, they are still built and included in the extension, but they are unlisted and do not show up in the manifest.
|
||||
|
||||
In this case, `injected.ts` gets bundled to `<outdir>/injected.js` and is accessible via `browser.runtime.getURL("/injected.js")`.
|
||||
|
||||
:::info
|
||||
See [`/entrypoints` folder](/get-started/entrypoints.md) documentation for a full list of recognized entrypoint filenames.
|
||||
:::
|
||||
|
||||
## Entrypoint Options
|
||||
|
||||
Some entrypoints, like content scripts, actions, or the background, can recieve additional options.
|
||||
|
||||
In HTML files, options are listed as `meta` tags:
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<!-- Defining the popup's "default_icon" field -->
|
||||
<meta name="manifest.default_icon" content="{ '16': '/icon/16.png' }" />
|
||||
</head>
|
||||
</html>
|
||||
```
|
||||
|
||||
In TS files, options are apart of the file's default export:
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
matches: ['*://*.google.com/*'],
|
||||
runAt: 'document_start',
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::info
|
||||
For a full list of entrypoints and each of their options, see the [`/entrypoints` folder](/get-started/entrypoints.md) documentation.
|
||||
:::
|
||||
@@ -1,121 +0,0 @@
|
||||
# Installation
|
||||
|
||||
Bootstrap a new project or start from scratch.
|
||||
|
||||
## Bootstrap Project
|
||||
|
||||
:::warning 🚧 The `wxt init` command is not implemented yet.
|
||||
|
||||
See [From Scratch](#from-scratch) or reference one of the templates below.
|
||||
|
||||
:::
|
||||
|
||||
:::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
There are several starting templates available.
|
||||
|
||||
| TypeScript |
|
||||
| ---------------------------------------------------------------------------------------------------- |
|
||||
| <Icon name="TypeScript" /> [`vanilla`](https://github.com/aklinker1/wxt/tree/main/templates/vanilla) |
|
||||
| <Icon name="Vue" /> [`vue`](https://github.com/aklinker1/wxt/tree/main/templates/vue) |
|
||||
| <Icon name="React" /> [`react`](https://github.com/aklinker1/wxt/tree/main/templates/react) |
|
||||
| <Icon name="Svelte" /> [`svelte`](https://github.com/aklinker1/wxt/tree/main/templates/svelte) |
|
||||
|
||||
> All templates are in TypeScript. WXT does not support JS at this time.
|
||||
|
||||
## From Scratch
|
||||
|
||||
Create a new NPM project:
|
||||
|
||||
:::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm init <project-name>
|
||||
cd <project-name>
|
||||
echo 'shamefully-hoist=true' >> .npmrc
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm init <project-name>
|
||||
cd <project-name>
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn init <project-name>
|
||||
cd <project-name>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Then install `wxt`:
|
||||
|
||||
:::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add wxt
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm i --save wxt
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add wxt
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Add your first entrypoint:
|
||||
|
||||
```ts
|
||||
// entrypoints/background.ts
|
||||
export default defineBackground(() => {
|
||||
console.log(`Hello from ${browser.runtime.id}!`);
|
||||
});
|
||||
```
|
||||
|
||||
Finally, add scripts to your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "wxt", // [!code ++]
|
||||
"dev:firefox": "wxt --browser firefox", // [!code ++]
|
||||
"build": "wxt build", // [!code ++]
|
||||
"build:firefox": "wxt build --browser firefox", // [!code ++]
|
||||
"postinstall": "wxt prepare" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> You can skip `*:firefox` scripts if you don't want to support Firefox
|
||||
|
||||
## Development
|
||||
|
||||
Once you've installed WXT, you can start the development server using the `dev` script.
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
:::tip 🎉 Well done!
|
||||
|
||||
The dev command will build the extension for development, open the browser, and reload the different parts of the extension when you save changes.
|
||||
:::
|
||||
|
||||
## Next Steps
|
||||
|
||||
You're ready to build a out your web extension!
|
||||
|
||||
- Learn how to [add entrypoints](./entrypoints.md) like the popup, background, or content scripts
|
||||
- [Configure WXT](./configuration.md) by creating a `wxt.config.ts` file
|
||||
@@ -1,5 +0,0 @@
|
||||
# Publishing
|
||||
|
||||
:::warning 🚧 Not implemented yet!
|
||||
For now, manually zip the output directory and upload to stores by hand.
|
||||
:::
|
||||
@@ -1,5 +0,0 @@
|
||||
# Testing
|
||||
|
||||
:::warning 🚧 Testing utils are not implemented yet!
|
||||
Eventually, the plan is to have an integration with Vitest.
|
||||
:::
|
||||
@@ -63,7 +63,7 @@ Files in the assets directory will be processed by Vite. They are imported in yo
|
||||
<!-- ... -->
|
||||
</head>
|
||||
<body>
|
||||
<img src="~/illustration.svg" />
|
||||
<img src="~/assets/illustration.svg" />
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,27 +3,41 @@
|
||||
WXT uses the same tool as Nuxt for auto-imports, [`unimport`](https://github.com/unjs/unimport).
|
||||
|
||||
:::info Testing
|
||||
To setup your test environment for auto-imports, see [Testing](/get-started/testing.md).
|
||||
To setup your test environment for auto-imports, see [Testing](/guide/testing).
|
||||
:::
|
||||
|
||||
## WXT Auto-imports
|
||||
|
||||
Some WXT APIs can be used without importing them:
|
||||
|
||||
- [`browser`](/config.md#browser) from `webextension-polyfill`
|
||||
- [`defineContentScript`](/config.md#defiencontentscript) from `wxt/client`
|
||||
- [`defineBackground`](/config.md#definebackgroundscript) from `wxt/client`
|
||||
- [`browser`](/api/wxt/browser/variables/browser) from `wxt/browser`, a small wrapper around `webextension-polyfill`
|
||||
- [`defineContentScript`](/api/wxt/sandbox/functions/defineContentScript) from `wxt/sandbox`
|
||||
- [`defineBackground`](/api/wxt/sandbox/functions/defineBackground) from `wxt/sandbox`
|
||||
- [`defineUnlistedScript`](/api/wxt/sandbox/functions/defineUnlistedScript) from `wxt/sandbox`
|
||||
- [`createIntegratedUi`](/api/wxt/client/functions/createIntegratedUi) from `wxt/client`
|
||||
- [`createShadowRootUi`](/api/wxt/client/functions/createShadowRootUi) from `wxt/client`
|
||||
- [`createIframeUi`](/api/wxt/client/functions/createIframeUi) from `wxt/client`
|
||||
- [`fakeBrowser`](/api/wxt/testing/variables/fakeBrowser) from `wxt/testing`
|
||||
|
||||
And more. All [`wxt/client`](/config.md#wxtclient) APIs can be used without imports.
|
||||
And more!
|
||||
|
||||
## Project Auto-imports
|
||||
|
||||
In addition WXT APIs, default and named exports from inside the following directories can be used without listing them in imports.
|
||||
|
||||
- `<srcDir>/components/**/*`
|
||||
- `<srcDir>/composables/**/*`
|
||||
- `<srcDir>/hooks/**/*`
|
||||
- `<srcDir>/utils/**/*`
|
||||
- `<srcDir>/components/*`
|
||||
- `<srcDir>/composables/*`
|
||||
- `<srcDir>/hooks/*`
|
||||
- `<srcDir>/utils/*`
|
||||
|
||||
To add auto-imports from subdirectories, like `utils/api/some-file.ts`, re-export them from the base directory:
|
||||
|
||||
```ts
|
||||
// utils/index.ts
|
||||
export * from './api/some-file.ts';
|
||||
```
|
||||
|
||||
Alternatively, you could add the directory to the list of auto-import directories in your config file.
|
||||
|
||||
## TypeScript
|
||||
|
||||
@@ -51,8 +65,8 @@ import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
imports: {
|
||||
// Add auto-imports for vue fuctions like createApp, ref, computed, watch, toRaw, etc...
|
||||
preset: ['vue'],
|
||||
// Add auto-imports for vue functions like createApp, ref, computed, watch, toRaw, etc...
|
||||
presets: ['vue'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Compare
|
||||
|
||||
Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework) (another web extension framework) and [CRXJS](https://crxjs.dev/vite-plugin) (the most popular bundler plugin).
|
||||
|
||||
## Overview
|
||||
|
||||
| Features | WXT | Plasmo | CRXJS |
|
||||
| ---------------------------------------------------- | :-------------------------: | :--------------------------------------: | :---------------------------------------------------------------------: |
|
||||
| Supports all browsers | ✅ | ✅ | ❌ See [#56](https://github.com/crxjs/chrome-extension-tools/issues/56) |
|
||||
| MV2 Support | ✅ | ✅ | 🟡 Either MV2 or MV3 |
|
||||
| MV3 Support | ✅ | ✅ | 🟡 Either MV2 or MV3 |
|
||||
| Create Extension ZIPs | ✅ | ✅ | ❌ |
|
||||
| Create Firefox Sources ZIP | ✅ | ❌ | ❌ |
|
||||
| First-class TypeScript support | ✅ | ✅ | ✅ |
|
||||
| Entrypoint discovery | File based | File based | ❌ |
|
||||
| Inline entrypoint config | ✅ | ✅ | Manifest based |
|
||||
| Auto-imports | ✅ | ❌ | ❌ |
|
||||
| Supports all frontend frameworks | ✅ | 🟡 Only React, Vue, and Svelte | ✅ |
|
||||
| Framework specific entrypoints (like `Popup.tsx`) | 🟡 `.html` `.ts` `.tsx` | ✅ `.html` `.ts` `.tsx` `.vue` `.svelte` | ❌ |
|
||||
| Automated publishing | ✅ | ✅ | ❌ |
|
||||
| Remote Code Bundling (Google Analytics) | ✅ | ✅ | ❌ |
|
||||
| <strong style="opacity: 50%">Dev Mode</strong> | | |
|
||||
| `.env` Files | ✅ | ✅ | ✅ |
|
||||
| Opens browser and install extension | ✅ | ❌ | ❌ |
|
||||
| HMR for UIs | ✅ | 🟡 React only | ✅ |
|
||||
| Reload HTML Files on Change | ✅ | 🟡 Reloads entire extension | ✅ |
|
||||
| Reload Content Scripts on Change | ✅ | 🟡 Reloads entire extension | ✅ |
|
||||
| Reload Background on Change | 🟡 Reloads entire extension | 🟡 Reloads entire extension | 🟡 Reloads entire extension |
|
||||
| Respects Content Script `run_at` | ✅ | ✅ | ❌ ESM-style loaders run asynchronously |
|
||||
| <strong style="opacity: 50%">Built-in Utils</strong> | | | |
|
||||
| Storage | ✅ | ✅ | ❌ |
|
||||
| Messaging | ❌ | ✅ | ❌ |
|
||||
| Content Script UI | ✅ | ✅ | ❌ |
|
||||
|
||||
## Why use WXT?
|
||||
|
||||
WXT's main goal is improving the development experience (DX) of creating web extensions, while not sacrificing support. There are two things WXT does differently:
|
||||
|
||||
1. Automatically opens a browser with the extension installed when starting development
|
||||
2. Reload each part of the extension individually rather than reloading the entire extension
|
||||
|
||||
Opening a browser automatically makes it super easy to start and stop development without having to manually load the extension in your browser.
|
||||
|
||||
Reloading each part of the extension individually improves your iteration speed while developing UIs. This is because reloading the entire extension on every change will close the popup and any tabs open to an extension page, like options. If you save a file associated with a UI and a content script while working on the UI, it will randomly close because it needed to reload the extension when the content script changed. This interrupts your development flow and is really annoying.
|
||||
|
||||
WXT solves this problem by reloading HTML pages and content scripts individually (when possible) to keep your UIs open while you develop them. This is a MV3 feature, so if you're developing a MV2 extension, you'll get the same dev experience as Plasmo.
|
||||
|
||||
:::info
|
||||
Unfortunately, there isn't an API for reloading the background page/service worker individually, so if you change a file used by the background, the entire extension will reload. See [Issue #53](https://github.com/wxt-dev/wxt/issues/53) for more details.
|
||||
:::
|
||||
@@ -1,8 +1,6 @@
|
||||
# Configuration
|
||||
|
||||
WXT's behavior can be configured via the `wxt.config.ts` file. In this file, you can add Vite plugins, change the directory strucutre of your project, and provide permissions or other fields to the `<outdir>/manifest.json`.
|
||||
|
||||
However, since WXT is an opinionated framework, some things cannot be configured.
|
||||
WXT's behavior can be configured via the `wxt.config.ts` file. In this file, you can add Vite plugins, change the directory structure of your project, and set fields on your `manifest.json`.
|
||||
|
||||
## Config File
|
||||
|
||||
@@ -17,7 +15,7 @@ export default defineConfig({
|
||||
```
|
||||
|
||||
:::info
|
||||
See the [API reference](/api.md) for a full list of options.
|
||||
See the [Config reference](/api/wxt/interfaces/InlineConfig) for a full list of options.
|
||||
:::
|
||||
|
||||
## Directory Config
|
||||
@@ -25,13 +23,24 @@ See the [API reference](/api.md) for a full list of options.
|
||||
WXT allows you to edit several directories to your liking:
|
||||
|
||||
- `root` (default: `process.cwd()`) - Root of the WXT project
|
||||
- `srcDir` (default: `<root>`) - Location of all your source code
|
||||
- `srcDir` (default: `<rootDir>`) - Location of all your source code
|
||||
- `entrypointsDir` (default: `<srcDir>/entrypoints`) - Folder containing all the entrypoints.
|
||||
- `publicDir` (default: `<srcDir>/public`) - Folder containing [public assets](/get-started/assets.md)
|
||||
- `publicDir` (default: `<srcDir>/public`) - Folder containing [public assets](/guide/assets)
|
||||
|
||||
### Example
|
||||
|
||||
If you want a `src/` directory to contain all your source code, and you want to rename `entrypoints/` to `entries/`, your config would look like this:
|
||||
You want a `src/` directory to contain all your source code, and you want to rename `entrypoints/` → `entries/`:
|
||||
|
||||
```
|
||||
<rootDir>
|
||||
├─ src/
|
||||
│ └─ entries/
|
||||
│ ├─ background.ts
|
||||
│ └─ ...
|
||||
└─ wxt.config.ts
|
||||
```
|
||||
|
||||
Your config would look like this:
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'wxt';
|
||||
@@ -42,12 +51,6 @@ export default defineConfig({
|
||||
});
|
||||
```
|
||||
|
||||
## Vite Config
|
||||
|
||||
[Vite](https://vitejs.dev/) is the bundler used to build each entrypoint of your extension. Vite can be configured via the `vite` option.
|
||||
|
||||
See [Vite's documentation](https://vitejs.dev/config/) for configuring the bundler.
|
||||
|
||||
## Frontend Frameworks
|
||||
|
||||
Adding a framework like Vue, React, or Svelte is easy!
|
||||
@@ -61,9 +64,9 @@ import { defineConfig } from 'wxt';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
|
||||
export default defineConfig({
|
||||
vite: {
|
||||
vite: () => ({
|
||||
plugins: [vue()],
|
||||
},
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
@@ -72,9 +75,9 @@ import { defineConfig } from 'wxt';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
vite: {
|
||||
vite: () => ({
|
||||
plugins: [react()],
|
||||
},
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
@@ -83,10 +86,14 @@ import { defineConfig } from 'wxt';
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
export default defineConfig({
|
||||
vite: {
|
||||
vite: () => ({
|
||||
plugins: [svelte()],
|
||||
},
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::info
|
||||
Internally, WXT runs `vite build` multiple times during a single build. It can't share the same instance of some plugins between each build, so a function is used to return completely new instances of each plugin for each build.
|
||||
:::
|
||||
@@ -0,0 +1,408 @@
|
||||
# Content Script UI
|
||||
|
||||
There are three ways to mount a UI inside a content script:
|
||||
|
||||
[[toc]]
|
||||
|
||||
Each has their own set of advantages and disadvantages.
|
||||
|
||||
| Method | Isolated Styles | Isolated Events | HMR | Use page's context |
|
||||
| ----------- | :-------------: | :-----------------: | :-: | :----------------: |
|
||||
| Integrated | ❌ | ❌ | ❌ | ✅ |
|
||||
| Shadow Root | ✅ | ✅ (off by default) | ❌ | ✅ |
|
||||
| IFrame | ✅ | ✅ | ✅ | ❌ |
|
||||
|
||||
## Integrated
|
||||
|
||||
Integrated content script UIs are injected alongside the content of a page. This means that they are affected by CSS on that page.
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [Vanilla]
|
||||
// entrypoints/example-ui.content.ts
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
|
||||
main(ctx) {
|
||||
const ui = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
onMount: (container) => {
|
||||
// Append children to the container
|
||||
const app = document.createElement('p');
|
||||
app.textContent = '...';
|
||||
container.append(app);
|
||||
},
|
||||
});
|
||||
|
||||
// Call mount to add the UI to the DOM
|
||||
ui.mount();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts [Vue]
|
||||
// entrypoints/example-ui.content/index.ts
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
|
||||
main(ctx) {
|
||||
const ui = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
onMount: (container) => {
|
||||
// Create the app and mount it to the UI container
|
||||
const app = createApp(App);
|
||||
app.mount(container);
|
||||
return app;
|
||||
},
|
||||
onRemove: (app) => {
|
||||
// Unmount the app when the UI is removed
|
||||
app.unmount();
|
||||
},
|
||||
});
|
||||
|
||||
// Call mount to add the UI to the DOM
|
||||
ui.mount();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```tsx [React]
|
||||
// entrypoints/example-ui.content/index.tsx
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
|
||||
main(ctx) {
|
||||
const ui = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
onMount: (container) => {
|
||||
// Create a root on the UI container and render a component
|
||||
const root = ReactDOM.createRoot(container);
|
||||
root.render(<App />);
|
||||
return root;
|
||||
},
|
||||
onRemove: (root) => {
|
||||
// Unmount the root when the UI is removed
|
||||
root.unmount();
|
||||
},
|
||||
});
|
||||
|
||||
// Call mount to add the UI to the DOM
|
||||
ui.mount();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts [Svelte]
|
||||
// entrypoints/example-ui.content/index.ts
|
||||
import App from './App.svelte';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
|
||||
main(ctx) {
|
||||
const ui = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
onMount: (container) => {
|
||||
// Create the Svelte app inside the UI container
|
||||
const app = new App({
|
||||
target: container,
|
||||
});
|
||||
return app;
|
||||
},
|
||||
onRemove: (app) => {
|
||||
// Destroy the app when the UI is removed
|
||||
app.$destroy();
|
||||
},
|
||||
});
|
||||
|
||||
// Call mount to add the UI to the DOM
|
||||
ui.mount();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```tsx [Solid]
|
||||
// entrypoints/example-ui.content/index.ts
|
||||
import { render } from 'solid-js/web';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
|
||||
main(ctx) {
|
||||
const ui = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
onMount: (container) => {
|
||||
// Render your app to the UI container
|
||||
const unmount = render(() => <div>...</div>, container);
|
||||
},
|
||||
onRemove: (unmount) => {
|
||||
// Unmount the app when the UI is removed
|
||||
unmount();
|
||||
},
|
||||
});
|
||||
|
||||
// Call mount to add the UI to the DOM
|
||||
ui.mount();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
See the [API Reference](/api/wxt/client/functions/createIntegratedUi) for the complete list of options.
|
||||
|
||||
You can control how CSS is injected for an integrated content script UI with the [`cssInjectionMode`](/api/wxt/interfaces/BaseContentScriptEntrypointOptions#cssinjectionmode) property. Usually, you'll want to leave it as `"manifest"`, the default, so the UI inherits its style from the website's CSS.
|
||||
|
||||
## Shadow Root
|
||||
|
||||
Often in web extensions, you don't want your content script's CSS affecting the page, or vise-versa. The [`ShadowRoot`](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot) API is ideal for this.
|
||||
|
||||
WXT's [`createShadowRootUi`](/api/wxt/client/functions/createShadowRootUi) abstracts all the `ShadowRoot` setup away, making it easy to create UIs with isolated CSS. It also supports an optional `isolateEvents` parameter to further isolate user interactions.
|
||||
|
||||
To use `createShadowRootUi`, follow these steps:
|
||||
|
||||
1. Import your CSS file at the top of your content script
|
||||
2. Set [`cssInjectionMode: "ui"`](/api/wxt/interfaces/BaseContentScriptEntrypointOptions#cssinjectionmode) inside `defineContentScript`
|
||||
3. Define your UI with `createShadowRootUi()`
|
||||
4. Mount the UI so it is visible to users
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [Vanilla]
|
||||
// 1. Import the style
|
||||
import './style.css';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
// 2. Set cssInjectionMode
|
||||
cssInjectionMode: 'ui',
|
||||
|
||||
async main(ctx) {
|
||||
// 3. Define your UI
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
name: 'example-ui',
|
||||
position: 'inline',
|
||||
onMount(container) {
|
||||
// Define how your UI will be mounted inside the container
|
||||
const app = document.createElement('p');
|
||||
app.textContent = 'Hello world!';
|
||||
container.append(app);
|
||||
},
|
||||
});
|
||||
|
||||
// 4. Mount the UI
|
||||
ui.mount();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts [Vue]
|
||||
// 1. Import the style
|
||||
import './style.css';
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
// 2. Set cssInjectionMode
|
||||
cssInjectionMode: 'ui',
|
||||
|
||||
async main(ctx) {
|
||||
// 3. Define your UI
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
name: 'example-ui',
|
||||
position: 'inline',
|
||||
onMount: (container) => {
|
||||
// Define how your UI will be mounted inside the container
|
||||
const app = createApp(App);
|
||||
app.mount(container);
|
||||
return app;
|
||||
},
|
||||
onRemove: (app) => {
|
||||
// Unmount the app when the UI is removed
|
||||
app?.unmount();
|
||||
},
|
||||
});
|
||||
|
||||
// 4. Mount the UI
|
||||
ui.mount();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```tsx [React]
|
||||
// 1. Import the style
|
||||
import './style.css';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
// 2. Set cssInjectionMode
|
||||
cssInjectionMode: 'ui',
|
||||
|
||||
async main(ctx) {
|
||||
// 3. Define your UI
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
name: 'example-ui',
|
||||
position: 'inline',
|
||||
onMount: (container) => {
|
||||
// Container is a body, and React warns when creating a root on the body, so create a wrapper div
|
||||
const app = document.createElement('div');
|
||||
container.append(app);
|
||||
|
||||
// Create a root on the UI container and render a component
|
||||
const root = ReactDOM.createRoot(app);
|
||||
root.render(<App />);
|
||||
return root;
|
||||
},
|
||||
onRemove: (root) => {
|
||||
// Unmount the root when the UI is removed
|
||||
root?.unmount();
|
||||
},
|
||||
});
|
||||
|
||||
// 4. Mount the UI
|
||||
ui.mount();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts [Svelte]
|
||||
// 1. Import the style
|
||||
import './style.css';
|
||||
import App from './App.svelte';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
// 2. Set cssInjectionMode
|
||||
cssInjectionMode: 'ui',
|
||||
|
||||
async main(ctx) {
|
||||
// 3. Define your UI
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
name: 'example-ui',
|
||||
position: 'inline',
|
||||
onMount: (container) => {
|
||||
// Create the Svelte app inside the UI container
|
||||
const app = new App({
|
||||
target: container,
|
||||
});
|
||||
return app;
|
||||
},
|
||||
onRemove: (app) => {
|
||||
// Destroy the app when the UI is removed
|
||||
app?.$destroy();
|
||||
},
|
||||
});
|
||||
|
||||
// 4. Mount the UI
|
||||
ui.mount();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```tsx [Solid]
|
||||
// 1. Import the style
|
||||
import './style.css';
|
||||
import { render } from 'solid-js/web';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
// 2. Set cssInjectionMode
|
||||
cssInjectionMode: 'ui',
|
||||
|
||||
async main(ctx) {
|
||||
// 3. Define your UI
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
name: 'example-ui',
|
||||
position: 'inline',
|
||||
onMount: (container) => {
|
||||
// Render your app to the UI container
|
||||
const unmount = render(() => <div>...</div>, container);
|
||||
},
|
||||
onRemove: (unmount) => {
|
||||
// Unmount the app when the UI is removed
|
||||
unmount?.();
|
||||
},
|
||||
});
|
||||
|
||||
// 4. Mount the UI
|
||||
ui.mount();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
See the [API Reference](/api/wxt/client/functions/createShadowRootUi) for the complete list of options.
|
||||
|
||||
:::info TailwindCSS
|
||||
`createShadowRootUi` supports TailwindCSS out of the box! When importing the styles, just import the main CSS file containing the `@tailwind` directives, and everything will just work :+1:.
|
||||
:::
|
||||
|
||||
## IFrame
|
||||
|
||||
If you don't need to run your UI in the same frame as the content script, you can use an IFrame to host your UI instead. Since an IFrame just hosts an HTML page, **_HMR is supported_**.
|
||||
|
||||
WXT provides a helper function, [`createIframeUi`](/api/wxt/client/functions/createIframeUi), which simplifies setting up the IFrame.
|
||||
|
||||
1. Create an HTML page that will be loaded into your IFrame
|
||||
```html
|
||||
<!-- entrypoints/example-iframe.html -->
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Your Content Script IFrame</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
1. Add the page to the manifest's `web_accessible_resources`
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
web_accessible_resources: [
|
||||
{
|
||||
resources: ['example-iframe.html'],
|
||||
matches: [...],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
1. Create and mount the IFrame
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
|
||||
async main(ctx) {
|
||||
// Define the UI
|
||||
const ui = await createIframeUi(ctx, {
|
||||
page: '/example-iframe.html',
|
||||
position: 'inline',
|
||||
onMount: (wrapper, iframe) => {
|
||||
// Add styles to the iframe like width
|
||||
iframe.width = 123;
|
||||
},
|
||||
});
|
||||
|
||||
// Show UI to user
|
||||
ui.mount();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
See the [API Reference](/api/wxt/client/functions/createIframeUi) for the complete list of options.
|
||||
@@ -1,60 +0,0 @@
|
||||
# Content Scripts
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/content_scripts/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts)
|
||||
|
||||
## Filenames
|
||||
|
||||
When a filename matches the pattern below, it is added as a content script in the `manifest.json`.
|
||||
|
||||
- `entrypoints/content.tsx?`
|
||||
- `entrypoints/<name>.content.tsx?`
|
||||
- `entrypoints/content/index.tsx?`
|
||||
- `entrypoints/<name>.content/index.tsx?`
|
||||
|
||||
## Definition
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
// Set manifest options
|
||||
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
|
||||
excludeMatches: undefined | [],
|
||||
includeGlobs: undefined | [],
|
||||
excludeGlobs: undefined | [],
|
||||
allFrames: undefined | [],
|
||||
runAt: undefined | 'document_start' | 'document_end' | 'document_idle',
|
||||
matchAboutBlank: undefined | true | false,
|
||||
matchOriginAsFallback: undefined | true | false,
|
||||
world: undefined | 'ISOLATED' | 'MAIN',
|
||||
|
||||
main() {
|
||||
// Executed when content script is loaded
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
> All manifest options default to `undefined`.
|
||||
|
||||
## CSS
|
||||
|
||||
To include CSS with your content script, import the CSS file at the top of your entrypoint:
|
||||
|
||||
```
|
||||
|
||||
<srcDir>
|
||||
└─ entrypoints/
|
||||
└─ overlay.content/
|
||||
├─ index.ts
|
||||
└─ style.css
|
||||
```
|
||||
|
||||
```ts
|
||||
// entrypoints/overlay.content/index.ts
|
||||
import './style.css';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,116 @@
|
||||
# Development
|
||||
|
||||
WXT's main goal is providing the best DX it possibly can. When running your extension in dev mode, each part of your extension is reloaded separately when possible.
|
||||
|
||||
| | HMR | Reloaded individually | Reload extension | Restart browser |
|
||||
| ------------------- | :-: | :-------------------: | :--------------: | :----------------------------------------------------: |
|
||||
| HTML File | | ✅ |
|
||||
| HTML Dependency | ✅ |
|
||||
| MV3 Content Script | | ✅ |
|
||||
| MV2 Content Script | | | ✅ |
|
||||
| Background | | | ✅ |
|
||||
| manifest.json | | | | 🟡 See [#16](https://github.com/wxt-dev/wxt/issues/16) |
|
||||
| `wxt.config.ts` | | | | 🟡 See [#10](https://github.com/wxt-dev/wxt/issues/10) |
|
||||
| `web-ext.config.ts` | | | | 🟡 See [#10](https://github.com/wxt-dev/wxt/issues/10) |
|
||||
|
||||
## Dev Mode vs Production Builds
|
||||
|
||||
There are some notable differences between the development and production versions of an extension. During development:
|
||||
|
||||
1. **Content scripts are not listed in the `manifest.json`** when targeting MV3. Instead, the [`scripting`](https://developer.chrome.com/docs/extensions/reference/api/scripting) permission is used to register content scripts at runtime so they can be reloaded individually.
|
||||
|
||||
To get the list of content scripts during development, run the following in the background's console:
|
||||
|
||||
```ts
|
||||
await chrome.scripting.getRegisteredContentScripts();
|
||||
```
|
||||
|
||||
2. **The CSP is modified to allow loading scripts from the dev server**. Make sure you're using Chrome v110 or above for HMR to work.
|
||||
|
||||
3. If you don't include a background script/service worker, one will be created to perform various tasks in dev mode, mostly related to reloading different parts of the extension on change.
|
||||
|
||||
For production builds, none of the above modifications will be applied, and you're extension/manifest will only include what you have defined.
|
||||
|
||||
## Configure Browser Startup
|
||||
|
||||
WXT uses [`web-ext` by Mozilla](https://github.com/mozilla/web-ext) to automatically open a browser with the extension installed. You can configure the runner's behavior via the [`runner`](/api/wxt/interfaces/InlineConfig#runner) option, or in a separate gitignored file, `web-ext.config.ts`.
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
runner: {
|
||||
// Runner config
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts [web-ext.config.ts]
|
||||
import { defineRunnerConfig } from 'wxt';
|
||||
|
||||
export default defineRunnerConfig({
|
||||
// Runner config
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Browser Binaries
|
||||
|
||||
`web-ext`'s browser discovery is very limited. By default, it only guesses at where Chrome and Firefox are installed. If you've customized your install locations, you may need to tell `web-ext` where the binaries/executables are located using the [`binaries` option](/api/wxt/interfaces/ExtensionRunnerConfig#binaries). For other Chromium based browsers, like Edge or Opera, you'll need to explicitly list them in the `binaries` option as well, otherwise they will open in Chrome by default.
|
||||
|
||||
```ts
|
||||
// ~/web-ext.config.ts
|
||||
import { defineRunnerConfig } from 'wxt';
|
||||
|
||||
export default defineRunnerConfig({
|
||||
binaries: {
|
||||
chrome: '/path/to/chrome-beta', // Use Chrome Beta instead of regular Chrome
|
||||
firefox: 'firefoxdeveloperedition', // Use Firefox Developer Edition instead of regular Firefox
|
||||
edge: '/path/to/edge', // Open MS Edge when running "wxt -b edge"
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::tip
|
||||
When configuring browser binaries, it's helpful to put them in `~/web-ext.config.ts` instead of the project directory's `web-ext.config.ts` file. When placed in your home directory (`~/`), this config will be used by all WXT projects, so you only need to configure the binaries once.
|
||||
:::
|
||||
|
||||
### Other options
|
||||
|
||||
You can customize other options as well, like startup URLs, profiles, or additional command line arguments:
|
||||
|
||||
```ts
|
||||
// web-ext.config.ts
|
||||
import { defineRunnerConfig } from 'wxt';
|
||||
|
||||
export default defineRunnerConfig({
|
||||
startUrls: ['https://google.com', 'https://duckduckgo.com'],
|
||||
chromiumProfile: '/path/to/profile/to/use',
|
||||
chromiumArgs: ['--window-size=400,300'],
|
||||
});
|
||||
```
|
||||
|
||||
For a full list of options, see the [API Reference](/api/wxt/interfaces/ExtensionRunnerConfig).
|
||||
|
||||
## Reload the Extension
|
||||
|
||||
Normally, to manually reload an extension, you have to visit `chrome://extensions` and click the reload button for your extension.
|
||||
|
||||
When running `wxt` command to start the dev server, WXT adds a keyboard shortcut `Alt+R`, that reloads the extension when pressed, without visiting `chrome://extensions`. This can also be customized or disabled:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
dev: {
|
||||
reloadCommand: 'Alt+T', // false, to disable
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::info
|
||||
This shortcut is only available during development, and is not be added to your extension when running `wxt build` or `wxt-zip`.
|
||||
:::
|
||||
@@ -1,26 +0,0 @@
|
||||
# Devtools
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/devtools/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/devtools_page)
|
||||
|
||||
## Filenames
|
||||
|
||||
- `entrypoints/devtools.html`
|
||||
- `entrypoints/devtools/index.html`
|
||||
|
||||
## Definition
|
||||
|
||||
Plain old HTML file.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,141 @@
|
||||
# Defining Entrypoints
|
||||
|
||||
An "entrypoint" is any HTML, JS, or CSS file that needs to be bundled and included with the extension.
|
||||
|
||||
Entrypoints may or may not be listed in the extension's `manifest.json`.
|
||||
|
||||
## `/entrypoints` Directory
|
||||
|
||||
In WXT, entrypoints are defined by adding a file to the `entrypoints/` directory.
|
||||
|
||||
For example, a project that looks like this:
|
||||
|
||||
```
|
||||
<rootDir>
|
||||
└─ entrypoints/
|
||||
├─ background.ts
|
||||
├─ content.ts
|
||||
├─ injected.ts
|
||||
└─ popup.html
|
||||
```
|
||||
|
||||
would result in the following `manifest.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
// ...
|
||||
"manifest_version": 3,
|
||||
"action": {
|
||||
// ...
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
"background": {
|
||||
// ...
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
// ...
|
||||
"js": ["content-scripts/content.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If a file uses a special name recognized by WXT, it will be added to the manifest. In this case:
|
||||
|
||||
- `popup.html` → `action.default_popup`
|
||||
- `content.ts` → `content_scripts.0.js.0`
|
||||
- `background.ts` → `background.service_worker`
|
||||
|
||||
But not all entrypoints are added to the `manifest.json`. If the filename is not recognized by WXT, they are still built and included in the extension, but they are considered "unlisted" and are not apart of the manifest.
|
||||
|
||||
In this case, `injected.ts` gets output to `<outdir>/injected.js` and is accessible via `browser.runtime.getURL("/injected.js")`.
|
||||
|
||||
:::info
|
||||
See [`/entrypoints` folder](/entrypoints/background) documentation for a full list of recognized entrypoint filenames.
|
||||
:::
|
||||
|
||||
## Entrypoint Options
|
||||
|
||||
Some entrypoints, like content scripts, actions, or the background, can receive additional options.
|
||||
|
||||
In HTML files, options are listed as `meta` tags:
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<!-- Defining the popup's "default_icon" field -->
|
||||
<meta name="manifest.default_icon" content="{ '16': '/icon/16.png' }" />
|
||||
</head>
|
||||
</html>
|
||||
```
|
||||
|
||||
In TS files, options are apart of the file's default export:
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
matches: ['*://*.google.com/*'],
|
||||
runAt: 'document_start',
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::info
|
||||
For a full list of entrypoints and each of their options, see the [`/entrypoints` folder](/entrypoints/background) documentation.
|
||||
:::
|
||||
|
||||
### Side Effects
|
||||
|
||||
You cannot use imported variables outside the `main` function in JS entrypoints. This includes options, as shown below:
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
import { GOOGLE_MATCHES } from '~/utils/match-patterns';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: GOOGLE_MATCHES,
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```
|
||||
$ wxt build
|
||||
wxt build
|
||||
|
||||
WXT 0.14.1
|
||||
ℹ Building chrome-mv3 for production with Vite 5.0.5
|
||||
✖ Command failed after 360 ms
|
||||
|
||||
[8:55:54 AM] ERROR entrypoints/content.ts: Cannot use imported variable "GOOGLE_MATCHES" before main function. See https://wxt.dev/guide/entrypoints.html#side-effects
|
||||
```
|
||||
|
||||
This throws an error because WXT needs to import each entrypoint during the build process to extract its definition (containing the `match`, `runAt`, `include`/`exclude`, etc.) to render the `manifest.json` correctly. Before loading an entrypoint, a transformation is applied to remove all imports. This prevents imported modules (local or NPM) with side-effects from running during the build process, potentially throwing an error.
|
||||
|
||||
:::details Why?
|
||||
|
||||
When importing your entrypoint to get its definition, the file is imported in a **_node environment_**, and doesn't have access to the `window`, `chrome`, or `browser` globals a web extension usually has access to. If WXT doesn't remove all the imports from the file, the imported modules could try and access one of these variables, throwing an error.
|
||||
|
||||
:::
|
||||
|
||||
:::warning
|
||||
See [`wxt-dev/wxt#336`](https://github.com/wxt-dev/wxt/issues/336) to track the status of this bug.
|
||||
:::
|
||||
|
||||
Usually, this error occurs when you try to extract options into a shared file or try to run code outside the `main` function. To fix the example from above, use literal values when defining an entrypoint instead of importing them:
|
||||
|
||||
```ts
|
||||
import { GOOGLE_MATCHES } from '~/utils/match-patterns'; // [!code --]
|
||||
|
||||
export default defineContentScript({
|
||||
matches: GOOGLE_MATCHES, // [!code --]
|
||||
matches: ['*//*.google.com/*'], // [!code ++]
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -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 targeting 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.
|
||||
@@ -11,12 +11,14 @@ And that's it! Your extension now supports Chrome, Firefox, Safari, Edge, and ot
|
||||
|
||||
## Basic Usage
|
||||
|
||||
The `browser` variable is available globally via [auto-imports](/guide/auto-imports.md), or it can be imported manually.
|
||||
The `browser` variable is available globally via [auto-imports](/guide/auto-imports), or it can be imported manually.
|
||||
|
||||
```ts
|
||||
import browser from 'webextension-polyfill';
|
||||
import { browser } from 'wxt/browser';
|
||||
```
|
||||
|
||||
The `wxt/browser` module exports a customized version of `webextension-polyfill`'s browser with improved typing.
|
||||
|
||||
### Example
|
||||
|
||||
Let's save the date the extension was installed. Just like `chrome`, some APIs require the permission is added to your manifest before the API is defined. Here, we need to add the `storage` permission to your manifest.
|
||||
@@ -37,9 +39,9 @@ Then we can use `browser.storage` to save the install date to local storage.
|
||||
```ts
|
||||
// background.ts
|
||||
export default defineBackground(() => {
|
||||
browser.runtime.onInstall.addEventListener(({ reason }) => {
|
||||
browser.runtime.onInstalled.addListener(({ reason }) => {
|
||||
if (reason === 'install') {
|
||||
browser.storage.local.setItem({ installDate: Date.now() });
|
||||
browser.storage.local.set({ installDate: Date.now() });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -51,34 +53,32 @@ Follow [Chrome's message passing guide](https://developer.chrome.com/docs/extens
|
||||
|
||||
Here's a basic request/response example:
|
||||
|
||||
```ts
|
||||
// background.ts
|
||||
export default defineBackground(() => {
|
||||
browser.runtime.onMessage.addEventListener(
|
||||
(message, sender, sendResponse) => {
|
||||
console.log(message); // "ping"
|
||||
|
||||
// Wait 1 second and respond with "pong"
|
||||
setTimeout(() => sendResponse('pong'), 1000);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
// popup/main.ts
|
||||
const res = await browser.runtime.sendMessage('ping');
|
||||
|
||||
console.log('res'); // "pong"
|
||||
console.log(res); // "pong"
|
||||
```
|
||||
|
||||
```ts
|
||||
// background.ts
|
||||
export default defineBackground(() => {
|
||||
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
console.log(message); // "ping"
|
||||
|
||||
// Wait 1 second and respond with "pong"
|
||||
setTimeout(() => sendResponse('pong'), 1000);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
There are a number of message passing libraries you can use to improve the message passing experience.
|
||||
|
||||
Here are some that are compatible with WXT (because they are based off `webextension-polyfill` as well):
|
||||
|
||||
- [`@webext-core/messaging`](https://webext-core.aklinker1.io/guide/proxy-service/) - "A light-weight, type-safe wrapper around the `browser.runtime` messaging APIs"
|
||||
- [`@webext-core/proxy-service`](https://webext-core.aklinker1.io/guide/messaging/) - "Create TRPC-like services that can be called from anywhere but run in the background"
|
||||
- [`@webext-core/messaging`](https://webext-core.aklinker1.io/guide/messaging/) - "A light-weight, type-safe wrapper around the `browser.runtime` messaging APIs"
|
||||
- [`@webext-core/proxy-service`](https://webext-core.aklinker1.io/guide/proxy-service/) - "Create TRPC-like services that can be called from anywhere but run in the background"
|
||||
- [`webext-bridge`](https://github.com/zikaari/webext-bridge) - "Messaging in Web Extensions made super easy. Out of the box."
|
||||
|
||||
## Browser Differences
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# Handling Extension Updates
|
||||
|
||||
When releasing an update to your extension, there's a couple of things you need to keep in mind:
|
||||
|
||||
[[toc]]
|
||||
|
||||
## Content Script Cleanup
|
||||
|
||||
Old content scripts are not automatically stopped when an extension updates and reloads. Often, this leads to "Invalidated context" errors in production when a content script from an old version of your extension tries to use a web extension API (ie, the `browser` or `chrome` globals).
|
||||
|
||||
WXT provides a utility for handling this process: `ContentScriptContext`. An instance of this class is provided to you automatically inside the `main` function of each content script.
|
||||
|
||||
When your extension updates or reloads, the context will become invalidated, and will trigger any `ctx.onInvalidated` listeners you add:
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
main(ctx) {
|
||||
ctx.onInvalidated(() => {
|
||||
// Do something
|
||||
});
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
The `ctx` also provides other convenient APIs for stopping your content script without manually calling `onInvalidated` to add a listener:
|
||||
|
||||
1. Setting timers:
|
||||
```ts
|
||||
ctx.setTimeout(() => { ... }, ...);
|
||||
ctx.setInterval(() => { ... }, ...);
|
||||
ctx.requestAnimationFrame(() => { ... });
|
||||
```
|
||||
1. Adding DOM events:
|
||||
```ts
|
||||
ctx.addEventListener(window, "mousemove", (event) => { ... });
|
||||
```
|
||||
1. Implements [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) for canceling standard APIs:
|
||||
```ts
|
||||
fetch('...', {
|
||||
signal: ctx.signal,
|
||||
});
|
||||
```
|
||||
|
||||
Other WXT APIs require a `ctx` object so they can clean themselves up. For example, [`createIntegratedUi`](/guide/content-script-ui#integrated), [`createShadowRootUi`](/guide/content-script-ui#shadow-root), and [`createIframeUi`](/guide/content-script-ui#iframe) automatically unmount and stop a UI when the script is invalidated.
|
||||
|
||||
:::warning
|
||||
When working with content scripts, **you should always use the `ctx` object to stop any async or future work.**
|
||||
|
||||
This prevents old content scripts from interfering with new content scripts, and prevents error messages from being logged to the console in production.
|
||||
:::
|
||||
|
||||
## Testing Permission Changes
|
||||
|
||||
When `permissions`/`host_permissions` change during an update, depending on what exactly changed, Chrome will disable your extension until the user accepts the new permissions.
|
||||
|
||||
It is possible to test this before you release an update, but it's not a simple process:
|
||||
|
||||
1. Get 2 ZIPs of your extension, both generated by `wxt zip`. The first contains a previous version of your extension, the second contains the latest version. Make sure the second ZIP's version is higher than the first's.
|
||||
2. Unzip the two ZIP files somewhere next to each other that's easy to locate.
|
||||
3. In Chrome, open `chrome://extensions` and make sure developer mode is enabled
|
||||
4. Pack the first extension into a CRX, generating a new private key:
|
||||
1. Click "Pack Extension" in the top left
|
||||
2. For "Extension root directory", enter the path to the first extracted zip directory. The directory should contain a `manifest.json` file
|
||||
3. Leave "Private key file" blank
|
||||
4. Click "Pack Extension". This will generate a `.crx` and `.pem` file
|
||||
5. Pack the second extension into a CRX, reusing the private key generated by the previous step
|
||||
1. Click "Pack Extension" in the top left
|
||||
2. For "Extension root directory", enter the path to the second extracted zip directory.
|
||||
3. For "Private key file", enter the path to the generated `.pem` private key file
|
||||
4. Click "Pack Extension". This will generate a second `.crx` file.
|
||||
6. Install the first CRX file by dragging and dropping it onto the `chrome://extensions` page
|
||||
7. Install the second CRX file by dragging and dropping it onthe the `chrome://extensions` page
|
||||
|
||||
If a new permission must be accepted, you'll be prompted to accept it after dropping the second CRX file onto the page.
|
||||
|
||||
:::Info Note
|
||||
Note: Chrome no longer allows self-signed CRX extensions to run, but that's OK for this test case. We're still prompted to accept new permissions, even if we can't interact with the installed extension.
|
||||
|
||||
To validate this, you can create a third ZIP file with a rare permission like `geolocation` in the manifest, that's guarenteed to reprompt permissions when added.
|
||||
:::
|
||||
|
||||
## Update Event
|
||||
|
||||
You can setup a callback that runs after your extension updates like so:
|
||||
|
||||
```ts
|
||||
browser.runtime.onInstalled.addEventListener(({ reason }) => {
|
||||
if (reason === 'update') {
|
||||
// Do something
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
If the logic is simple, write a unit test to cover this logic. If you feel the need to manually test this callback, you can either:
|
||||
|
||||
1. In dev mode, remove the `if` statement and reload the extension from `chrome://extensions`
|
||||
2. Build two ZIPs with the same runtime ID and actually update the extension
|
||||
|
||||
The first approach is very straightforward. The second is more complicated...
|
||||
|
||||
Here are the steps:
|
||||
|
||||
So the steps:
|
||||
|
||||
1. Checkout an old commit.
|
||||
2. [Add a `key`](https://developer.chrome.com/docs/extensions/reference/manifest/key#keep-consistent-id) to the `manifest` in your `wxt.config.ts`.
|
||||
3. Run `wxt zip` to create the first ZIP.
|
||||
4. Stash or reset changes and checkout your latest code.
|
||||
5. Add back the same `key` to your manifest.
|
||||
6. Make sure the extension's version is higher than the first zip. It can be any version that's higher, since you won't be releasing this version.
|
||||
7. Run `wxt zip` to create the second ZIP.
|
||||
8. In a fresh chrome profile, go to `chrome://extensions`, enable dev mode, and drag and drop the first zip onto the page to install it.
|
||||
9. In the extension, play around and setup your test case.
|
||||
10. Back on `chrome://extensions`, drag and drop your second zip onto the page.
|
||||
|
||||
If you setup the `key` correctly, it will cause the extension to act like it was updated instead of installing a second version of your extension.
|
||||
@@ -0,0 +1,164 @@
|
||||
# Installation
|
||||
|
||||
Bootstrap a new project, start from scratch, or [migrate an existing project](/guide/migrate-to-wxt).
|
||||
|
||||
## Bootstrap Project
|
||||
|
||||
:::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm dlx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bunx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
There are several starting templates available.
|
||||
|
||||
| TypeScript |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| <Icon name="TypeScript" /> [`vanilla`](https://github.com/wxt-dev/wxt/tree/main/templates/vanilla) |
|
||||
| <Icon name="Vue" /> [`vue`](https://github.com/wxt-dev/wxt/tree/main/templates/vue) |
|
||||
| <Icon name="React" /> [`react`](https://github.com/wxt-dev/wxt/tree/main/templates/react) |
|
||||
| <Icon name="Svelte" /> [`svelte`](https://github.com/wxt-dev/wxt/tree/main/templates/svelte) |
|
||||
| <Icon name="Solid" icon="https://www.solidjs.com/img/favicons/favicon-32x32.png" /> [`solid`](https://github.com/wxt-dev/wxt/tree/main/templates/solid) |
|
||||
|
||||
:::info
|
||||
All templates default to TypeScript. Rename the file extensions to `.js` to use JavaScript instead.
|
||||
:::
|
||||
|
||||
## From Scratch
|
||||
|
||||
Create a new NPM project:
|
||||
|
||||
:::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
mkdir project-name
|
||||
cd project-name
|
||||
pnpm init
|
||||
echo 'shamefully-hoist=true' >> .npmrc
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
mkdir project-name
|
||||
cd project-name
|
||||
npm init
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
mkdir project-name
|
||||
cd project-name
|
||||
yarn init
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
mkdir project-name
|
||||
cd project-name
|
||||
bun init
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Then install `wxt`:
|
||||
|
||||
:::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add -D wxt
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm i --save-dev wxt
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add --dev wxt
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add --dev wxt
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Add your first entrypoint:
|
||||
|
||||
```ts
|
||||
// entrypoints/background.ts
|
||||
export default defineBackground(() => {
|
||||
console.log(`Hello from ${browser.runtime.id}!`);
|
||||
});
|
||||
```
|
||||
|
||||
Finally, add scripts to your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "wxt", // [!code ++]
|
||||
"dev:firefox": "wxt --browser firefox", // [!code ++]
|
||||
"build": "wxt build", // [!code ++]
|
||||
"build:firefox": "wxt build --browser firefox", // [!code ++]
|
||||
"zip": "wxt zip", // [!code ++]
|
||||
"zip:firefox": "wxt zip --browser firefox", // [!code ++]
|
||||
"postinstall": "wxt prepare" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Migrate an Existing Project
|
||||
|
||||
Before starting the migration, it is recommended to run `pnpm dlx wxt@latest init` to see what a basic project looks like. Once you have an understanding of how WXT projects are structured, you're ready to convert the project, using the initialized project as a reference.
|
||||
|
||||
Migrating a project to WXT comes down to a few steps:
|
||||
|
||||
1. Install WXT and remove any old build tools: `pnpm i -D wxt`
|
||||
1. Refactoring/move your entrypoints to the `entrypoints` directory
|
||||
1. Moving public assets to the `public` directory
|
||||
1. Update the dev and build scripts to use WXT
|
||||
1. Ensure project is compatible with Vite, which is used under the hood to bundle your extension
|
||||
|
||||
:::info
|
||||
Since projects vary greatly in setup, [start a discussion on GitHub](https://github.com/wxt-dev/wxt/discussions/new/choose) if you need help migrating your project to WXT.
|
||||
:::
|
||||
|
||||
## Development
|
||||
|
||||
Once you've installed WXT, you can start the development server using the `dev` script.
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
:::tip 🎉 Well done!
|
||||
|
||||
The dev command will build the extension for development, open the browser, and reload the different parts of the extension when you save changes.
|
||||
:::
|
||||
|
||||
:::details Development Manifest
|
||||
When running the dev command, WXT will make several changes to your `manifest.json` to improve your development experience:
|
||||
|
||||
- If missing, add a background script/service worker to enable fast reloads
|
||||
- Add several `permissions` and `host_permissions` to enable HMR and fast reloads
|
||||
- Modify the CSP to allow connections with the dev server
|
||||
- Remove `content_scripts` and register them at runtime so they can be easily reloaded when you save a file
|
||||
|
||||
If you're an experienced web extension developer and think the dev manifest looks wrong, this is why. Run a production build with `wxt build` to see the unmodified `manifest.json`.
|
||||
:::
|
||||
|
||||
## Next Steps
|
||||
|
||||
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 perform common tasks with WXT
|
||||
@@ -1,19 +1,26 @@
|
||||
---
|
||||
head:
|
||||
- - link
|
||||
- rel: canonical
|
||||
href: https://wxt.dev
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
WXT is a free and open source framework for building web extensions in an conventional, intuative, and safe way **_for all browsers_**.
|
||||
WXT is a free and open source framework for building web extensions in an conventional, intuitive, and safe way **_for all browsers_**.
|
||||
|
||||
WXT comes with full TypeScript support and auto-imports. Sounds familiar? That's right, **_WXT was based off of Nuxt_** and aims to provide the same greate DX and features.
|
||||
WXT is based of [Nuxt](https://nuxt.com), and aims to provide the same great DX with TypeScript, auto-imports, and an opinionated project structure.
|
||||
|
||||

|
||||
|
||||
## Conventions
|
||||
|
||||
WXT is an optionated framework. This helps keep projects consistent and easy to pick up.
|
||||
WXT is an opinionated framework. This helps keep projects consistent and easy to pick up.
|
||||
|
||||
- **Generated manifest**: Based on your project's file structure
|
||||
- **Entrypoint configuration**: Configure entrypoints from the same file they're declare in
|
||||
- **Type-safety is a priority**: Out-of-the-box TypeScript support with improved browser API typing
|
||||
- **Simple output file structure**: Ouptut file paths minimize the path at runtime
|
||||
- **Simple output file structure**: Output file paths minimize the path at runtime
|
||||
|
||||
## Development
|
||||
|
||||
@@ -28,5 +35,5 @@ Production builds are optimized for store review, changing as few files as possi
|
||||
In addition, WXT fully supports Firefox's source code requirements when using a bundler. It will automatically create and upload a ZIP file of your source code.
|
||||
|
||||
:::info
|
||||
See [Publishing](./publishing.md) for more info around production builds.
|
||||
See [Publishing](./publishing) for more info around production builds.
|
||||
:::
|
||||
@@ -1,12 +1,17 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Manifest.json
|
||||
|
||||
The manifest.json is generated at build-time based on files in your `entrypoints` directory and your `wxt.config.ts`.
|
||||
The manifest.json is generated at build-time based on files in the `entrypoints/` directory and `wxt.config.ts`.
|
||||
|
||||
## Customization
|
||||
## Configuration
|
||||
|
||||
While entrypoints are generated and added to the manifest at build-time, you can customize or add to your `manifest.json` in the config file.
|
||||
|
||||
```ts
|
||||
// wxt.config.ts entrypoint of your extension
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
@@ -19,18 +24,20 @@ export default defineConfig({
|
||||
});
|
||||
```
|
||||
|
||||
## `name`
|
||||
### `name`
|
||||
|
||||
If not provided via the `manifest` config, the [manifest's `name`](https://developer.chrome.com/docs/extensions/mv3/manifest/name/) defaults to your package.json's `name` property.
|
||||
|
||||
## `version` and `version_name`
|
||||
### `version` and `version_name`
|
||||
|
||||
The [manifest's `version` and `version_name`](https://developer.chrome.com/docs/extensions/mv3/manifest/version/) fields are based on your package.json's `version` property.
|
||||
The [manifest's `version` and `version_name`](https://developer.chrome.com/docs/extensions/mv3/manifest/version/) properties are based on the `version` field listed in your `package.json` or `wxt.config.ts`.
|
||||
|
||||
- `version_name` is the exact string listed in your package.json
|
||||
- `version_name` is the exact string listed in your `package.json` or `wxt.config.ts` file
|
||||
- `version` is the string cleaned up, with any invalid suffixes removed
|
||||
|
||||
### Example
|
||||
If a version is not found, a warning is logged and the version defaults to `"0.0.0"`.
|
||||
|
||||
#### Example
|
||||
|
||||
```json
|
||||
// package.json
|
||||
@@ -49,7 +56,7 @@ The [manifest's `version` and `version_name`](https://developer.chrome.com/docs/
|
||||
|
||||
### `icons`
|
||||
|
||||
The [manifest's `icons`](https://developer.chrome.com/docs/extensions/mv3/manifest/icons/) property needs to be set in the config file. The files should be added to WXT's [`public` directory](/get-started/assets#public-directory).
|
||||
By default, WXT will discover icons in your [`public` directory](/guide/assets#public-directory) and use them for the [manifest's `icons`](https://developer.chrome.com/docs/extensions/mv3/manifest/icons/).
|
||||
|
||||
```
|
||||
public/
|
||||
@@ -60,21 +67,27 @@ public/
|
||||
└─ icon-128.png
|
||||
```
|
||||
|
||||
Icon files need to match the following regex to be automatically included in the manifest. Most design software can output icons in one of these formats
|
||||
|
||||
<<< @/../packages/wxt/src/core/utils/manifest.ts#snippet
|
||||
|
||||
If you prefer to use filenames in a different format, you can add the icons manually in your `wxt.config.ts` file:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
icons: {
|
||||
16: '/icon-16.png',
|
||||
24: '/icon-24.png',
|
||||
48: '/icon-48.png',
|
||||
96: '/icon-96.png',
|
||||
128: '/icon-128.png',
|
||||
16: '/extension-icon-16.png',
|
||||
24: '/extension-icon-24.png',
|
||||
48: '/extension-icon-48.png',
|
||||
96: '/extension-icon-96.png',
|
||||
128: '/extension-icon-128.png',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Permissions
|
||||
### `permissions`
|
||||
|
||||
[Permissions](https://developer.chrome.com/docs/extensions/reference/permissions/) must be listed in the manifest config.
|
||||
|
||||
@@ -86,9 +99,9 @@ export default defineConfig({
|
||||
});
|
||||
```
|
||||
|
||||
### Localization
|
||||
## Localization
|
||||
|
||||
Similar to the icon, the [`_locales` directory](https://developer.chrome.com/docs/extensions/reference/i18n/) should be placed inside the the WXT's [`public` directory](/get-started/assets#public-directory).
|
||||
Similar to the icon, the [`_locales` directory](https://developer.chrome.com/docs/extensions/reference/i18n/) should be placed inside the the WXT's [`public` directory](/guide/assets#public-directory).
|
||||
|
||||
```
|
||||
public/
|
||||
@@ -112,3 +125,113 @@ export default defineConfig({
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
See the official localization examples for more details:
|
||||
|
||||
<ExampleList tag="i18n" />
|
||||
|
||||
## Per-Manifest Version Config
|
||||
|
||||
WXT applies several transformations to your manifest to simplify managing both MV2 and MV3 keys in your `wxt.config.ts` file:
|
||||
|
||||
1. Top level MV2-only or MV3-only keys are stripped from the final manifest when targeting the other manifest version
|
||||
2. Some keys, are automatically converted between versions when possible:
|
||||
- Define `web_accessible_resources` in it's MV3 style and it will be converted to the MV2 style automatically
|
||||
- `action` will automatically be converted to `browser_action` for MV3. To use `page_action` instead, add both `action` and `page_action` entries to your manifest
|
||||
|
||||
For example, a `wxt.config.ts` file that looks like this:
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
mainfest: {
|
||||
action: {
|
||||
default_title: 'Some Title',
|
||||
},
|
||||
web_accessible_resources: [
|
||||
{
|
||||
matches: ['*://*.google.com/*'],
|
||||
resources: ['icon/*.png'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Will be output differently for each manifest version:
|
||||
|
||||
:::code-group
|
||||
|
||||
```json [MV2]
|
||||
{
|
||||
"manifest_version": 2,
|
||||
// ...
|
||||
"browser_action": {
|
||||
"default_title": "Some Title"
|
||||
},
|
||||
"web_accessible_resources": ["icon/*.png"]
|
||||
}
|
||||
```
|
||||
|
||||
```json [MV3]
|
||||
{
|
||||
"manifest_version": 3,
|
||||
// ...
|
||||
"action": {
|
||||
"default_title": "Some Title"
|
||||
},
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"matches": ["*://*.google.com/*"],
|
||||
"resources": ["icon/*.png"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::tip
|
||||
If this isn't enough control for your use-case, remember you can use a function for the `manifest` key and generate it however you'd like, or you can use the `build:manifestGenerated` hook to apply additional transformations.
|
||||
:::
|
||||
|
||||
## Per-Browser Configuration
|
||||
|
||||
The `manifest` field can be a function. If you are building and extension for multiple browsers, and need to modify the manifest per browser, using a function instead of an object is very useful.
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: ({ browser, manifestVersion, mode, command }) => {
|
||||
return {
|
||||
// Your manifest
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::info
|
||||
The first argument is of type `ConfigEnv`. See the [API reference](/api/wxt/interfaces/ConfigEnv) for info about each property.
|
||||
:::
|
||||
|
||||
For example, say you use OAuth, and you need to provide a different `oauth.client_id` for each browser:
|
||||
|
||||
```ts
|
||||
const clientIds = {
|
||||
chrome: '<your-chrome-client-id>',
|
||||
edge: '<your-edge-client-id>',
|
||||
firefox: '<your-firefox-client-id>',
|
||||
opera: '<your-opera-client-id>',
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
manifest: ({ browser }) => ({
|
||||
oauth: {
|
||||
client_id: clientIds[browser],
|
||||
scopes: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Migrate to WXT
|
||||
|
||||
> If you have problems migrating to WXT, feel free to ask for help in GitHub by [starting a discussion](https://github.com/wxt-dev/wxt/discussions/new?category=q-a)!
|
||||
|
||||
## Overview
|
||||
|
||||
Always start by generating a new vanilla project and merging it into your project one file at a time.
|
||||
|
||||
```sh
|
||||
cd path/to/your/project
|
||||
pnpm dlx wxt@latest init example-wxt --template vanilla
|
||||
```
|
||||
|
||||
In general, you'll need to:
|
||||
|
||||
<input type="checkbox" /> Install `wxt`<br />
|
||||
<input type="checkbox" /> Update/create `package.json` scripts to use `wxt` (don't forget about `postinstall`)<br />
|
||||
<input type="checkbox" /> Move entrypoints into `entrypoints/` directory<br />
|
||||
<input type="checkbox" /> Move assets into either the `assets/` or `public/` directories<br />
|
||||
<input type="checkbox" /> Move manifest.json content into `wxt.config.ts`<br />
|
||||
<input type="checkbox" /> Convert custom import syntax to be compatible with Vite<br />
|
||||
<input type="checkbox" /> Add a default export to JS entrypoints<br />
|
||||
<input type="checkbox" /> Use the `browser` global instead of `chrome`<br />
|
||||
<input type="checkbox" /> Compare final `manifest.json` files, making sure permissions and host permissions are unchanged<br />
|
||||
<input type="checkbox" /> Extension output by `wxt build` works the same way as before the migration<br />
|
||||
|
||||
Every project is different, so there's no one-solution-fits-all to migrating your project. Just make sure `wxt dev` runs, `wxt build` results in a working extension, and the list of permissions in the `manifest.json` hasn't changed. If all that looks good, you've finished migrating your extension!
|
||||
|
||||
## Popular Tools/Frameworks
|
||||
|
||||
Here's specific steps for other popular frameworks/build tools.
|
||||
|
||||
### `vite-plugin-web-extension`
|
||||
|
||||
Since you're already using Vite, it's a simple refactor.
|
||||
|
||||
1. Install `wxt`
|
||||
2. Move and refactor your entrypoints to WXT's style (with a default export)
|
||||
3. Update package.json scripts to use `wxt`
|
||||
4. Add `"postinstall": "wxt prepare"` script
|
||||
5. Move the `manifest.json` into `wxt.config.ts`
|
||||
6. Move any custom settings from `vite.config.ts` into `wxt.config.ts`'s
|
||||
7. Compare `dist/manifest.json` to `.output/*/manifest.json`, they should have the same content as before. If not, tweak your entrypoints and config to get as close as possible.
|
||||
|
||||
### `plasmo`
|
||||
|
||||
1. Install `wxt`
|
||||
2. Move entrypoints into `entrypoints/` directory, merging the named exports used to configure your JS entrypoints into WXT's default export
|
||||
3. Move public `assets/*` into the `public/` directory
|
||||
4. If you use CSUI, migrate to WXT's `createContentScriptUi`
|
||||
5. Convert Plasmo's custom import resolutions to Vite's
|
||||
6. If importing remote code via a URL, add a `url:` prefix so it works with WXT
|
||||
7. Compare your output `manifest.json` files from before the migration to after the migration. They should have the same content. If not, tweak your entrypoints and config to get as close as possible.
|
||||
@@ -0,0 +1,133 @@
|
||||
# Multiple Browsers
|
||||
|
||||
You can build an extension for any combination of browser and manifest version. Different browsers and manifest versions support different APIs and entrypoints, so be sure to check that your extension functions as expected for each target.
|
||||
|
||||
Separate build targets are written to their own output directories:
|
||||
|
||||
```
|
||||
<rootDir>
|
||||
└─ .output
|
||||
├─ chrome-mv3
|
||||
├─ firefox-mv2
|
||||
├─ edge-mv3
|
||||
└─ ...
|
||||
```
|
||||
|
||||
## Target Browser
|
||||
|
||||
To build for a specific browser, pass the `-b --browser` flag from the CLI:
|
||||
|
||||
```sh
|
||||
wxt --browser firefox
|
||||
wxt build --browser firefox
|
||||
```
|
||||
|
||||
By default, it will build for `chrome`. When excluding the [manifest version flags](#target-manifest-version), it will default to the commonly accepted manifest version for that browser.
|
||||
|
||||
| Browser | Default Manifest Version |
|
||||
| ---------------- | :----------------------: |
|
||||
| `chrome` | 3 |
|
||||
| `firefox` | 2 |
|
||||
| `safari` | 2 |
|
||||
| `edge` | 3 |
|
||||
| Any other string | 3 |
|
||||
|
||||
:::tip
|
||||
To configure which browser is opened when running dev mode via `wxt -b <browser>`, see the [Development docs](/guide/development#configure-browser-startup) docs.
|
||||
:::
|
||||
|
||||
## Target Manifest Version
|
||||
|
||||
To build for a specific manifest version, pass either the `--mv2` flag or `--mv3` flag from the CLI.
|
||||
|
||||
```sh
|
||||
wxt --mv2
|
||||
wxt build --mv2
|
||||
```
|
||||
|
||||
When the `-b --browser` flag is not passed, it defaults to `chrome`. So here, we're targeting MV2 for Chrome.
|
||||
|
||||
## Customizing Entrypoints
|
||||
|
||||
There are several ways to customize entrypoint definitions per browser.
|
||||
|
||||
First, you can use either the `include` or `exclude` option to include or exclude the entrypoint from specific browsers. Here are some examples
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [Background]
|
||||
export default defineBackground({
|
||||
// Only include a background script when targeting chrome
|
||||
include: ['chrome'],
|
||||
});
|
||||
```
|
||||
|
||||
```ts [Content Script]
|
||||
export default defineContentScript({
|
||||
// Do not add this content script to the manifest when targeting firefox
|
||||
exclude: ['firefox'],
|
||||
});
|
||||
```
|
||||
|
||||
```html [HTML page]
|
||||
<!-- entrypoints/options.html -->
|
||||
<html>
|
||||
<head>
|
||||
<!-- Don't include the options page for safari -->
|
||||
<meta name="manifest.exclude" content="['safari']" />
|
||||
</head>
|
||||
</html>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Second, you can change individual options per-browser:
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [Background]
|
||||
export default defineBackground({
|
||||
persistent: {
|
||||
// Use a non-persistent background script for just safari
|
||||
safari: false,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts [Content Script]
|
||||
export default defineContentScript({
|
||||
matches: {
|
||||
// Run the content script on different pages for each browser
|
||||
chrome: ['*://*.google.com/*'],
|
||||
firefox: ['*://*.duckduckgo.com/*'],
|
||||
edge: ['*://*.bing.com/*'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::warning
|
||||
Only `defineBackground` and `defineContentScript` support per-browser options right now.
|
||||
:::
|
||||
|
||||
## Runtime
|
||||
|
||||
To determine the browser or manifest version at runtime, you can use any of the below variables:
|
||||
|
||||
- `import.meta.env.BROWSER`: A string, the target browser, usually equal to the `--browser` flag
|
||||
- `import.meta.env.MANIFEST_VERSION`: A number, either `2` or `3`, depending on the manifest version targeted
|
||||
- `import.meta.env.CHROME`: A boolean equivalent to `import.meta.env.BROWSER === "chrome"`
|
||||
- `import.meta.env.FIREFOX`: A boolean equivalent to `import.meta.env.BROWSER === "firefox"`
|
||||
- `import.meta.env.EDGE`: A boolean equivalent to `import.meta.env.BROWSER === "edge"`
|
||||
- `import.meta.env.SAFARI`: A boolean equivalent to `import.meta.env.BROWSER === "safari"`
|
||||
- `import.meta.env.OPERA`: A boolean equivalent to `import.meta.env.BROWSER === "opera"`
|
||||
- `import.meta.env.COMMAND`: A string, `"serve"` when running `wxt` for development or `"build"` in all other cases.
|
||||
|
||||
:::info
|
||||
These variables are constants defined at build time based on the build target. They do not actually detect which browser the code is running in.
|
||||
|
||||
For example, if you build for `--browser chrome` and publish it on Edge, `import.meta.env.BROWSER` will be `"chrome"`, not `"edge"`. You have to build a separate ZIP for `--browser edge` before `import.meta.env.BROWSER` will be `"edge"`.
|
||||
|
||||
If you need to know the actual browser your code is being ran on, you should use a [user agent parser](https://www.npmjs.com/package/ua-parser-js).
|
||||
:::
|
||||
@@ -0,0 +1,238 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Publishing
|
||||
|
||||
WXT will help you ZIP your extensions and submit them to the stores for review.
|
||||
|
||||
## First Time Publishing
|
||||
|
||||
If you're publishing an extension to a store for the first time, you must manually navigate the process. WXT doesn't help you create listings, each store has unique steps and requirements that you need to familiarize yourself with.
|
||||
|
||||
For specific details about each store, see the stores sections below.
|
||||
|
||||
- [Chrome Web Store](#chrome-web-store)
|
||||
- [Firefox Addon Store](#firefox-addon-store)
|
||||
- [Edge Addons](#edge-addons)
|
||||
|
||||
## Automation
|
||||
|
||||
WXT provides two commands to help automate the release process:
|
||||
|
||||
- `wxt submit`: Submit new versions of your extension for review (and publish them automatically once approved)
|
||||
- `wxt submit init`: Help setup all the required secrets and options for the `wxt submit` command
|
||||
|
||||
To get started, run `wxt submit init` and follow the prompts. Once finished, you should have a `.env.submit` file! WXT will use this file to submit your updates.
|
||||
|
||||
> In CI, make sure you add all the environment variables to the submit step.
|
||||
|
||||
To release an update, build all the ZIPs you plan on releasing:
|
||||
|
||||
```sh
|
||||
wxt zip
|
||||
wxt zip -b firefox
|
||||
```
|
||||
|
||||
Then run the `wxt submit` command, passing in all the ZIP files you want to release. In this case, we'll do a release for all 3 major stores: Chrome Web Store, Edge Addons, and Firefox Addons Store.
|
||||
|
||||
If it's your first time running the command, you'll want to test your secrets by passing the `--dry-run` flag:
|
||||
|
||||
```sh
|
||||
wxt submit --dry-run \
|
||||
--chrome-zip .output/<your-extension>-<version>-chrome.zip \
|
||||
--firefox-zip .output/<your-extension>-<version>-firefox.zip --firefox-sources-zip .output/<your-extension>-<version>-sources.zip \
|
||||
--edge-zip .output/<your-extension>-<version>-chrome.zip
|
||||
```
|
||||
|
||||
If the dry run passes, remove the flag and do the actual release:
|
||||
|
||||
```sh
|
||||
wxt submit \
|
||||
--chrome-zip .output/<your-extension>-<version>-chrome.zip \
|
||||
--firefox-zip .output/<your-extension>-<version>-firefox.zip --firefox-sources-zip .output/<your-extension>-<version>-sources.zip \
|
||||
--edge-zip .output/<your-extension>-<version>-chrome.zip
|
||||
```
|
||||
|
||||
:::tip
|
||||
If you only need to release to a single store, only pass that store's ZIP flag.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
See the [Firefox Addon Store](#firefox-addon-store) section for more details about the `--firefox-sources-zip` option.
|
||||
:::
|
||||
|
||||
## GitHub Action
|
||||
|
||||
Here's an example of a GitHub Action that submits new versions of an extension for review. Ensure that you've added all required secrets used in the workflow to the repo's settings.
|
||||
|
||||
```yml
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
submit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 'latest'
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Zip extensions
|
||||
run: |
|
||||
pnpm zip
|
||||
pnpm zip:firefox
|
||||
|
||||
- name: Submit to stores
|
||||
run: |
|
||||
pnpm wxt submit \
|
||||
--chrome-zip .output/*-chrome.zip \
|
||||
--firefox-zip .output/*-firefox.zip --firefox-sources-zip .output/*-sources.zip
|
||||
env:
|
||||
CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
||||
CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
|
||||
CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||
CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||
FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }}
|
||||
FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }}
|
||||
FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }}
|
||||
```
|
||||
|
||||
The action above lays the foundation for a basic workflow, including `zip` and `submit` steps. To further enhance your GitHub Action and delve into more complex scenarios, consider exploring the following examples from real projects. They introduce advanced features such as version management, changelog generation, and GitHub releases, tailored for different needs:
|
||||
|
||||
- [`aklinker1/github-better-line-counts`](https://github.com/aklinker1/github-better-line-counts/blob/main/.github/workflows/submit.yml) - Conventional commits, automated version bump and changelog generation, triggered manually, optional dry run for testing
|
||||
- [`GuiEpi/plex-skipper`](https://github.com/GuiEpi/plex-skipper/blob/main/.github/workflows/deploy.yml) - Triggered automatically when `package.json` version is changed, creates and uploads artifacts to GitHub release.
|
||||
|
||||
> These examples are designed to provide clear insights and are a good starting point for customizing your own workflows. Feel free to explore and adapt them to your project needs.
|
||||
|
||||
## Stores
|
||||
|
||||
### Chrome Web Store
|
||||
|
||||
> ✅ Supported • [Developer Dashboard](https://chrome.google.com/webstore/developer/dashboard) • [Publishing Docs](https://developer.chrome.com/docs/webstore/publish/)
|
||||
|
||||
To create a ZIP for Chrome:
|
||||
|
||||
```sh
|
||||
wxt zip
|
||||
```
|
||||
|
||||
### Firefox Addon Store
|
||||
|
||||
> ✅ Supported • [Developer Dashboard](https://addons.mozilla.org/developers/) • [Publishing Docs](https://extensionworkshop.com/documentation/publish/submitting-an-add-on/)
|
||||
|
||||
Firefox requires you to upload a ZIP of your source code. This allows them to rebuild your extension and review the code in a readable way. More details can be found in [Firefox's docs](https://extensionworkshop.com/documentation/publish/source-code-submission/).
|
||||
|
||||
WXT fully supports generating and automatically submitting a source code ZIP.
|
||||
|
||||
When you run `wxt zip -b firefox`, your sources are zipped into the `.output` directory alongside the extension. WXT will automatically exclude certain files such as config files, hidden files, and tests. However, it's important to manually check the ZIP to ensure it only contains the files necessary to rebuild your extension.
|
||||
|
||||
To customize which files are zipped, add the `zip` option to your config file.
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
zip: {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
If it's your first time submitting to the Firefox Addon Store, or if you've updated your project layout, always test your sources ZIP! The commands below should allow you to rebuild your extension from inside the extracted ZIP.
|
||||
|
||||
:::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm i
|
||||
pnpm zip:firefox
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm i
|
||||
npm run zip:firefox
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn
|
||||
yarn zip:firefox
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun i
|
||||
bun zip:firefox
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Make sure the build output is the exact same when running `wxt build -b firefox` in your main project and inside the zipped sources.
|
||||
|
||||
:::warning
|
||||
If you use a `.env` files, they can effect the chunk hashes in the output directory. Either delete the .env file before running `wxt zip -b firefox`, or include it in your sources zip with the [`zip.includeSources`](/api/wxt/interfaces/InlineConfig#includesources) option. Be careful to not include any secrets in your `.env` files.
|
||||
|
||||
See Issue [#377](https://github.com/wxt-dev/wxt/issues/377) for more details.
|
||||
:::
|
||||
|
||||
Ensure that you have a `README.md` or `SOURCE_CODE_REVIEW.md` file with the above commands so that the Firefox team knows how to build your extension.
|
||||
|
||||
#### Private Packages
|
||||
|
||||
If you use private packages and you don't want to provide your auth token to the Firefox team during the review process, you can use `zip.downloadPackages` to download any private packages and include them in the zip.
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
zip: {
|
||||
downloadPackages: [
|
||||
'@mycompany/some-package',
|
||||
//...
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Depending on your package manager, the `package.json` in the sources zip will be modified to use the downloaded dependencies via the `overrides` or `resolutions` field.
|
||||
|
||||
:::warning
|
||||
WXT uses the command `npm pack <package-name>` to download the package. That means regardless of your package manager, you need to properly setup a `.npmrc` file. NPM and PNPM both respect `.npmrc` files, but Yarn and Bun have their own ways of authorizing private registries, so you'll need to add an `.npmrc` file.
|
||||
:::
|
||||
|
||||
### Safari
|
||||
|
||||
> 🚧 Not supported yet
|
||||
|
||||
WXT does not currently support automated publishing for Safari. Safari extensions require a native MacOS or iOS app wrapper, which WXT does not create yet. For now, if you want to publish to Safari, follow this guide:
|
||||
|
||||
- [Converting a web extension for Safari](https://developer.apple.com/documentation/safariservices/safari_web_extensions/converting_a_web_extension_for_safari) - "Convert your existing extension to a Safari web extension using Xcode’s command-line tool."
|
||||
|
||||
When running the `safari-web-extension-converter` CLI tool, pass the `.output/safari-mv2` or `.output/safari-mv3` directory, not your source code directory.
|
||||
|
||||
```sh
|
||||
pnpm wxt build -b safari
|
||||
xcrun safari-web-extension-converter .output/safari-mv2
|
||||
```
|
||||
|
||||
### Edge Addons
|
||||
|
||||
> ✅ Supported • [Developer Dashboard](https://aka.ms/PartnerCenterLogin) • [Publishing Docs](https://learn.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/publish-extension)
|
||||
|
||||
No need to create a specific ZIP for Edge. If you're already publishing to the Chrome Web Store, you can reuse your Chrome ZIP.
|
||||
|
||||
However, if you have features specifically for Edge, create a separate ZIP with:
|
||||
|
||||
```sh
|
||||
wxt zip -b edge
|
||||
```
|
||||
@@ -0,0 +1,31 @@
|
||||
# Remote Code
|
||||
|
||||
WXT will automatically download and bundle imports with the `url:` prefix so the extension does not depend of remote code, [a requirement from Google for MV3](https://developer.chrome.com/docs/extensions/migrating/improve-security/#remove-remote-code).
|
||||
|
||||
## Google Analytics
|
||||
|
||||
For example, you can import google analytics:
|
||||
|
||||
```ts
|
||||
// utils/google-analytics.ts
|
||||
import 'url:https://www.googletagmanager.com/gtag/js?id=G-XXXXXX';
|
||||
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
// NOTE: This line is different from Google's documentation
|
||||
window.gtag = function () {
|
||||
dataLayer.push(arguments);
|
||||
};
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-XXXXXX');
|
||||
```
|
||||
|
||||
Then you can import this in your HTML files to enable Google Analytics:
|
||||
|
||||
```ts
|
||||
// popup/main.ts
|
||||
import '~/utils/google-analytics';
|
||||
|
||||
gtag('event', 'event_name', {
|
||||
key: 'value',
|
||||
});
|
||||
```
|
||||
@@ -1,32 +0,0 @@
|
||||
# Sandbox
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/sandbox/)
|
||||
|
||||
:::tip Chromium Only
|
||||
Firefox does not support sandboxed pages.
|
||||
:::
|
||||
|
||||
## Filenames
|
||||
|
||||
- `entrypoints/sandbox.html`
|
||||
- `entrypoints/<name>.sandbox.html`
|
||||
- `entrypoints/sandbox/index.html`
|
||||
- `entrypoints/<name>.sandbox/index.html`
|
||||
|
||||
## Definition
|
||||
|
||||
Plain old HTML file.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -1,32 +0,0 @@
|
||||
# Side Panel
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/sidePanel/)
|
||||
|
||||
:::tip Chromium Only
|
||||
Firefox does not support sandboxed pages.
|
||||
:::
|
||||
|
||||
## Filenames
|
||||
|
||||
- `entrypoints/sidepanel.html`
|
||||
- `entrypoints/<name>.sidepanel.html`
|
||||
- `entrypoints/sidepanel/index.html`
|
||||
- `entrypoints/<name>.sidepanel/index.html`
|
||||
|
||||
## Definition
|
||||
|
||||
Plain old HTML file.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,281 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Storage API
|
||||
|
||||
WXT provides a simplified API to replace the `browser.storage.*` APIs. Use the `storage` auto-import from `wxt/storage` or import it manually to get started:
|
||||
|
||||
```ts
|
||||
import { storage } from 'wxt/storage';
|
||||
```
|
||||
|
||||
:::warning
|
||||
To use the `wxt/storage` API, the `"storage"` permission must be added to the manifest:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ['storage'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
More info on permissions [here](/guide/manifest#permissions).
|
||||
:::
|
||||
|
||||
[[toc]]
|
||||
|
||||
## Basic Usage
|
||||
|
||||
All storage keys must be prefixed by their storage area.
|
||||
|
||||
```ts
|
||||
// ❌ This will throw an error
|
||||
await storage.getItem('installDate');
|
||||
|
||||
// ✅ This is good
|
||||
await storage.getItem('local:installDate');
|
||||
```
|
||||
|
||||
You can use `local:`, `session:`, `sync:`, or `managed:`.
|
||||
|
||||
If you use TypeScript, you can add a type parameter to most methods to specify the expected type of the key's value:
|
||||
|
||||
```ts
|
||||
await storage.getItem<number>('local:installDate');
|
||||
await storage.watch<number>(
|
||||
'local:installDate',
|
||||
(newInstallDate, oldInstallDate) => {
|
||||
// ...
|
||||
},
|
||||
);
|
||||
await storage.getMeta<{ v: number }>('local:installDate');
|
||||
```
|
||||
|
||||
For a full list of methods available, see the [API reference](/api/wxt/storage/interfaces/WxtStorage).
|
||||
|
||||
## Watchers
|
||||
|
||||
To listen for storage changes, use the `storage.watch` function. It lets you setup a listener for a single key:
|
||||
|
||||
```ts
|
||||
const unwatch = storage.watch<number>('local:counter', (newCount, oldCount) => {
|
||||
console.log('Count changed:', { newCount, oldCount });
|
||||
});
|
||||
```
|
||||
|
||||
To remove the listener, call the returned `unwatch` function:
|
||||
|
||||
```ts
|
||||
const unwatch = storage.watch(...);
|
||||
|
||||
// Some time later...
|
||||
unwatch();
|
||||
```
|
||||
|
||||
## Metadata
|
||||
|
||||
`wxt/storage` also supports setting metadata for keys, stored at `key + "$"`. Metadata is a collection of properties associated with a key. It might be a version number, last modified date, etc.
|
||||
|
||||
[Other than versioning](#versioning), you are responsible for managing a field's metadata:
|
||||
|
||||
```ts
|
||||
await Promise.all([
|
||||
storage.setItem('local:preference', true),
|
||||
storage.setMeta('local:preference', { lastModified: Date.now() }),
|
||||
]);
|
||||
```
|
||||
|
||||
When setting different properties of metadata from multiple calls, the properties are combined instead of overwritten:
|
||||
|
||||
```ts
|
||||
await storage.setMeta('local:preference', { lastModified: Date.now() });
|
||||
await storage.setMeta('local:preference', { v: 2 });
|
||||
|
||||
await storage.getMeta('local:preference'); // { v: 2, lastModified: 1703690746007 }
|
||||
```
|
||||
|
||||
You can remove all metadata associated with a key, or just specific properties:
|
||||
|
||||
```ts
|
||||
// Remove all properties
|
||||
await storage.removeMeta('local:preference');
|
||||
|
||||
// Remove one property
|
||||
await storage.removeMeta('local:preference', 'lastModified');
|
||||
|
||||
// Remove multiple properties
|
||||
await storage.removeMeta('local:preference', ['lastModified', 'v']);
|
||||
```
|
||||
|
||||
## Defining Storage Items
|
||||
|
||||
Writing the key and type parameter for the same key over and over again can be annoying. As an alternative, you can use `storage.defineItem` to create a "storage item".
|
||||
|
||||
Storage items contain the same APIs as the `storage` variable, but you can configure its type, default value, and more in a single place:
|
||||
|
||||
```ts
|
||||
// utils/storage.ts
|
||||
const showChangelogOnUpdate = storage.defineItem<boolean>(
|
||||
'local:showChangelogOnUpdate',
|
||||
{
|
||||
defaultValue: true,
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
Now, instead of using the `storage` variable, you can use the helper functions on the storage item you created:
|
||||
|
||||
```ts
|
||||
await showChangelogOnUpdate.getValue();
|
||||
await showChangelogOnUpdate.setValue(false);
|
||||
await showChangelogOnUpdate.removeValue();
|
||||
const unwatch = showChangelogOnUpdate.watch((newValue) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
For a full list of properties and methods available, see the [API reference](/api/wxt/storage/interfaces/WxtStorageItem).
|
||||
|
||||
### Versioning
|
||||
|
||||
You can add versioning to storage items if you expect them to grow or change over time. When defining the first version of an item, start with version 1.
|
||||
|
||||
For example, consider a storage item that stores a list of websites that are ignored by an extension.
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [v1]
|
||||
type IgnoredWebsiteV1 = string;
|
||||
|
||||
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV1[]>(
|
||||
'local:ignoredWebsites',
|
||||
{
|
||||
defaultValue: [],
|
||||
version: 1,
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts [v2]
|
||||
import { nanoid } from 'nanoid'; // [!code ++]
|
||||
|
||||
type IgnoredWebsiteV1 = string;
|
||||
interface IgnoredWebsiteV2 { // [!code ++]
|
||||
id: string; // [!code ++]
|
||||
website: string; // [!code ++]
|
||||
} // [!code ++]
|
||||
|
||||
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV1[]>( // [!code --]
|
||||
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>( // [!code ++]
|
||||
'local:ignoredWebsites',
|
||||
{
|
||||
defaultValue: [],
|
||||
version: 1, // [!code --]
|
||||
version: 2, // [!code ++]
|
||||
migrations: { // [!code ++]
|
||||
// Ran when migrating from v1 to v2 // [!code ++]
|
||||
2: (websites: IgnoredWebsiteV1[]): IgnoredWebsiteV2[] => { // [!code ++]
|
||||
return websites.map((website) => ({ id: nanoid(), website })); // [!code ++]
|
||||
}, // [!code ++]
|
||||
}, // [!code ++]
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts [v3]
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
type IgnoredWebsiteV1 = string;
|
||||
interface IgnoredWebsiteV2 {
|
||||
id: string;
|
||||
website: string;
|
||||
}
|
||||
interface IgnoredWebsiteV3 { // [!code ++]
|
||||
id: string; // [!code ++]
|
||||
website: string; // [!code ++]
|
||||
enabled: boolean; // [!code ++]
|
||||
} // [!code ++]
|
||||
|
||||
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>( // [!code --]
|
||||
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV3[]>( // [!code ++]
|
||||
'local:ignoredWebsites',
|
||||
{
|
||||
defaultValue: [],
|
||||
version: 2, // [!code --]
|
||||
version: 3, // [!code ++]
|
||||
migrations: {
|
||||
// Ran when migrating from v1 to v2
|
||||
2: (websites: IgnoredWebsiteV1[]): IgnoredWebsiteV2[] => {
|
||||
return websites.map((website) => ({ id: nanoid(), website }));
|
||||
},
|
||||
// Ran when migrating from v2 to v3 // [!code ++]
|
||||
3: (websites: IgnoredWebsiteV2[]): IgnoredWebsiteV3[] => { // [!code ++]
|
||||
return websites.map((website) => ({ ...website, enabled: true })); // [!code ++]
|
||||
}, // [!code ++]
|
||||
},
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::info
|
||||
Internally, this uses a metadata property called `v` to track the value's current version.
|
||||
:::
|
||||
|
||||
In this case, we thought that the ignored website list might change in the future, and were able to setup a versioned storage item from the start.
|
||||
|
||||
Realistically, you won't know a item needs versioned until you need to change it's schema. Thankfully, it's simple to add versioning to an unversioned storage item.
|
||||
|
||||
When a previous version isn't found, WXT assumes the version was `1`. That means you just need to set `version: 2` and add a migration for `2`, and it will just work!
|
||||
|
||||
Lets look at the same ignored websites example from before, but start with an unversioned item this time:
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [Unversioned]
|
||||
export const ignoredWebsites = storage.defineItem<string[]>(
|
||||
'local:ignoredWebsites',
|
||||
{
|
||||
defaultValue: [],
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts [v2]
|
||||
import { nanoid } from 'nanoid'; // [!code ++]
|
||||
|
||||
// Retroactively add a type for the first version // [!code ++]
|
||||
type IgnoredWebsiteV1 = string; // [!code ++]
|
||||
interface IgnoredWebsiteV2 { // [!code ++]
|
||||
id: string; // [!code ++]
|
||||
website: string; // [!code ++]
|
||||
} // [!code ++]
|
||||
|
||||
export const ignoredWebsites = storage.defineItem<string[]>( // [!code --]
|
||||
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>( // [!code ++]
|
||||
'local:ignoredWebsites',
|
||||
{
|
||||
defaultValue: [],
|
||||
version: 2, // [!code ++]
|
||||
migrations: { // [!code ++]
|
||||
// Ran when migrating from v1 to v2 // [!code ++]
|
||||
2: (websites: IgnoredWebsiteV1[]): IgnoredWebsiteV2[] => { // [!code ++]
|
||||
return websites.map((website) => ({ id: nanoid(), website })); // [!code ++]
|
||||
}, // [!code ++]
|
||||
}, // [!code ++]
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Running Migrations
|
||||
|
||||
As soon as `storage.defineItem` is called, WXT checks if migrations need to be ran, and if so, runs them. Calls to get or update the storage item's value or metadata (`getValue`, `setValue`, `removeValue`, `getMeta`, etc) will automatically wait for the migration process to finish before actually reading or writing values.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Testing
|
||||
|
||||
## Official Frameworks
|
||||
|
||||
WXT officially supports [Vitest](https://vitest.dev/) for unit tests and either [Playwright](https://playwright.dev/) or [Puppeteer](https://pptr.dev/) for E2E tests against Chromium browsers.
|
||||
|
||||
For details setting up each testing framework, see the official examples:
|
||||
|
||||
<ExampleList tag="testing" />
|
||||
|
||||
### Unofficial Frameworks
|
||||
|
||||
Puppeteer and Playwright are the only E2E test runners that support Chrome Extensions. There are no other options at the time of writing.
|
||||
|
||||
There are other options for unit tests however, like [Jest](https://jestjs.io/), [Mocha](https://mochajs.org/), or [`node:test`](https://nodejs.org/api/test.html). **_WXT does not claim to support any of them_** because none of them support all of WXT's features, like TypeScript or auto-imports.
|
||||
|
||||
If you want to try to use a different framework for unit tests, you will need to configure the environment manually:
|
||||
|
||||
- **Auto-imports**: Add `unimport` to your test environment or disable them by setting `imports: false` in your `wxt.config.ts` file
|
||||
- **`browser` mock**: Mock the `webextension-polyfill` module globally with `wxt/dist/virtual/mock-browser.js`
|
||||
- **[Remote Code Bundling](/guide/remote-code)**: If you use it, configure your environment to handle the `url:` module prefix
|
||||
- **Global Variables**: If you consume them, manually define globals provided by WXT (like `import.meta.env.BROWSER`) by adding them to the global scope before accessing them (`import.meta.env.BROWSER = "chrome"`)
|
||||
- **Import paths**: If you use the `@/` or `~/` path aliases, add them to your test environment
|
||||
|
||||
[Here's how Vitest is configured](https://github.com/wxt-dev/wxt/blob/main/src/testing/wxt-vitest-plugin.ts) for reference.
|
||||
@@ -1,42 +0,0 @@
|
||||
# Unlisted Pages
|
||||
|
||||
HTML pages that are built by Vite, but are not included in the manifest.
|
||||
|
||||
### Examples
|
||||
|
||||
- Onboarding
|
||||
- Dashboard
|
||||
- FAQ
|
||||
- Help
|
||||
- Changelog
|
||||
|
||||
## Filenames
|
||||
|
||||
- `entrypoints/<name>.html`
|
||||
- `entrypoints/<name>/index.html`
|
||||
|
||||
Pages are accessible at `'/<name>.html'`:
|
||||
|
||||
```ts
|
||||
const url = browser.runtime.getURL('/<name>.html');
|
||||
|
||||
console.log(url); // "chrome-extension://<id>/<name>.html"
|
||||
```
|
||||
|
||||
## Definition
|
||||
|
||||
Plain old HTML file.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -1,16 +0,0 @@
|
||||
# Unlisted Scripts
|
||||
|
||||
TypeScript files that are built, but are not included in the manifest.
|
||||
|
||||
## Filenames
|
||||
|
||||
- `entrypoints/<name>.tsx?`
|
||||
- `entrypoints/<name>/index.tsx?`
|
||||
|
||||
## Definition
|
||||
|
||||
Unlike the background and content scripts, you can define this script's logic in the main scope.
|
||||
|
||||
```ts
|
||||
// Code goes here
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
# Vite
|
||||
|
||||
Under the hood, WXT uses Vite to bundle your web extension.
|
||||
|
||||
## Basic Vite Configuration
|
||||
|
||||
All of Vite's config can be customized by setting the `vite` configuration in your `wxt.config.ts` file.
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
vite: () => ({
|
||||
// Same as `defineConfig({ ... })` inside vite.config.ts
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
## Using Plugins
|
||||
|
||||
Plugins can be passed into the `vite` configuration in you `wxt.config.ts` file, just like any other option.
|
||||
|
||||
All plugins should work in WXT, but it is worth pointing out that since WXT orchestrates multiple vite builds to bundle an extension, plugins will be executed multiple times if necessary.
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
vite: () => ({
|
||||
plugins: [
|
||||
// ...
|
||||
],
|
||||
}),
|
||||
});
|
||||
```
|
||||
@@ -1,49 +1,98 @@
|
||||
---
|
||||
# https://vitepress.dev/reference/default-theme-home-page
|
||||
layout: home
|
||||
titleTemplate: 'Next Generation Web Extension Framework'
|
||||
title: Next-gen Web Extension Framework
|
||||
|
||||
hero:
|
||||
name: WXT
|
||||
text: Next gen framework for web extensions
|
||||
tagline: Powered by Vite, inspired by Nuxt.
|
||||
text: Next-gen Web Extension Framework
|
||||
tagline: An open source tool that makes Chrome Extension development faster than ever before.
|
||||
image:
|
||||
src: /hero-logo.svg
|
||||
alt: WXT
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Get Started
|
||||
link: /get-started/installation
|
||||
link: /guide/installation
|
||||
- theme: alt
|
||||
text: Learn More
|
||||
link: /get-started/introduction
|
||||
link: /guide/introduction
|
||||
|
||||
features:
|
||||
- icon: 🌐
|
||||
title: Supported Browsers
|
||||
details: Chrome, Firefox, Edge, Safari, and any Chromium based browser.
|
||||
details: WXT will build extensions for Chrome, Firefox, Edge, Safari, and any Chromium based browser.
|
||||
link: /guide/multiple-browsers
|
||||
linkText: Read docs
|
||||
- icon: ✅
|
||||
title: MV2 and MV3
|
||||
details: Supports both manifest versions for each browser.
|
||||
details: Build Manifest V2 or V3 extensions for any browser using the same codebase.
|
||||
link: /guide/multiple-browsers#target-manifest-version
|
||||
linkText: Read docs
|
||||
- icon: ⚡
|
||||
title: Fast Dev Mode
|
||||
details: HMR for UIs and fast reload for background and content scripts.
|
||||
details: Lighting fast HMR for UI development and fast reloads for content/background scripts enables faster iterations.
|
||||
link: /guide/development.html
|
||||
linkText: Learn more
|
||||
- icon: 📂
|
||||
title: File Based Entrypoints
|
||||
details: Manifest is generated based on files inside the project.
|
||||
details: Manifest is generated based on files in the project with inline configuration.
|
||||
link: /guide/entrypoints
|
||||
linkText: See project structure
|
||||
- icon: 🚔
|
||||
title: TypeScript
|
||||
details: Scale projects with full TS support.
|
||||
details: Create large projects with confidence using TS by default.
|
||||
- icon: 🦾
|
||||
title: Auto-imports
|
||||
details: Nuxt-like auto-imports to speed up development.
|
||||
link: /guide/auto-imports
|
||||
linkText: Read docs
|
||||
- icon: 🤖
|
||||
title: Automated Publishing
|
||||
details: Automatically zip, upload, submit, and publish extensions.
|
||||
- icon: 🎨
|
||||
title: Frontend Framework Agnostic
|
||||
details: Works with any front-end framework with a Vite plugin.
|
||||
link: /guide/configuration#frontend-frameworks
|
||||
linkText: Add a framework
|
||||
- icon: 🖍️
|
||||
title: Bootstrap a New Project
|
||||
details: Get started quickly with several awesome project templates.
|
||||
link: /guide/installation#bootstrap-project
|
||||
linkText: See templates
|
||||
- icon: 📏
|
||||
title: Bundle Analysis
|
||||
details: Tools for analyzing the final extension bundle and minimizing your extension's size.
|
||||
- icon: ⬇️
|
||||
title: Bundle Remote Code
|
||||
details: Downloads and bundles remote code imported from URLs.
|
||||
- icon: 🎨
|
||||
title: Frontend framework agnostic
|
||||
details: Works with any front-end framework with a Vite plugin.
|
||||
- icon: 🤖
|
||||
title: Automated Publishing
|
||||
details: 'TODO: Automatically zip, upload, and release extensions.'
|
||||
- icon: 📏
|
||||
title: Bundle analysis
|
||||
details: 'TODO: Tools for analyizing the final extension bundle.'
|
||||
link: /guide/remote-code
|
||||
linkText: Read docs
|
||||
---
|
||||
|
||||
<section class="vp-doc" style="padding: 0 48px">
|
||||
<div class="container">
|
||||
<h2>Put <span style="color: var(--vp-c-brand-1)">Developer Experience</span> First</h2>
|
||||
<p>
|
||||
WXT's simplifies the chrome extension development process by providing tools for zipping and publishing, the best-in-class dev mode, an opinionated project structure, and more. Iterate faster, develop features not build scripts, and use everything the JS ecosystem has to offer.
|
||||
</p>
|
||||
<div style="margin: auto; width: 100%; max-width: 900px; text-align: center">
|
||||
<video src="https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94" controls></video>
|
||||
<br />
|
||||
<small>
|
||||
And who doesn't appreciate a beautiful CLI?
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ClientOnly>
|
||||
<UsingWxtSection />
|
||||
</ClientOnly>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
max-width: 1152px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Netlify Redirects File
|
||||
# https://docs.netlify.com/routing/redirects/
|
||||
|
||||
# Old URLs -> New URLs
|
||||
/config.html /api/wxt/interfaces/InlineConfig.html
|
||||
/api/config.html /api/wxt/interfaces/InlineConfig.html
|
||||
/entrypoints /entrypoints/background.html
|
||||
/get-started/assets.html /guide/assets.html
|
||||
/get-started/build-targets.html /guide/multiple-browsers.html
|
||||
/get-started/compare.html /guide/compare.html
|
||||
/get-started/configuration.html /guide/configuration.html
|
||||
/get-started/entrypoints.html /guide/entrypoints.html
|
||||
/get-started/installation.html /guide/installation.html
|
||||
/get-started/introduction.html /guide/introduction.html
|
||||
/get-started/publishing.html /guide/publishing.html
|
||||
/get-started/testing.html /guide/testing.html
|
||||
/guide/background.html /entrypoints/background.html
|
||||
/guide/bookmarks.html /entrypoints/bookmarks.html
|
||||
/guide/content-scripts.html /entrypoints/content-scripts.html
|
||||
/guide/css.html /entrypoints/css.html
|
||||
/guide/devtools.html /entrypoints/devtools.html
|
||||
/guide/history.html /entrypoints/history.html
|
||||
/guide/manifest.html /entrypoints/manifest.html
|
||||
/guide/newtab.html /entrypoints/newtab.html
|
||||
/guide/options.html /entrypoints/options.html
|
||||
/guide/popup.html /entrypoints/popup.html
|
||||
/guide/sandbox.html /entrypoints/sandbox.html
|
||||
/guide/sidepanel.html /entrypoints/sidepanel.html
|
||||
/guide/unlisted-pages.html /entrypoints/unlisted-pages.html
|
||||
/guide/unlisted-scripts.html /entrypoints/unlisted-scripts.html
|
||||
/guide/build-targets.html /guide/multiple-browsers.html
|
||||
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M354.435 418.141C383.217 418.141 406.55 394.808 406.55 366.026V313.91H416.026C444.808 313.91 468.141 290.578 468.141 261.795C468.141 233.013 444.808 209.68 416.026 209.68H406.55V157.565C406.55 128.783 383.217 105.45 354.435 105.45H302.32V95.9745C302.32 67.1921 278.987 43.8594 250.205 43.8594C221.422 43.8594 198.09 67.1921 198.09 95.9745V105.45H145.974C117.192 105.45 93.8594 128.783 93.8594 157.565V209.68H103.335C132.117 209.68 155.45 233.013 155.45 261.795C155.45 290.578 132.117 313.91 103.335 313.91H93.8594V418.141H198.09V408.665C198.09 379.883 221.422 356.55 250.205 356.55C278.987 356.55 302.32 379.883 302.32 408.665V418.141H354.435Z" stroke="#67D55E" stroke-width="20"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 798 B |
@@ -0,0 +1,10 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_305_490)">
|
||||
<path d="M348.608 492C384.905 492 414.329 462.576 414.329 426.279V360.557H426.279C462.576 360.557 492 331.132 492 294.835C492 258.538 462.576 229.114 426.279 229.114H414.329V163.392C414.329 127.095 384.905 97.6709 348.608 97.6709H282.886V85.7215C282.886 49.4245 253.462 20 217.165 20C180.868 20 151.443 49.4245 151.443 85.7215V97.6709H85.7215C49.4245 97.6709 20 127.095 20 163.392V229.114H31.9494C68.2464 229.114 97.6709 258.538 97.6709 294.835C97.6709 331.132 68.2464 360.557 31.9494 360.557H20V492H151.443V480.051C151.443 443.754 180.868 414.329 217.165 414.329C253.462 414.329 282.886 443.754 282.886 480.051V492H348.608Z" stroke="#67D55E" stroke-width="40"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_305_490">
|
||||
<rect width="512" height="512" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 914 B |
@@ -0,0 +1,5 @@
|
||||
User-agent: *
|
||||
Disallow: /api.html
|
||||
Disallow: /config.html
|
||||
|
||||
Sitemap: https://wxt.dev/sitemap.xml
|
||||
|
After Width: | Height: | Size: 472 KiB |
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://typedoc.org/schema.json",
|
||||
"entryPointStrategy": "packages",
|
||||
"entryPoints": ["../packages/wxt"],
|
||||
"plugin": ["typedoc-plugin-markdown", "typedoc-vitepress-theme"],
|
||||
"out": "./api",
|
||||
"githubPages": false,
|
||||
"excludePrivate": true,
|
||||
"excludeProtected": true,
|
||||
"excludeInternal": true,
|
||||
"readme": "none"
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
|
||||
describe('Auto Imports', () => {
|
||||
it('should output types for entrypoint paths', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile(
|
||||
'entrypoints/background.ts',
|
||||
'export default defineBackground(() => {})',
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/overlay.content.ts',
|
||||
'export default defineContentScript(() => {})',
|
||||
);
|
||||
project.addFile('entrypoints/popup.html', '<html></html>');
|
||||
|
||||
await project.build();
|
||||
|
||||
expect(await project.serializeFile('.wxt/types/paths.d.ts'))
|
||||
.toMatchInlineSnapshot(`
|
||||
".wxt/types/paths.d.ts
|
||||
----------------------------------------
|
||||
// Generated by wxt
|
||||
type EntrypointPath =
|
||||
| \\"/background.js\\"
|
||||
| \\"/content-scripts/overlay.js\\"
|
||||
| \\"/popup.html\\"
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should make some client utils auto-importable', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', `<html></html>`);
|
||||
|
||||
await project.build();
|
||||
|
||||
expect(await project.serializeFile('.wxt/types/imports.d.ts'))
|
||||
.toMatchInlineSnapshot(`
|
||||
".wxt/types/imports.d.ts
|
||||
----------------------------------------
|
||||
// Generated by wxt
|
||||
export {}
|
||||
declare global {
|
||||
const browser: typeof import('webextension-polyfill')
|
||||
const defineBackground: typeof import('wxt/client')['defineBackground']
|
||||
const defineConfig: typeof import('wxt')['defineConfig']
|
||||
const defineContentScript: typeof import('wxt/client')['defineContentScript']
|
||||
const mountContentScriptUi: typeof import('wxt/client')['mountContentScriptUi']
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -1,209 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
|
||||
// TODO: move to unit tests to speed this up - this doesn't have to be in E2E tests
|
||||
|
||||
describe('Manifest Content', () => {
|
||||
describe('popup', () => {
|
||||
const popupContent = (type?: 'browser_action' | 'page_action') => `
|
||||
<html>
|
||||
<head>
|
||||
${type == null ? '' : `<meta name="manifest.type" content="${type}">`}
|
||||
<meta name="manifest.default_icon" content="{ '16': '/icon/16.png' }">
|
||||
<title>Default Title</title>
|
||||
</head>
|
||||
</html>
|
||||
`;
|
||||
|
||||
it('should include an action for mv3', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', popupContent());
|
||||
|
||||
await project.build();
|
||||
|
||||
const manifest = await project.getOutputManifest();
|
||||
expect(manifest.action).toEqual({
|
||||
default_icon: { '16': '/icon/16.png' },
|
||||
default_title: 'Default Title',
|
||||
default_popup: 'popup.html',
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
[undefined, 'browser_action'],
|
||||
['browser_action', 'browser_action'],
|
||||
['page_action', 'page_action'],
|
||||
] as const)(
|
||||
'should include a browser_action for mv2',
|
||||
async (type, expectedType) => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', popupContent(type));
|
||||
|
||||
await project.build({ manifestVersion: 2 });
|
||||
|
||||
const manifest = await project.getOutputManifest(
|
||||
'.output/chrome-mv2/manifest.json',
|
||||
);
|
||||
expect(manifest[expectedType]).toEqual({
|
||||
default_icon: { '16': '/icon/16.png' },
|
||||
default_title: 'Default Title',
|
||||
default_popup: 'popup.html',
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('options', () => {
|
||||
const optionsContent = `
|
||||
<html>
|
||||
<head>
|
||||
<meta name="manifest.open_in_tab" content="false">
|
||||
<meta name="manifest.chrome_style" content="true">
|
||||
<meta name="manifest.browser_style" content="true">
|
||||
</head>
|
||||
</html>
|
||||
`;
|
||||
|
||||
it('should include a options_ui and chrome_style for chrome', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/options.html', optionsContent);
|
||||
|
||||
await project.build();
|
||||
const manifest = await project.getOutputManifest();
|
||||
|
||||
expect(manifest.options_ui).toEqual({
|
||||
open_in_tab: false,
|
||||
chrome_style: true,
|
||||
page: 'options.html',
|
||||
});
|
||||
});
|
||||
|
||||
it('should include a options_ui and browser_style for firefox', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/options.html', optionsContent);
|
||||
|
||||
await project.build({ browser: 'firefox' });
|
||||
const manifest = await project.getOutputManifest(
|
||||
'.output/firefox-mv2/manifest.json',
|
||||
);
|
||||
|
||||
expect(manifest.options_ui).toEqual({
|
||||
open_in_tab: false,
|
||||
browser_style: true,
|
||||
page: 'options.html',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('background', () => {
|
||||
const backgroundContent = `
|
||||
export default defineBackground({
|
||||
persistent: true,
|
||||
type: "module",
|
||||
main: () => {},
|
||||
})
|
||||
`;
|
||||
|
||||
it('should include a background script for mv2', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/background.ts', backgroundContent);
|
||||
|
||||
await project.build();
|
||||
const manifest = await project.getOutputManifest();
|
||||
|
||||
expect(manifest.background).toEqual({
|
||||
type: 'module',
|
||||
service_worker: 'background.js',
|
||||
});
|
||||
});
|
||||
|
||||
it('should include a options_ui and browser_style for firefox', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/background.ts', backgroundContent);
|
||||
|
||||
await project.build({ manifestVersion: 2 });
|
||||
const manifest = await project.getOutputManifest(
|
||||
'.output/chrome-mv2/manifest.json',
|
||||
);
|
||||
|
||||
expect(manifest.background).toEqual({
|
||||
persistent: true,
|
||||
scripts: ['background.js'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should group content scripts and styles together based on their matches and run_at', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile(
|
||||
'entrypoints/one.content/index.ts',
|
||||
`import "./style.css";
|
||||
export default defineContentScript({
|
||||
matches: ["*://google.com/*"],
|
||||
main: () => {},
|
||||
})`,
|
||||
);
|
||||
project.addFile('entrypoints/one.content/style.css', `body { color: red }`);
|
||||
project.addFile(
|
||||
'entrypoints/two.content/index.ts',
|
||||
`import "./style.css";
|
||||
export default defineContentScript({
|
||||
matches: ["*://google.com/*"],
|
||||
runAt: "document_end",
|
||||
main: () => {},
|
||||
})`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/two.content/style.css',
|
||||
`body { color: green }`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/three.content/index.ts',
|
||||
`import "./style.css";
|
||||
export default defineContentScript({
|
||||
matches: ["*://google.com/*"],
|
||||
runAt: "document_end",
|
||||
main: () => {},
|
||||
})`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/three.content/style.css',
|
||||
`body { color: blue }`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/four.content/index.ts',
|
||||
`import "./style.css";
|
||||
export default defineContentScript({
|
||||
matches: ["*://duckduckgo.com/*"],
|
||||
runAt: "document_end",
|
||||
main: () => {},
|
||||
})`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/four.content/style.css',
|
||||
`body { color: yellow }`,
|
||||
);
|
||||
|
||||
await project.build();
|
||||
|
||||
const manifest = await project.getOutputManifest();
|
||||
|
||||
expect(manifest.content_scripts).toContainEqual({
|
||||
matches: ['*://duckduckgo.com/*'],
|
||||
run_at: 'document_end',
|
||||
css: ['assets/four.css'],
|
||||
js: ['content-scripts/four.js'],
|
||||
});
|
||||
expect(manifest.content_scripts).toContainEqual({
|
||||
matches: ['*://google.com/*'],
|
||||
run_at: 'document_end',
|
||||
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: ['assets/one.css'],
|
||||
js: ['content-scripts/one.js'],
|
||||
});
|
||||
});
|
||||
});
|
||||