Compare commits
628 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7366d8aab7 | |||
| a7ab409d48 | |||
| 9dea0cf3ae | |||
| 50d7c1ec80 | |||
| f32cdafdf6 | |||
| 2e2272d07a | |||
| 27ffff7e0a | |||
| 305cebe0d0 | |||
| 4602a068f8 | |||
| 3a947d72c6 | |||
| 1940e3530f | |||
| dd549dc686 | |||
| 3417bd3ed2 | |||
| 9470d2b696 | |||
| f523a7d31d | |||
| ea4c2b035b | |||
| c5bfba837f | |||
| c5ad957b25 | |||
| cc849e7cea | |||
| 22428da54d | |||
| f6071f7e2d | |||
| 08e8560865 | |||
| 75f01db543 | |||
| 5a028d2551 | |||
| 07a4ba8bc5 | |||
| f84921f735 | |||
| 872494e490 | |||
| d0848b0be5 | |||
| c848956777 | |||
| 296997d10e | |||
| ffdb22238c | |||
| a63c320403 | |||
| aa86ed78a3 | |||
| 19456ec855 | |||
| 2c57840c44 | |||
| 26b7927f62 | |||
| d4267af725 | |||
| 16a2aedcd0 | |||
| 755f04f777 | |||
| cf9539ddab | |||
| 99d558f33c | |||
| 1fe073004b | |||
| 6ae5fb5edb | |||
| 3a3953edf6 | |||
| c8e788eaf8 | |||
| 088a44496d | |||
| 60a57c5abc | |||
| 482466bbbf | |||
| 65deb7317b | |||
| 00ae061822 | |||
| 9bd7fc1bd5 | |||
| 98ddb83f1d | |||
| 6dd8b9d072 | |||
| 1e3482a878 | |||
| 734846843f | |||
| f1eac66494 | |||
| b4c3280f58 | |||
| b2f2998767 | |||
| 720eb93c67 | |||
| cdf8611e0b | |||
| 905d797b65 | |||
| 6ca8ccc332 | |||
| 76496e8865 | |||
| f38fec8588 | |||
| 4351ac9657 | |||
| f3834ac0e2 | |||
| 48891a7ca5 | |||
| 3b6a4aa635 | |||
| b20f0471c2 | |||
| 0f9e66e1e8 | |||
| 6fb4cee43f | |||
| 0e8afbbf56 | |||
| 83decf7bf5 | |||
| 5c77cf23b8 | |||
| 606fd3164c | |||
| 71308d0d6f | |||
| 8bbea7cda4 | |||
| a87d3e4809 | |||
| 1f3db58254 | |||
| 43f6c06271 | |||
| 04ae542b13 | |||
| f97c81df0d | |||
| b7b3fc53eb | |||
| 4b44fb849d | |||
| 5d0a266e9c | |||
| 24667861db | |||
| 25cc403886 | |||
| 923312c482 | |||
| adeef6eb73 | |||
| 6f84d45960 | |||
| 938c25cab4 | |||
| 0404637da8 | |||
| 98d95e4b7f | |||
| 2687a35781 | |||
| 886dae28fa | |||
| 8c9bc8cdbc | |||
| 5414a0aa34 | |||
| 223cf8613b | |||
| 4f1776befc | |||
| 38473b9f6c | |||
| 41b428c75f | |||
| d8d754658f | |||
| e52ebf1d53 | |||
| 535c7a5f92 | |||
| 99b2bd784a | |||
| 31ebf966fd | |||
| 6036c6e8e0 | |||
| 597151b5dd | |||
| 29aeaed223 | |||
| edf87952c7 | |||
| 78f8434a06 | |||
| 7eaccad683 | |||
| bfa9c1e1c8 | |||
| a2d6577579 | |||
| 0e8191488f | |||
| a1510bb081 | |||
| 1eaca30f97 | |||
| bc79987ed5 | |||
| 36a501506e | |||
| e1d47f5f63 | |||
| 7faa9b5a64 | |||
| 295bee708c | |||
| 9e917481c4 | |||
| c5a8b9fb3d | |||
| 3f4e32f8da | |||
| f630098f1d | |||
| 608d6e2b54 | |||
| fad5ab6c44 | |||
| d4cb7997fe | |||
| 28364b8be1 | |||
| d28a5c7d5e | |||
| df707aef4c | |||
| 77af2e58ec | |||
| f738e56337 | |||
| f2fe2584e0 | |||
| 02673ae017 | |||
| 8dd294a163 | |||
| 469e735d53 | |||
| b02001796e | |||
| 0d4d2fdd7e | |||
| b65e5446fa | |||
| f75fe3ce5b | |||
| 59ba2712cf | |||
| fefe601541 | |||
| 7291c60413 | |||
| bb679102b1 | |||
| 16027c8971 | |||
| 84405a33e3 | |||
| bdf795c3f2 | |||
| ab73084bb5 | |||
| e9fb4c5414 | |||
| bc54f525e4 | |||
| 9170159bc1 | |||
| fa657a0366 | |||
| 24920ddd52 | |||
| eb0b372113 | |||
| 8f5f4f674e | |||
| 38c1dae9c7 | |||
| f92bcd1d14 | |||
| df09b86d23 | |||
| bd5cfa10ad | |||
| 94914dd1ad | |||
| dfbb58a2f1 | |||
| 54a1c3576f | |||
| 9ddf73b4fe | |||
| 4ceec1a6ab | |||
| ba314d7434 | |||
| 55fed67067 | |||
| 852390092e | |||
| 067be3fed3 | |||
| 1aecdbb71c | |||
| d8e517cb7b | |||
| 9da5b4cacc | |||
| 84af32242b | |||
| 7a81472407 | |||
| 9399d8df42 | |||
| 459f73fd5c | |||
| c1c3d3b7c5 | |||
| 380a9630e5 | |||
| 984568e00c | |||
| 5fefd8e083 | |||
| a6eef643f6 | |||
| a01928e010 | |||
| d901db2316 | |||
| fdda812078 | |||
| 7c59e9d5b5 | |||
| c9b0c4da34 | |||
| 9f214141b4 | |||
| 4638e90f02 | |||
| d1c50ab6f8 | |||
| 489aa0ffd8 | |||
| ebcc605961 | |||
| bb630049d5 | |||
| 39321adfe1 | |||
| ea2b8df906 | |||
| 74a9b1aef4 | |||
| 8b3e35de94 | |||
| 9d4724fa99 | |||
| a8379cbd80 | |||
| bdfb6c5a84 | |||
| 87414d7137 | |||
| f9d0b64d7a | |||
| 97918710bd | |||
| ac340b3f1e | |||
| a2097e148b | |||
| 190be7f732 | |||
| 0333ce5c46 | |||
| 47df623fd4 | |||
| c609efbd9a | |||
| c8cd41a833 | |||
| 02a0110091 | |||
| 6c572c56a3 | |||
| 2db516ef3b | |||
| 9f3787cd95 | |||
| 5511943237 | |||
| 626838eba6 | |||
| 587328f54c | |||
| 1aa88b1fb6 | |||
| e7db119545 | |||
| da79536456 | |||
| aaaff43731 | |||
| 5d77142758 | |||
| 586ea0002b | |||
| e642d6fe76 | |||
| 7148f288d5 | |||
| 40abf3fb5b | |||
| adea6c7177 | |||
| 96a307e99f | |||
| f0201f72ef | |||
| 0b1b677b3d | |||
| b1dcec86cd | |||
| 7c783fe565 | |||
| 9a0207d7cf | |||
| 1b0e047d6b | |||
| 7ab3c7e5d9 | |||
| 9ece49d226 | |||
| 1272e2ec74 | |||
| db16602018 | |||
| 5c202805b9 | |||
| bfc8866b69 | |||
| 9aedc29289 | |||
| e1f3d75ac4 | |||
| 030f23d2e9 | |||
| 6be9a4e71b | |||
| eb0bffb5dd | |||
| 240bbb3676 | |||
| 7d25110a6e | |||
| 598eea703b | |||
| 8793f7bff4 | |||
| c1b3911622 | |||
| b0bf05109d | |||
| d9cb0ebcc8 | |||
| 1eda21f63f | |||
| f2dbcde124 | |||
| 60f00db2c5 | |||
| a9332453fc | |||
| b99da14799 | |||
| f02400a1fb | |||
| bbcb7f7cdd | |||
| 30cf8e6d54 | |||
| 4f772fc0f7 | |||
| 2cf2582138 | |||
| ad4de4b92c | |||
| 27ed73a6ae | |||
| a1570c8917 | |||
| 7e419ddd43 | |||
| b045d3616d | |||
| f6b50e2e77 | |||
| 9d1381cbb3 | |||
| 99b0242d86 | |||
| db27628211 | |||
| 9b9d86229a | |||
| 46f830dacf | |||
| 3dd9bc7772 | |||
| c7335add19 | |||
| c133498208 | |||
| 6a52bb22a3 | |||
| 6f970efda5 | |||
| c69350a0eb | |||
| 4add8820eb | |||
| 033dd4811e | |||
| da5cd32502 | |||
| edf33fdec9 | |||
| 448cbf16c9 | |||
| e45b77b528 | |||
| b61fd1716e | |||
| d584b5cc7e | |||
| faf9cedfbd | |||
| 3a38ac9339 | |||
| 7ea36a3cfc | |||
| 3577c0b47a | |||
| d0bef42186 | |||
| d9f10c62ad | |||
| 35d3c03e4b | |||
| fdd38a1580 | |||
| 3018801f06 | |||
| 8e96bfef06 | |||
| 31071bd11e | |||
| f5619f6be1 | |||
| adad1b5a2c | |||
| a6c4e19a5d | |||
| af6f74c344 | |||
| ce45cb8a89 | |||
| 14d4aa202d | |||
| 4c35798dba | |||
| 656a9b365f | |||
| 67fa3db921 | |||
| f2d3061e97 | |||
| 760c34e416 | |||
| 48398b315c | |||
| 45d0d9d7f1 | |||
| b0f4ac8221 | |||
| 719192b1aa | |||
| 7eb32bdac8 | |||
| 862756fd5b | |||
| 5ba16e72e0 | |||
| e43ae0504d | |||
| acb6cd180c | |||
| a3301d0413 | |||
| b9e72358ac | |||
| 61b42ef326 | |||
| da9e27c28d | |||
| 6f80fbabaa | |||
| d2308bd45c | |||
| d35972d24f | |||
| 298a264934 | |||
| ad63b595b4 | |||
| 986a9ceebf | |||
| 682b65e723 | |||
| cb661506c0 | |||
| 505aa06443 | |||
| f534a978f1 | |||
| 1293815934 | |||
| 0c0fd18bf4 | |||
| c49c31813f | |||
| 877ab11bb4 | |||
| 0d18d8d7e3 | |||
| 5f977857a1 | |||
| a63b21d417 | |||
| e1b7d6090e | |||
| 1bcc5cdd81 | |||
| 873fdbc4b3 | |||
| 79128effea | |||
| 14ef94fc09 | |||
| 759e3e3a4d | |||
| 5b7b16efba | |||
| 7fd171acb5 | |||
| 02ed3f1d44 | |||
| 4f7f3f36a3 | |||
| 73a8cf12a9 | |||
| 5f5eb16705 | |||
| f0f4437870 | |||
| 868c042511 | |||
| bdb49c0b3d | |||
| cc3c43d1bd | |||
| e47131efc1 | |||
| 82d8024fb4 | |||
| c662a59d85 | |||
| 4e50fb1f92 | |||
| fbae370d1e | |||
| 9c64c9dd61 | |||
| 8f6dba20d2 | |||
| f531fcd5c3 | |||
| ff1720a4c6 | |||
| d62203dff8 | |||
| 99b5076d69 | |||
| 298e7101f7 | |||
| b4ce36b708 | |||
| 5d096a4a92 | |||
| e54df0aca8 | |||
| 2e8baf0161 | |||
| c9dca0222c | |||
| 6044ab73bd | |||
| 660945c792 | |||
| bcb20874a8 | |||
| 83ad0e3ff0 | |||
| aad17c8d26 | |||
| b0ef178c9c | |||
| 0175c430a3 | |||
| 2776587392 | |||
| b978465d7a | |||
| f7989ea1e0 | |||
| 0cf34d3170 | |||
| 4fe04c6f8a | |||
| 1773762d2f | |||
| 9057000e45 | |||
| d540233673 | |||
| 9907290a04 | |||
| d9fb919580 | |||
| 17723d5828 | |||
| c7db2d26b3 | |||
| 259cec9ea8 | |||
| 2ced9c40d3 | |||
| 756efc9311 | |||
| 17fd2ff0ce | |||
| 35833c00f0 | |||
| 300c045187 | |||
| 4c05cc19e5 | |||
| 72dab65a1d | |||
| 4e5fb7745a | |||
| 2bc92043f8 | |||
| 3024bd825d | |||
| 0b39774690 | |||
| a461a23845 | |||
| c0867e3374 | |||
| 47040277a2 | |||
| 0f0daf378c | |||
| 51b315f23a | |||
| 60f5117bbc | |||
| 15634ecb95 | |||
| 0a5007aee0 | |||
| bc54e1bd63 | |||
| d8a8202e63 | |||
| c9eb3b0ca9 | |||
| 665f5a52c4 | |||
| 798f3b8e47 | |||
| a886ab28e1 | |||
| 28cff9c90a | |||
| 8ce197b023 | |||
| a5553457c6 | |||
| 7d9ed777fb | |||
| 59362e7274 | |||
| d32d17952c | |||
| 6630bca73f | |||
| befd442075 | |||
| da885ff6c8 | |||
| 349b216439 | |||
| 47b78d66cf | |||
| fe2baef2b1 | |||
| 357bbdaec9 | |||
| a38e3c2733 | |||
| f17e527d72 | |||
| e20de4ca0d | |||
| 16ac53e0a1 | |||
| fee978bf25 | |||
| f80909d63e | |||
| d5b2503155 | |||
| 80ca5760e2 | |||
| 44dab3726f | |||
| c4295a4f4b | |||
| a6a91250d1 | |||
| 1db50ed7a9 | |||
| 342057b3f5 | |||
| 151b1392c6 | |||
| 1d5d0663ac | |||
| 30b8ba495d | |||
| 5775c9ce0f | |||
| 4f82dd87fc | |||
| fab9fa81d1 | |||
| d6ee4560d8 | |||
| 655ba863a0 | |||
| ac92d4019f | |||
| 0ff964a04b | |||
| 89459f0a4d | |||
| 0d56100abb | |||
| dc770b5952 | |||
| 8cd2bff67a | |||
| 11e55e2499 | |||
| 4cab3a5593 | |||
| daa0eed851 | |||
| 868a347d40 | |||
| 48a87aa352 | |||
| dfec8a3608 | |||
| 9a71231436 | |||
| db4592fbb3 | |||
| 8551d6071e | |||
| c40285f750 | |||
| 9238f9fd85 | |||
| ba3d8838e3 | |||
| e9b6f5f97c | |||
| 5b6c82d38f | |||
| 5b41b70abe | |||
| 93f624610f | |||
| 73bdd49471 | |||
| 635f3f0898 | |||
| a53ee6d590 | |||
| 665b9194cf | |||
| 4690418c94 | |||
| 20eea2a7d1 | |||
| ac7b6760f2 | |||
| a593cfbbb6 | |||
| a205f2a0a8 | |||
| 8e2badc92b | |||
| 7c18d9c0b8 | |||
| 55a1f39450 | |||
| d708eca78e | |||
| 5d7e2516b6 | |||
| fb6a29872b | |||
| 187206062e | |||
| 2615e29e52 | |||
| 8ca176d37e | |||
| cb2bf43f30 | |||
| 1719d17cff | |||
| 32afb8d97c | |||
| 72b8a29d76 | |||
| af0a32d478 | |||
| 060e85f90e | |||
| 654a54ac79 | |||
| ef24358942 | |||
| e5e67e39bd | |||
| 228304bef9 | |||
| 78f97f32a2 | |||
| 459662a6c1 | |||
| b150a52471 | |||
| 028c601515 | |||
| af382ef065 | |||
| 91ef7f2ab5 | |||
| 0bf7279c8f | |||
| 62525d33c6 | |||
| 88ff40e301 | |||
| 1a72a0cdc6 | |||
| 2b07b4bc12 | |||
| 06c09e7c87 | |||
| 30b96ac87c | |||
| 2e9b5cffe8 | |||
| 3e1641a6ec | |||
| d64ca52bb0 | |||
| 8c645fa1ce | |||
| 79c9c89923 | |||
| 11d93fe724 | |||
| 039c561a80 | |||
| 64a704fc72 | |||
| 4fbf137d15 | |||
| ff065ab88b | |||
| 7fdf3fb968 | |||
| 7ebd451409 | |||
| a7a48a8a75 | |||
| 27298b7f22 | |||
| 565ce93a9a | |||
| 0bc6088b6a | |||
| 2fb99e8fbb | |||
| cd2050c534 | |||
| 5b557d0224 | |||
| 0137d79ba0 | |||
| e05fbf4fb5 | |||
| 72e6ca5063 | |||
| af9e842fe0 | |||
| 230962686f | |||
| 413cd9354f | |||
| 64a42de31f | |||
| f7f03e2669 | |||
| 1dc7c34bfd | |||
| 0fd5c3846c | |||
| e42e92f9df | |||
| e45c91f941 | |||
| 8f71d80f94 | |||
| 78e896e5fb | |||
| a643852387 | |||
| e221252325 | |||
| 88f126e658 | |||
| 17af05acc8 | |||
| fd436a0d63 | |||
| 0d796b63c2 | |||
| cb143d2bbc | |||
| f3e7758521 | |||
| 23ef101954 | |||
| c2f5efbde4 | |||
| 4ce178c592 | |||
| 7aa30afa7f | |||
| 90aaed202c | |||
| c92ed13d66 | |||
| 0e4953fbfc | |||
| 799d687061 | |||
| 866eb0694c | |||
| e5723aab03 | |||
| f51f5dd19f | |||
| aae5f0789d | |||
| 586fe3950c | |||
| 241c907c65 | |||
| aab4244d84 | |||
| 767a3a987c | |||
| a2924d872a | |||
| 54baf21490 | |||
| 5a3790a7d3 | |||
| 236e099b81 | |||
| 9b54bc4704 | |||
| ffb99af719 | |||
| 90bb145d91 | |||
| 190c87d120 | |||
| 4c511d5728 | |||
| b8ecbc5a54 | |||
| 8c3d756187 | |||
| cfdc6b3e6b | |||
| 226e6c9fbe | |||
| db41f27072 | |||
| db025cd3a2 | |||
| 57229c69c1 | |||
| 8f3a90eeb2 | |||
| a360965d92 | |||
| eb1ace6d20 | |||
| 497c902283 | |||
| 4d4453b830 | |||
| ef53580ed1 | |||
| 52e5387d47 | |||
| f5b7f7e0e5 | |||
| 592bdbe4d9 | |||
| 6fc227bad6 | |||
| d74f6b0735 | |||
| 62ea796045 | |||
| 064704c9b7 | |||
| c846a1febf | |||
| c3959e8427 | |||
| e770f9b6b7 | |||
| 67f35615a2 | |||
| 5c7b6098a2 | |||
| f7d12b7486 | |||
| 3481313859 | |||
| 2f23556893 | |||
| 330e39159a | |||
| aa14676f91 | |||
| 199f330324 | |||
| 63dee97ac3 | |||
| b121ed2fe2 | |||
| 607e1162e5 | |||
| b4d569a915 | |||
| 4fac364542 | |||
| 51c7ee5fa8 | |||
| 9b780f2d00 | |||
| f5aec7ec9b | |||
| 5d991eda31 | |||
| 0318a34046 | |||
| e7c66304d3 | |||
| 4603ebb511 | |||
| ea570c12b7 | |||
| c17ce34a27 | |||
| 0e4d3ad8ab | |||
| 4304f71df2 | |||
| bb5ea34396 |
@@ -0,0 +1,10 @@
|
||||
extends:
|
||||
- '@commitlint/config-conventional'
|
||||
rules:
|
||||
subject-case:
|
||||
- 0
|
||||
- always
|
||||
- - sentence-case
|
||||
- start-case
|
||||
- pascal-case
|
||||
- upper-case
|
||||
@@ -1,5 +1,16 @@
|
||||
# See https://git-scm.com/docs/gitattributes#_pattern_format for more about `.gitattributes`.
|
||||
|
||||
# Normalize EOL for all files that Git considers text files
|
||||
* text=auto eol=lf
|
||||
|
||||
# Mark lock files as generated to avoid diffing
|
||||
pnpm-lock.yaml linguist-generated
|
||||
package-lock.json linguist-generated
|
||||
bun.lockb linguist-generated
|
||||
yarn.lock linguist-generated
|
||||
|
||||
# Exclude templates from language statistics
|
||||
templates/**/* linguist-vendored
|
||||
|
||||
# Other generated files
|
||||
packages/browser/src/gen/** linguist-generated
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Set default
|
||||
* @aklinker1 @Timeraa
|
||||
|
||||
# Secure Directories
|
||||
/.github/ @aklinker1
|
||||
|
||||
# Creator of specific wxt modules
|
||||
/packages/auto-icons/ @Timeraa
|
||||
/packages/unocss/ @Timeraa
|
||||
@@ -0,0 +1,3 @@
|
||||
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository#about-funding-files
|
||||
|
||||
github: wxt-dev
|
||||
@@ -1,6 +1,7 @@
|
||||
name: "\U0001F41E Bug report"
|
||||
description: Report an issue with WXT
|
||||
labels: [pending-triage]
|
||||
type: Bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: Feature request
|
||||
about: Suggest an idea for WXT
|
||||
title: ''
|
||||
labels: feature
|
||||
type: Feature
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
name: Basic Setup
|
||||
description: Install PNPM, Node, and dependencies
|
||||
|
||||
inputs:
|
||||
install:
|
||||
default: 'true'
|
||||
description: Whether or not to run 'pnpm install'
|
||||
|
||||
installArgs:
|
||||
default: ''
|
||||
description: Additional args to append to "pnpm install"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v4
|
||||
- name: 🛠️ Setup PNPM
|
||||
uses: pnpm/action-setup@f2b2b233b538f500472c7274c7012f57857d8ce0 # v4.1.0
|
||||
|
||||
- name: 🛠️ Setup NodeJS
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
- name: Install Dependencies
|
||||
|
||||
- name: 📦 Install Dependencies
|
||||
if: ${{ inputs.install == 'true' }}
|
||||
shell: bash
|
||||
run: pnpm install
|
||||
run: pnpm install ${{ inputs.installArgs }}
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directories:
|
||||
- /
|
||||
- packages/*
|
||||
directory: /
|
||||
schedule:
|
||||
interval: 'monthly'
|
||||
- package-ecosystem: 'github-actions'
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
### Overview
|
||||
|
||||
<!-- Describe your changes and why you made them -->
|
||||
|
||||
### Manual Testing
|
||||
|
||||
<!-- Describe how to test your changes to make sure the PR works as intended -->
|
||||
|
||||
### Related Issue
|
||||
|
||||
<!-- If this PR is related to an issue, please link it here -->
|
||||
|
||||
This PR closes #<issue_number>
|
||||
@@ -0,0 +1,36 @@
|
||||
name: 🔔 Notify Unreleased Commits
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 20 * * 1' # Weekly at 8 PM UTC (3 PM CT)
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.repository_owner == 'wxt-dev'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: List Commits
|
||||
id: list-commits
|
||||
run: |
|
||||
./scripts/list-unreleased-commits.sh > summary.txt
|
||||
|
||||
- name: Discord notification
|
||||
run: |
|
||||
PAYLOAD=$(jq -n --arg content "${{ env.MESSAGE }}" '{"content": $content}')
|
||||
curl -X POST \
|
||||
-F "payload_json=${PAYLOAD}" \
|
||||
-F "file1=@summary.txt" \
|
||||
$DISCORD_WEBHOOK
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_UNRELEASED_COMMITS }}
|
||||
MESSAGE: |
|
||||
If a package needs released, please [run the workflow](<https://github.com/wxt-dev/wxt/actions/workflows/release.yml>).
|
||||
|
||||
Before running, consider:
|
||||
- Are there any breaking changes? If so, prepare a list of breaking changes and update the changelog and release notes after the release.
|
||||
- Are there any PRs open that we wait to release after they're merged?
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Continuous Publish
|
||||
name: ✨ pkg.pr.new
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -7,11 +7,23 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Publish Test Packages
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ github.repository == 'wxt-dev/wxt' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm buildc all
|
||||
- run: pnpx pkg-pr-new publish --compact './packages/*'
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build All Packages
|
||||
run: pnpm buildc all
|
||||
|
||||
- name: Publish
|
||||
run: pnpx pkg-pr-new publish --compact --pnpm './packages/*'
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
name: 🎉 PR closed
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
thank-you:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.merged == true
|
||||
|
||||
steps:
|
||||
- name: Post Thank You Comment
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
comment: Thanks for helping make WXT better!
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: process.env.comment
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
name: 🛡️ Check PR Title
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited]
|
||||
|
||||
jobs:
|
||||
lint-pr-title:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# Only fetch the config file from the repository
|
||||
sparse-checkout-cone-mode: false
|
||||
sparse-checkout: .commitlintrc.yml
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install --global @commitlint/config-conventional commitlint
|
||||
|
||||
- name: Check PR title with commitlint
|
||||
env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
HELP_URL: https://github.com/wxt-dev/wxt/blob/main/CONTRIBUTING.md#conventional-pr-titles
|
||||
run: echo "$PR_TITLE" | npx commitlint --help-url $HELP_URL
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish Docs
|
||||
name: 📝 Publish Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -10,20 +10,38 @@ on:
|
||||
required: true
|
||||
default: latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
# Only run if it's the upstream repository, not forks
|
||||
if: github.repository == 'wxt-dev/wxt'
|
||||
name: Publish Docs
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: docker/login-action@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
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 }}
|
||||
|
||||
- name: Build docs
|
||||
run: |
|
||||
pnpm docs:build
|
||||
docker build docs/.vitepress -t ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
|
||||
|
||||
- name: Push Image
|
||||
run: docker push ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
|
||||
|
||||
- name: Deploy
|
||||
run: curl -X POST -i ${{ secrets.UPDATE_DOCS_WEBHOOK }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Release
|
||||
name: 🚀 Release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -7,47 +7,66 @@ on:
|
||||
default: wxt
|
||||
type: choice
|
||||
options:
|
||||
- analytics
|
||||
- auto-icons
|
||||
- i18n
|
||||
- module-react
|
||||
- module-solid
|
||||
- module-svelte
|
||||
- module-vue
|
||||
- runner
|
||||
- storage
|
||||
- unocss
|
||||
- webextension-polyfill
|
||||
- wxt
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate
|
||||
uses: './.github/workflows/validate.yml'
|
||||
secrets: inherit
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-22.04
|
||||
name: Publish
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write # Push version changes
|
||||
id-token: write # OIDC for NPM publishing
|
||||
needs:
|
||||
- validate
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }} # https://github.com/sbellone/release-workflow-example
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
git config --global push.followTags true
|
||||
|
||||
- name: Bump and Tag
|
||||
run: |
|
||||
git config user.email "github-actions@users.noreply.github.com"
|
||||
git config user.name "GitHub Actions"
|
||||
pnpm tsx scripts/bump-package-version.ts ${{ inputs.package }}
|
||||
git push
|
||||
git push --tags
|
||||
|
||||
- name: NPM
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
|
||||
pnpm build
|
||||
pnpm publish
|
||||
- name: Publish to NPM
|
||||
working-directory: packages/${{ inputs.package }}
|
||||
run: |
|
||||
pnpm pack
|
||||
sudo npm i -g npm@latest
|
||||
/usr/local/bin/npm publish *.tgz
|
||||
|
||||
- name: GitHub Release
|
||||
- name: Create GitHub release
|
||||
run: pnpm tsx scripts/create-github-release.ts ${{ inputs.package }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Sync Releases
|
||||
name: 🔄 Sync Releases
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -7,20 +7,37 @@ on:
|
||||
default: wxt
|
||||
type: choice
|
||||
options:
|
||||
- wxt
|
||||
- module-react
|
||||
- module-vue
|
||||
- module-svelte
|
||||
- module-solid
|
||||
- analytics
|
||||
- auto-icons
|
||||
- i18n
|
||||
- module-react
|
||||
- module-solid
|
||||
- module-svelte
|
||||
- module-vue
|
||||
- runner
|
||||
- storage
|
||||
- webextension-polyfill
|
||||
- wxt
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
name: Sync Releases
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
installArgs: --ignore-scripts
|
||||
|
||||
- name: Sync Releases
|
||||
run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: 🔄 Update @wxt-dev/browser
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Every day at midnight
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
name: 'Sync with @types/chrome'
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'wxt-dev'
|
||||
permissions:
|
||||
contents: write # Push version changes
|
||||
id-token: write # OIDC for NPM publishing
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }}
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
installArgs: --ignore-scripts
|
||||
|
||||
- name: Generate Latest Code
|
||||
working-directory: packages/browser
|
||||
run: pnpm gen
|
||||
|
||||
- name: Run Checks
|
||||
working-directory: packages/browser
|
||||
run: pnpm check
|
||||
|
||||
- name: Commit Changes
|
||||
id: commit
|
||||
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
commit_message: 'fix: Upgrade `@wxt-dev/browser` to latest `@types/chrome` version'
|
||||
|
||||
- name: Publish to NPM
|
||||
if: steps.commit.outputs.changes_detected == 'true'
|
||||
working-directory: packages/browser
|
||||
run: |
|
||||
pnpm pack
|
||||
sudo npm i -g npm@latest
|
||||
/usr/local/bin/npm publish *.tgz
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Validate
|
||||
name: 🛡️ Validate
|
||||
on:
|
||||
workflow_call:
|
||||
pull_request:
|
||||
@@ -6,46 +6,92 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
checks:
|
||||
name: Checks
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm check
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Basic Checks
|
||||
run: pnpm check
|
||||
|
||||
builds:
|
||||
name: Builds
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm buildc all
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build All Packages
|
||||
run: pnpm buildc all
|
||||
|
||||
build-demo:
|
||||
name: Build Demo
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm build:all
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build
|
||||
run: pnpm build:all
|
||||
working-directory: packages/wxt-demo
|
||||
- run: pnpm wxt zip
|
||||
|
||||
- name: ZIP
|
||||
run: pnpm wxt zip
|
||||
working-directory: packages/wxt-demo
|
||||
|
||||
tests:
|
||||
runs-on: ubuntu-22.04
|
||||
name: Tests (${{ matrix.title }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- title: 'Linux'
|
||||
os: ubuntu-22.04
|
||||
coverage: true
|
||||
- title: 'Windows'
|
||||
os: windows-latest
|
||||
coverage: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- name: pnpm test:coverage
|
||||
run: pnpm test:coverage -- --reporter=default --reporter=hanging-process
|
||||
- uses: codecov/codecov-action@v4
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||
|
||||
- name: Run Tests
|
||||
if: ${{ ! matrix.coverage }}
|
||||
run: pnpm test
|
||||
|
||||
- name: Run Tests (Coverage)
|
||||
if: matrix.coverage
|
||||
run: pnpm test:coverage --reporter=default --reporter=hanging-process
|
||||
|
||||
- name: Upload Coverage
|
||||
if: matrix.coverage
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
windows-tests:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm test
|
||||
|
||||
template:
|
||||
name: Template
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -57,19 +103,34 @@ jobs:
|
||||
- vanilla
|
||||
- vue
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm pack
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Pack WXT package
|
||||
run: pnpm pack
|
||||
working-directory: packages/wxt
|
||||
- run: npm i
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm i
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: npm i -D ../../packages/wxt/wxt-*.tgz
|
||||
|
||||
- name: Install Packed WXT
|
||||
run: npm i -D ../../packages/wxt/wxt-*.tgz
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: pnpm compile
|
||||
|
||||
- name: Type Check Template
|
||||
run: pnpm compile
|
||||
if: matrix.template != 'svelte'
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: pnpm check
|
||||
|
||||
- name: Type Check Template
|
||||
run: pnpm check
|
||||
if: matrix.template == 'svelte'
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: pnpm build
|
||||
|
||||
- name: Build Template
|
||||
run: pnpm build
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
|
||||
@@ -1,26 +1,42 @@
|
||||
name: vhs
|
||||
name: 📼 VHS
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'docs/tapes/*.tape'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
vhs:
|
||||
name: Create VHS
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ github.repository == 'wxt-dev/wxt' }}
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
install: 'false'
|
||||
- name: Preinstall WXT
|
||||
install: false
|
||||
|
||||
# This prevents pnpm dlx from downloading WXT in the video
|
||||
- name: Pre-install WXT
|
||||
run: |
|
||||
pnpm store add wxt@latest
|
||||
pnpm dlx wxt@latest --version
|
||||
- uses: charmbracelet/vhs-action@v2.1.0
|
||||
|
||||
- name: Record VHS
|
||||
uses: charmbracelet/vhs-action@59641cdc7fadf3978db65eb8c6937ea2752f4ec3 # v2.1.0
|
||||
with:
|
||||
path: 'docs/tapes/init-demo.tape'
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
|
||||
- name: Save recorded GIF
|
||||
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
+2
-1
@@ -5,9 +5,10 @@
|
||||
.output
|
||||
.webextrc
|
||||
.wxt
|
||||
.wxt-runner
|
||||
*.log
|
||||
/.vscode/
|
||||
/docs/.vitepress/cache
|
||||
docs/.vitepress/.temp
|
||||
coverage
|
||||
dist
|
||||
node_modules
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint/refs/heads/main/schema/markdownlint-config-schema.json",
|
||||
"line-length": false,
|
||||
"no-inline-html": false,
|
||||
"first-line-heading": false
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
node_modules
|
||||
.git
|
||||
.output
|
||||
dist
|
||||
|
||||
# Generated files
|
||||
packages/wxt/README.md
|
||||
packages/*/CHANGELOG.md
|
||||
docs/api
|
||||
@@ -5,3 +5,4 @@ dist
|
||||
docs/.vitepress/cache
|
||||
pnpm-lock.yaml
|
||||
CHANGELOG.md
|
||||
packages/browser/src/gen
|
||||
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"davidanson.vscode-markdownlint",
|
||||
"esbenp.prettier-vscode",
|
||||
"github.vscode-github-actions"
|
||||
]
|
||||
}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
// Set default formatter
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
|
||||
"[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
||||
"[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
||||
"[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
||||
"[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
||||
|
||||
// Additional guidelines for Copilot
|
||||
"github.copilot.chat.codeGeneration.instructions": [
|
||||
{ "file": "CONTRIBUTING.md" }
|
||||
],
|
||||
"oxc.enable": true
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
<aaronklinker1@gmail.com>.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
<https://www.contributor-covenant.org/version/2/0/code_of_conduct.html>.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
<https://www.contributor-covenant.org/faq>. Translations are available at
|
||||
<https://www.contributor-covenant.org/translations>.
|
||||
+81
-5
@@ -6,11 +6,45 @@ 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.
|
||||
|
||||
## WXT's Vision
|
||||
|
||||
WXT is two things:
|
||||
|
||||
1. A build tool
|
||||
2. A set of runtime utilities
|
||||
|
||||
The long term goal of WXT is provide an opinionated build tool that keeps WXT projects standard, while providing light-weight runtime utils that simplify a lot of the boilerplate/overhead when setting up a new extension.
|
||||
|
||||
I also want to provide a way for developers to use either one of those two things independently, and not require them to use both. This is why all of WXT's runtime utils are shipped as their own NPM packages, most of them not bundled inside the core `wxt` package. If you just want to use the packages, they're availalbe, and if you just want to use WXT's built tool, you don't have to import any of WXT's utilities, you can use your own.
|
||||
|
||||
> The few runtime utils shipped inside WXT are things that should be used by 90% of extensions. That said, they're also legacy utils left in from before I started creating separate NPM packages, and in the future, they may be removed from the core package.
|
||||
|
||||
## 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.
|
||||
This project uses [Conventional Commit format](https://www.conventionalcommits.org/en/v1.0.0/) to automatically generate a changelog and better understand the changes in the project
|
||||
|
||||
Maintainers, commits to the `main` branch (either directly or via PRs) must be valid conventional commits.
|
||||
Here are some examples of conventional commit messages:
|
||||
|
||||
- `feat: add new functionality`
|
||||
- `fix: correct typos in code`
|
||||
- `ci: add GitHub Actions for automated testing`
|
||||
|
||||
## Conventional PR Titles
|
||||
|
||||
The title of your pull request should follow the [conventional commit format](#conventional-commits). When a pull request is merged to the main branch, all changes are going to be squashed into a single commit. The message of this commit will be the title of the pull request. And for every release, the commit messages are used to generate the changelog.
|
||||
|
||||
## Breaking Changes Policy
|
||||
|
||||
A quick word on WXT's breaking changes policy. I am willing to make breaking changes, but they have to be for a good enough reason - they have to make WXT better as a whole, they can't be based on one opinion.
|
||||
|
||||
Breaking changes also require a major release. Major releases have happened once or twice a year, so after you merge your PR, you'll have to wait a little bit before it is released.
|
||||
|
||||
To make a breaking change:
|
||||
|
||||
1. Make sure you're PR is targeting the `major` branch
|
||||
2. Add `!` after the conventional commit type (`fix!: ...`, `feat!: ...`, `chore!: ...`, etc) to indicate that it is a breaking change
|
||||
3. At the top of the PR, provide documentation that will inform developers about the breaking change, why it was done, and how to migrate their extension so nothing breaks.
|
||||
- This documentation will be put ["Upgrading WXT"](https://wxt.dev/guide/resources/upgrading.html) page in the docs, read through previous breaking change docs for an idea of what is required.
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -32,6 +66,7 @@ Here are some helpful commands:
|
||||
|
||||
```sh
|
||||
# Build WXT package
|
||||
cd packages/wxt
|
||||
pnpm build
|
||||
```
|
||||
|
||||
@@ -137,10 +172,51 @@ Releases are done with GitHub actions:
|
||||
|
||||
## Upgrading Dependencies
|
||||
|
||||
Use [`taze`](https://www.npmjs.com/package/taze) to upgrade dependencies throughout the entire monorepo.
|
||||
WXT has custom rules around what dependencies can be upgraded. Use the `scripts/upgrade-deps.ts` script to upgrade dependencies and follow these rules.
|
||||
|
||||
```sh
|
||||
pnpm dlx taze -r
|
||||
pnpm tsx scripts/upgrade-deps.ts
|
||||
```
|
||||
|
||||
Configuration is in [`taze.config.ts`](./taze.config.ts).
|
||||
To see all the options, run:
|
||||
|
||||
```sh
|
||||
pnpm tsx scripts/upgrade-deps.ts --help
|
||||
```
|
||||
|
||||
## Install Unreleased Versions
|
||||
|
||||
This repo uses <https://pkg.pr.new> to publish versions of all it's packages for almost every commit. You can install them via:
|
||||
|
||||
```sh
|
||||
npm i https://pkg.pr.new/[package-name]@[ref]
|
||||
```
|
||||
|
||||
Or use one of the shorthands:
|
||||
|
||||
```sh
|
||||
# Install the latest build of `wxt` from a PR:
|
||||
npm i https://pkg.pr.new/wxt@1283
|
||||
|
||||
# Install the latest build of `@wxt-dev/module-react` on the `main` branch
|
||||
npm i https://pkg.pr.new/@wxt-dev/module-react@main
|
||||
|
||||
# Install `@wxt-dev/storage` from a specific commit:
|
||||
npm i https://pkg.pr.new/@wxt-dev/module-react@426f907
|
||||
```
|
||||
|
||||
## Blog Posts
|
||||
|
||||
Anyone is welcome to submit a blog post on <https://wxt.dev/blog>!
|
||||
|
||||
> [!NOTE]
|
||||
> Before starting on a blog post, please message Aaron on Discord or start a discussion on GitHub to get permission to write about a topic, but most topics are welcome: Major version updates, tutorials, etc.
|
||||
|
||||
- **English only**: Blog posts should be written in English. Unfortunately, our maintainers don't have the bandwidth right now to translate our docs, let alone blog posts. Sorry 😓
|
||||
- **AI**: Please only use AI to translate or proof-read your blog post. Don't generate the whole thing... We don't want to publish that.
|
||||
|
||||
## Become a Maintainer
|
||||
|
||||
If you're interested in becoming a maintainer, send an email to Aaron at <aaronklinker1@gmail.com> with your github username saying you're interested. The process is very informal, I will add you quickly if you've contributed code or answered questions and helped out the community!
|
||||
|
||||
Maintainers don't have to just write code - they can manage issues, answer questsions, review PRs, organize and prioritize work - there's lots of ways for you to help out.
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Maintainers
|
||||
|
||||
A couple of things for you to consider before merging a PR or giving the go-ahead on a feature proposal.
|
||||
|
||||
## Be picky about new features and packages
|
||||
|
||||
We are responsible for maintaining them and fixing related bugs after the PR is merged. The community can always release their own WXT modules or packages, not everything needs to be built into WXT.
|
||||
|
||||
## Prefer standards over customization
|
||||
|
||||
Don't merge PRs that just add another way to do something, like [this one](https://github.com/wxt-dev/wxt/pull/2053#issuecomment-3857010196).
|
||||
|
||||
WXT is opinionated, if you have questions about what is WXT's opinion or we need to create a new one, create an issue and @ me to discuss.
|
||||
|
||||
## PRs should be small and targeted
|
||||
|
||||
A PR should make one change. They should not make any unrelated changes outside of accomplishing the one thing. This makes PRs easier to review and they get merged more quickly - a win-win for everyone.
|
||||
|
||||
## `@wxt-dev/*` packages are separate
|
||||
|
||||
We can't make changes to these packages assuming people are using them only with WXT. For example, I almost missed [this PR](https://github.com/wxt-dev/wxt/pull/2049#issuecomment-3861251599).
|
||||
|
||||
I want these packages to be usable on their own so if people don't like WXT's build tool, they can still use our other awesome packages.
|
||||
|
||||
## Minimize dependencies
|
||||
|
||||
I don't like how heavy lots of frameworks are. It's unavoidable to a certain extent, but if you can do something without another dependency, try to.
|
||||
|
||||
If you need a dependency, look for one with zero dependencies and that's respectable.
|
||||
|
||||
## Look at milestones
|
||||
|
||||
I've organized WXT's long term plans into [milestones](https://github.com/wxt-dev/wxt/milestones), check those out to get an idea of my priorities for some of the larger features.
|
||||
|
||||
## Require tests
|
||||
|
||||
If someone opens a PR to fix a bug but doesn't include tests, don't merge the PR. Tests are required to prevent regressions and maintain a codebase long term.
|
||||
|
||||
## Ask for reproductions for bugs
|
||||
|
||||
You don't need to triage bugs if someone doesn't give you enough information. You can always ask for a repo with a reproduction or wait for more details.
|
||||
|
||||
If there's not an easy way to reproduce a bug, you're wasting your time triaging it. Just be mindful of your own time!
|
||||
|
||||
Here's an example of how to ask for a reproduction: <https://github.com/wxt-dev/wxt/issues/2064#issuecomment-3862579110>. You could be more blunt than this.
|
||||
|
||||
## Add yourself as a code owner
|
||||
|
||||
If you want to be responsible for a specific package or directory, add yourself to the [`.github/CODEOWNERS`](https://github.com/wxt-dev/wxt/blob/main/.github/CODEOWNERS) file to get added as a reviewer to PRs automatically. You can also add yourself to the default list to be added as a reviewer on all PRs.
|
||||
@@ -1,50 +1,32 @@
|
||||
<h1 align="center">
|
||||
<img align="top" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo">
|
||||
<span>WXT</span>
|
||||
</h1>
|
||||
<div align="center">
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm version" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<span> </span>
|
||||
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="downloads" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<span> </span>
|
||||
<a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE" target="_blank"><img alt="license | MIT" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<span> </span>
|
||||
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank"><img alt="coverage" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048"></a>
|
||||
</p>
|
||||
# <img align="top" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo"> WXT
|
||||
|
||||
<p align="center">
|
||||
<span>Next-gen framework for developing web extensions.</span>
|
||||
<br/>
|
||||
<span>⚡</span>
|
||||
<br/>
|
||||
<q><i>It's like Nuxt, but for Web Extensions</i></q>
|
||||
</p>
|
||||
[](https://www.npmjs.com/package/wxt)
|
||||
[](https://www.npmjs.com/package/wxt)
|
||||
[](https://github.com/wxt-dev/wxt/blob/main/LICENSE)
|
||||
[](https://codecov.io/github/wxt-dev/wxt)
|
||||
|
||||
<p align="center">
|
||||
<a href="https://wxt.dev/guide/installation.html" target="_blank">Get Started</a>
|
||||
•
|
||||
<a href="https://wxt.dev/api/config.html" target="_blank">Configuration</a>
|
||||
•
|
||||
<a href="https://wxt.dev/examples.html" target="_blank">Examples</a>
|
||||
•
|
||||
<a href="https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md" target="_blank">Changelog</a>
|
||||
•
|
||||
<a href="https://discord.gg/ZFsZqGery9" target="_blank">Discord</a>
|
||||
</p>
|
||||
Next-gen framework for developing web extensions.<br/>⚡<br/><q><i>It's like Nuxt, but for Web Extensions</i></q>
|
||||
|
||||
[Get Started](https://wxt.dev/guide/installation.html) •
|
||||
[Configuration](https://wxt.dev/api/config.html) •
|
||||
[Examples](https://wxt.dev/examples.html) •
|
||||
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md) •
|
||||
[Discord](https://discord.gg/ZFsZqGery9)
|
||||
|
||||
</div>
|
||||
|
||||

|
||||
|
||||
## Demo
|
||||
|
||||
https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94
|
||||
<https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94>
|
||||
|
||||
## Quick Start
|
||||
|
||||
Bootstrap a new project:
|
||||
|
||||
<!-- automd:pm-x version="latest" name="wxt" args="init" -->
|
||||
|
||||
```sh
|
||||
# npm
|
||||
npx wxt@latest init
|
||||
@@ -56,8 +38,6 @@ pnpm dlx wxt@latest init
|
||||
bunx wxt@latest init
|
||||
```
|
||||
|
||||
<!-- /automd -->
|
||||
|
||||
Or see the [installation guide](https://wxt.dev/guide/installation.html) to get started with WXT.
|
||||
|
||||
## Features
|
||||
@@ -75,15 +55,15 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
|
||||
- 📏 Bundle analysis
|
||||
- ⬇️ Download and bundle remote URL imports
|
||||
|
||||
## Contributors
|
||||
## Sponsors
|
||||
|
||||
<!-- automd:contributors author="aklinker1" license="MIT" github="wxt-dev/wxt" -->
|
||||
WXT is a [MIT-licensed](https://github.com/wxt-dev/wxt/blob/main/LICENSE) open source project with its ongoing development made possible entirely by the support of these awesome backers. If you'd like to join them, please consider [sponsoring WXT's development](https://github.com/sponsors/wxt-dev).
|
||||
|
||||
[](https://github.com/sponsors/wxt-dev)
|
||||
|
||||
## Contributors
|
||||
|
||||
Published under the [MIT](https://github.com/wxt-dev/wxt/blob/main/LICENSE) license.
|
||||
Made by [@aklinker1](https://github.com/aklinker1) and [community](https://github.com/wxt-dev/wxt/graphs/contributors) 💛
|
||||
<br><br>
|
||||
<a href="https://github.com/wxt-dev/wxt/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=wxt-dev/wxt" />
|
||||
</a>
|
||||
|
||||
<!-- /automd -->
|
||||
[](https://github.com/wxt-dev/wxt/graphs/contributors)
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# Security Policy
|
||||
|
||||
While WXT is in prerelease, only the latest version will receive security updates. The latest version is:
|
||||
|
||||
<img alt="npm version" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048">
|
||||
|
||||
<!--
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Use this section to tell people how to report a vulnerability.
|
||||
|
||||
Tell them where to go, how often they can expect to get an update on a
|
||||
reported vulnerability, what to expect if the vulnerability is accepted or
|
||||
declined, etc.
|
||||
-->
|
||||
@@ -0,0 +1,70 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
// @ts-expect-error: Vitepress data-loader magic, this import is correct
|
||||
import { data } from '../loaders/blog.data';
|
||||
import BlogPostPreview from './BlogPostPreview.vue';
|
||||
|
||||
const posts = computed(() =>
|
||||
data
|
||||
.map((post) => ({
|
||||
...post,
|
||||
...post.frontmatter,
|
||||
date: new Date(post.frontmatter.date),
|
||||
}))
|
||||
.sort((a, b) => b.date.getTime() - a.date.getTime()),
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<div>
|
||||
<div class="vp-doc">
|
||||
<h1>Blog</h1>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<BlogPostPreview v-for="post of posts" :key="post.url" :post />
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.container > div {
|
||||
padding: 32px;
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
list-style: none;
|
||||
}
|
||||
ul,
|
||||
li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul li {
|
||||
padding-top: 16px;
|
||||
margin-top: 16px;
|
||||
border-top: 1px solid var(--vp-c-default);
|
||||
}
|
||||
ul li:last-child {
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid var(--vp-c-default);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,76 @@
|
||||
<script lang="ts" setup>
|
||||
import useBlogDate from '../composables/useBlogDate';
|
||||
import { useData } from 'vitepress';
|
||||
|
||||
const { frontmatter } = useData();
|
||||
const date = useBlogDate(() => frontmatter.value.date);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vp-doc">
|
||||
<main class="container-content">
|
||||
<h1 v-html="$frontmatter.title" />
|
||||
<p class="meta-row">
|
||||
<a
|
||||
class="author"
|
||||
v-for="author of $frontmatter.authors"
|
||||
:key="author.github"
|
||||
:href="`https://github.com/${author.github}`"
|
||||
>
|
||||
<img :src="`https://github.com/${author.github}.png?size=96`" />
|
||||
<span>{{ author.name }}</span>
|
||||
</a>
|
||||
<span>•</span>
|
||||
<span>{{ date }}</span>
|
||||
</p>
|
||||
<Content />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
vp-doc {
|
||||
display: flex;
|
||||
}
|
||||
main {
|
||||
max-width: 1080px;
|
||||
padding: 32px;
|
||||
margin: auto;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
main {
|
||||
padding: 64px;
|
||||
}
|
||||
}
|
||||
.meta-row {
|
||||
display: flex;
|
||||
color: var(--vp-c-text-2);
|
||||
gap: 16px;
|
||||
overflow: hidden;
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
.meta-row > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.author {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
}
|
||||
.author img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
.author span {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.author:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,72 @@
|
||||
<script lang="ts" setup>
|
||||
import useBlogDate from '../composables/useBlogDate';
|
||||
|
||||
const props = defineProps<{
|
||||
post: {
|
||||
title: string;
|
||||
description?: string;
|
||||
date: Date;
|
||||
url: string;
|
||||
authors: Array<{ name: string; github: string }>;
|
||||
};
|
||||
}>();
|
||||
|
||||
const date = useBlogDate(() => props.post.date);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li class="blog-list-item">
|
||||
<a :href="post.url">
|
||||
<div class="vp-doc">
|
||||
<h3 class="title" v-html="post.title" />
|
||||
<p class="description" v-html="post.description" />
|
||||
<p class="meta">
|
||||
{{ post.authors.map((author) => author.name).join(', ') }}
|
||||
•
|
||||
{{ date }}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
li > a > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 -16px;
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
li > a > div:hover {
|
||||
background: var(--vp-c-default);
|
||||
}
|
||||
li .title {
|
||||
color: var(--vp-c-text);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
li .description {
|
||||
font-size: 16px;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
li .meta {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
</style>
|
||||
@@ -4,11 +4,8 @@ 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
|
||||
// Add extension IDs to end of the list. On the website, extensions will be sorted by a combination of weekly active users and rating.
|
||||
// Change the commit message or PR title to: "docs: Added "[extension name]" to the homepage"
|
||||
const chromeExtensionIds = [
|
||||
'ocfdgncpifmegplaglcnglhioflaimkd', // GitHub: Better Line Counts
|
||||
'mgmdkjcljneegjfajchedjpdhbadklcf', // Anime Skip Player
|
||||
@@ -16,7 +13,7 @@ const chromeExtensionIds = [
|
||||
'elfaihghhjjoknimpccccmkioofjjfkf', // StayFree - Website Blocker & Web Analytics
|
||||
'okifoaikfmpfcamplcfjkpdnhfodpkil', // Doozy: Ai Made Easy
|
||||
'lknmjhcajhfbbglglccadlfdjbaiifig', // tl;dv - Record, Transcribe & ChatGPT for Google Meet
|
||||
'youtube中文配音/oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
|
||||
'oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
|
||||
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
|
||||
'aiakblgmlabokilgljkglggnpflljdgp', // Markdown Sticky Notes
|
||||
'nomnkbngkijpffepcgbbofhcnafpkiep', // DocVersionRedirector
|
||||
@@ -50,6 +47,76 @@ const chromeExtensionIds = [
|
||||
'fcphghnknhkimeagdglkljinmpbagone', // YouTube Auto HD + FPS
|
||||
'lpomjgbicdemjkgmbnkjncgdebogkhlb', // MultiViewer Companion
|
||||
'ggiafipgeeaaahnjamgpjcgkdpanhddg', // Sync Watch - Watch videos together on any site
|
||||
'nmldnjcblcihmegipecakhmnieiofmgl', // Keyword Rank Checker
|
||||
'gppllamhaciichleihemgilcpledblpn', // YouTube Simple View - Hide distractions & more
|
||||
'pccbghdfdnnkkbcdcibchpbffdgednkf', // Propbar - Property Data Enhancer
|
||||
'lfknakglefggmdkjdfhhofkjnnolffkh', // Text Search Pro - Search by case and whole-word match!
|
||||
'mbenhbocjckkbaojacmaepiameldglij', // Invoice Generator
|
||||
'phlfhkmdofajnbhgmbmjkbkdgppgoppb', // Monthly Bill Tracker
|
||||
'macmkmchfoclhpbncclinhjflmdkaoom', // Wandpen - Instantly improve your writing with AI
|
||||
'lhmgechokhmdekdpgkkemoeecelcaonm', // YouTube Hider - Remove Comments By Keywords, Usernames & Tools
|
||||
'imgheieooppmahcgniieddodaliodeeg', // QA Compass - Record standardized bug reports easily
|
||||
'npgghjedpchajflknnbngajkjkdhncdo', // aesthetic Notion, styled
|
||||
'hmdcmlfkchdmnmnmheododdhjedfccka', // Eye Dropper
|
||||
'eihpmapodnppeemkhkbhikmggfojdkjd', // Cursorful - Screen Recorder with Auto Zoom
|
||||
'hjjkgbibknbahijglkffklflidncplkn', // Show IP – Live View of Website IPs for Developers
|
||||
'ilbikcehnpkmldojkcmlldkoelofnbde', // Strong Password Generator
|
||||
'ocllfkhcdopiafndigclebelbecaiocp', // ZenGram: Mindful Instagram, Your Way
|
||||
'odffpjnpocjfcaclnenaaaddghkgijdb', // Blync: Preview Links, Selection Search, AI Assistant
|
||||
'kofbbilhmnkcmibjbioafflgmpkbnmme', // HTML to Markdown - Convert webpages to markdown
|
||||
'boecmgggeigllcdocgioijmleimjbfkg', // Walmart WFS Profit Calculator
|
||||
'dlnjcbkmomenmieechnmgglgcljhoepd', // Youtube Live Chat Fullscreen
|
||||
'keiealdacakpnbbljlmhfgcebmaadieg', // Python Code Runner
|
||||
'hafcajcllbjnoolpfngclfmmgpikdhlm', // Monochromate
|
||||
'bmoggiinmnodjphdjnmpcnlleamkfedj', // AliasVault - Open-Source Password & (Email) Alias Manager
|
||||
'hlnhhamckimoaiekbglafiebkfimhapb', // SnapThePrice: AI-Powered Real-time Lowest Price Finder
|
||||
'gdjampjdgjmbifnhldgcnccdjkcoicmg', // radiofrance - news & broadcasts (French), music (international)
|
||||
'jlnhphlghikichhgbnkepenehbmloenb', // Blens - Time Tracker and AI Insight
|
||||
'njnammmpdodmfkodnfpammnpdcbhnlcm', // Always Light Mode - Setting website always in light mode
|
||||
'lblmfclcfniabobmamfkdogcgdagbhhb', // DesignPicker - Color Picker & Font Detector
|
||||
'pamnlaoeobcmhkliljfaofekeddpmfoh', // Web to PDF
|
||||
'jmbcbeepjfenihlocplnbmbhimcoooka', // Online CSV Viewer
|
||||
'nkjcoophmpcmmgadnljnlpbpfdfacgbo', // YouTube Video Transcript
|
||||
'lcaieahkjgeggeiihblhcjbbjlppgieh', // NetSuite Record Scripts
|
||||
'gmocfknjllodfiomnljmaehcplnekhlo', // VueTracker
|
||||
'ggcfemmoabhhelfkhknhbnkmeahloiod', // CanCopy - A web extension that allow you to copy any content from website
|
||||
'modkelfkcfjpgbfmnbnllalkiogfofhb', // Language Learning with AI
|
||||
'npfopljnjbamegincfjelhjhnonnjloo', // Bilibili Feed History Helper
|
||||
'edkhpdceeinkcacjdgebjehipmnbomce', // NZBDonkey - The ultimate NZB file download tool
|
||||
'cckggnbnimdbbpmdinkkgbbncopbloob', // WeChat Markdown Editor(微信 Markdown 编辑器)
|
||||
'jcblcjolcojmfopefcighfmkkefbaofg', // Tab Grab
|
||||
'eehmoikadcijkapfjocnhjclpbaindlb', // BrowserLens - https://browserlens.com/
|
||||
'hfhellofkjebbchcdffmicekjdomkcmc', // Epic Games Library Extension
|
||||
'gknigcbhlammoakmmdddkblknanpjiac', // Zen Analytics Pixel Tracker - zapt.web.app
|
||||
'cnklededohhcbmjjdlbjdkkihkgoggol', // Crypto Pulse - Compose your newtab with nature images, widgets & realtime Crypto Price & Bitcoin RSS.
|
||||
'miponnamafdenpgjemkknimgjfibicdc', // Youtube Video Scheduler
|
||||
'nhmbcmalgpkjbomhlhgdicanmkkaajmg', // Chatslator: Livestream Chat Translator
|
||||
'mbamjfdjbcdgpopfnkkmlohadbbnplhm', // 公众号阅读增强器 - https://wxreader.honwhy.wang
|
||||
'hannhecbnjnnbbafffmogdlnajpcomek', // 토탐정
|
||||
'ehboaofjncodknjkngdggmpdinhdoijp', // 2FAS Pass - https://2fas.com/
|
||||
'hnjamiaoicaepbkhdoknhhcedjdocpkd', // Quick Prompt - https://github.com/wenyuanw/quick-prompt
|
||||
'kacblhilkacgfnkjfodalohcnllcgmjd', // Add QR Code Generator Icon Back To Address Bar
|
||||
'fkbdlogfdjmpfepbbbjcgcfbgbcfcnne', // Piwik PRO Tracking Helper
|
||||
'nkbikckldmljjiiajklecmgmajgapbfl', // PIPX - Take Control of Picture-in-Picture, Automatically
|
||||
'hgppdobcpkfkmiegekaglonjajeojmdd', // Browsely - AI-powered browser extension
|
||||
'ehmoihnjgkdimihkhokkmfjdgomohjgm', // Filmbudd Pro - Simple, private – and synced ratings and watch notes across all your devices
|
||||
'alglchohmdikgdjhafiicilegegieafa', // MultiField CopyCat - Copy, Paste & Autofill Web Forms Instantly
|
||||
'aamihahiiogceidpbnfgehacgiecephe', // ChatSight - Add Table of Contents to ChatGPT
|
||||
'cndibmoanboadcifjkjbdpjgfedanolh', // BetterCampus (prev. BetterCanvas)
|
||||
'hinfimgacobnellbncbcpdlpaapcofaa', // Leetcode Fonts - Change fonts in leetcode effortlessly
|
||||
'kbkbfefhhabpkibojinapkkgciiacggg', // TranslateManga - Manga Translator & Manga Tracker
|
||||
'emeakbgdecgmdjgegnejpppcnkcnoaen', // SiteData - Free Website Traffic Checker & Reverse AdSense Tool
|
||||
'gpnckbhgpnjciklpoehkmligeaebigaa', // Livestream Chat Reader - Text-to-Speech for YouTube/Twitch chat
|
||||
'fjlalaedpfcojcfpkgkglbjjbbkofgnl', // ChatGPT Token Counter - Count tokens in real time on chatgpt conversation
|
||||
'fbgblmjbeebanackldpbmpacppflgmlj', // LinuxDo Scripts - 为 linux.do 用户提供了一些增强功能
|
||||
'dfacnjidgbagicaekenjgclfnhdnjjdi', // Zen Virtual Piano - https://zen-piano.web.app/
|
||||
'naeibcegmgpofimedkmfgjgphfhfhlab', // Crypto Pulse price tracker - https://get-crypto-pulse.web.app/
|
||||
'ffglckbhfbfmdkefdmjbhpnffkcmlhdh', // Redirect Web - Automatically redirect pages or open them in another app
|
||||
'eglpfhbhmelampoihamjomgkeobgdofl', // Capture It - Capture & Edit Screenshots
|
||||
'jmghclbfbbapimhbgnpffbimphlpolnm', // Teams Chat Exporter
|
||||
'jdcppdokgfbnhiacbeplahgnciahnhck', // Lofi BGM Player - Free lofi focus music for work & study
|
||||
'cgpmbiiagnehkikhcbnhiagfomajncpa', // Margin - Annotate and highlight any webpage, with your notes saved to the decentralized AT Protocol.
|
||||
'mfjdonmgmgcijagclnkfhmjiblbfjaid', // KeyFloat - Floating multilingual keyboard with native key mappings, drag, dark mode, sounds, and dynamic layouts for macOS & Windows
|
||||
];
|
||||
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { computed, toValue, MaybeRefOrGetter } from 'vue';
|
||||
|
||||
const MONTH_FORMATTER = new Intl.DateTimeFormat(
|
||||
globalThis?.navigator?.language,
|
||||
{
|
||||
month: 'long',
|
||||
},
|
||||
);
|
||||
|
||||
export default function (date: MaybeRefOrGetter<Date | string>) {
|
||||
return computed(() => {
|
||||
const d = new Date(toValue(date));
|
||||
return `${MONTH_FORMATTER.format(d)} ${d.getDate()}, ${d.getFullYear()}`;
|
||||
});
|
||||
}
|
||||
@@ -35,6 +35,9 @@ export default function (ids: string[]) {
|
||||
query,
|
||||
variables: { ids },
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(async (res) => {
|
||||
isLoading.value = false;
|
||||
|
||||
+106
-11
@@ -1,4 +1,4 @@
|
||||
import { defineConfig } from 'vitepress';
|
||||
import { DefaultTheme, defineConfig } from 'vitepress';
|
||||
import typedocSidebar from '../api/reference/typedoc-sidebar.json';
|
||||
import {
|
||||
menuGroup,
|
||||
@@ -13,26 +13,105 @@ import { version as wxtVersion } from '../../packages/wxt/package.json';
|
||||
import { version as i18nVersion } from '../../packages/i18n/package.json';
|
||||
import { version as autoIconsVersion } from '../../packages/auto-icons/package.json';
|
||||
import { version as unocssVersion } from '../../packages/unocss/package.json';
|
||||
import { version as storageVersion } from '../../packages/storage/package.json';
|
||||
import { version as analyticsVersion } from '../../packages/analytics/package.json';
|
||||
import { version as runnerVersion } from '../../packages/runner/package.json';
|
||||
import addKnowledge from 'vitepress-knowledge';
|
||||
import {
|
||||
groupIconMdPlugin,
|
||||
groupIconVitePlugin,
|
||||
localIconLoader,
|
||||
} from 'vitepress-plugin-group-icons';
|
||||
import { Feed } from 'feed';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import llmstxt from 'vitepress-plugin-llms';
|
||||
|
||||
const origin = 'https://wxt.dev';
|
||||
|
||||
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.";
|
||||
"WXT provides the best developer experience, making it quick, easy, and fun to develop web extensions. 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';
|
||||
const ogUrl = origin;
|
||||
const ogImage = `${origin}/social-preview.png`;
|
||||
|
||||
const otherPackages = {
|
||||
analytics: analyticsVersion,
|
||||
'auto-icons': autoIconsVersion,
|
||||
i18n: i18nVersion,
|
||||
storage: storageVersion,
|
||||
unocss: unocssVersion,
|
||||
runner: runnerVersion,
|
||||
};
|
||||
|
||||
const knowledge = addKnowledge<DefaultTheme.Config>({
|
||||
serverUrl: 'https://knowledge.wxt.dev',
|
||||
paths: {
|
||||
'/': 'docs',
|
||||
'/api/': 'api-reference',
|
||||
'/blog/': 'blog',
|
||||
},
|
||||
layoutSelectors: {
|
||||
blog: '.container-content',
|
||||
},
|
||||
pageSelectors: {
|
||||
'examples.md': '#VPContent > .VPPage',
|
||||
'blog.md': '#VPContent > .VPPage',
|
||||
},
|
||||
});
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
extends: knowledge,
|
||||
|
||||
titleTemplate: `:title${titleSuffix}`,
|
||||
title: 'WXT',
|
||||
description,
|
||||
vite: {
|
||||
clearScreen: false,
|
||||
plugins: [
|
||||
llmstxt(),
|
||||
groupIconVitePlugin({
|
||||
customIcon: {
|
||||
'wxt.config.ts': localIconLoader(
|
||||
import.meta.url,
|
||||
'../public/logo.svg',
|
||||
),
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
lastUpdated: true,
|
||||
sitemap: {
|
||||
hostname: 'https://wxt.dev',
|
||||
hostname: origin,
|
||||
},
|
||||
|
||||
async buildEnd(site) {
|
||||
// @ts-expect-error: knowledge.buildEnd is not typed, but it exists.
|
||||
await knowledge.buildEnd(site);
|
||||
|
||||
// Only construct the RSS document for production builds
|
||||
const { default: blogDataLoader } = await import('./loaders/blog.data');
|
||||
const posts = await blogDataLoader.load();
|
||||
const feed = new Feed({
|
||||
copyright: 'MIT',
|
||||
id: 'wxt',
|
||||
title: 'WXT Blog',
|
||||
link: `${origin}/blog`,
|
||||
});
|
||||
posts.forEach((post) => {
|
||||
feed.addItem({
|
||||
date: post.frontmatter.date,
|
||||
link: new URL(post.url, origin).href,
|
||||
title: post.frontmatter.title,
|
||||
description: post.frontmatter.description,
|
||||
});
|
||||
});
|
||||
// console.log('rss.xml:');
|
||||
// console.log(feed.rss2());
|
||||
await writeFile(join(site.outDir, 'rss.xml'), feed.rss2(), 'utf8');
|
||||
},
|
||||
|
||||
head: [
|
||||
@@ -51,6 +130,10 @@ export default defineConfig({
|
||||
markdown: {
|
||||
config: (md) => {
|
||||
md.use(footnote);
|
||||
md.use(groupIconMdPlugin);
|
||||
},
|
||||
languageAlias: {
|
||||
mjs: 'js',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -61,6 +144,16 @@ export default defineConfig({
|
||||
alt: 'WXT logo',
|
||||
},
|
||||
|
||||
footer: {
|
||||
message: [
|
||||
'<a class="light-netlify" href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-bg.svg" alt="Deploys by Netlify" style="display: inline;" /></a>',
|
||||
'<a class="dark-netlify" href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-accent.svg" alt="Deploys by Netlify" style="display: inline;" /></a>',
|
||||
'Released under the <a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE">MIT License</a>.',
|
||||
].join('<br/>'),
|
||||
copyright:
|
||||
'Copyright © 2023-present <a href="https://github.com/aklinker1">Aaron Klinker</a>',
|
||||
},
|
||||
|
||||
editLink: {
|
||||
pattern: 'https://github.com/wxt-dev/wxt/edit/main/docs/:path',
|
||||
},
|
||||
@@ -78,6 +171,7 @@ export default defineConfig({
|
||||
navItem('Guide', '/guide/installation'),
|
||||
navItem('Examples', '/examples'),
|
||||
navItem('API', '/api/reference/wxt'),
|
||||
navItem('Blog', '/blog'),
|
||||
navItem(`v${wxtVersion}`, [
|
||||
navItem('wxt', [
|
||||
navItem(`v${wxtVersion}`, '/'),
|
||||
@@ -86,12 +180,12 @@ export default defineConfig({
|
||||
'https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md',
|
||||
),
|
||||
]),
|
||||
navItem('Other Packages', [
|
||||
navItem(`wxt/storage — ${wxtVersion}`, '/storage'),
|
||||
navItem(`@wxt-dev/auto-icons — ${autoIconsVersion}`, '/auto-icons'),
|
||||
navItem(`@wxt-dev/i18n — ${i18nVersion}`, '/i18n'),
|
||||
navItem(`@wxt-dev/unocss — ${unocssVersion}`, '/unocss'),
|
||||
]),
|
||||
navItem(
|
||||
'Other Packages',
|
||||
Object.entries(otherPackages).map(([name, version]) =>
|
||||
navItem(`@wxt-dev/${name} — ${version}`, `/${name}`),
|
||||
),
|
||||
),
|
||||
]),
|
||||
],
|
||||
|
||||
@@ -141,6 +235,7 @@ export default defineConfig({
|
||||
menuGroup('Resources', '/guide/resources/', [
|
||||
menuItem('Compare', 'compare.md'),
|
||||
menuItem('FAQ', 'faq.md'),
|
||||
menuItem('Community', 'community.md'),
|
||||
menuItem('Upgrading WXT', 'upgrading.md'),
|
||||
menuItem('Migrate to WXT', 'migrate.md'),
|
||||
menuItem('How WXT Works', 'how-wxt-works.md'),
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { createContentLoader } from 'vitepress';
|
||||
|
||||
export default createContentLoader('blog/*.md');
|
||||
@@ -1,72 +1,28 @@
|
||||
/* Colors */
|
||||
:root {
|
||||
--wxt-c-green: #53bc4a;
|
||||
--wxt-c-green-1: #0b8a00;
|
||||
--wxt-c-green-2: #096600;
|
||||
--wxt-c-green-3: #096600;
|
||||
--wxt-c-green-soft: rgba(11, 138, 0, 0.14);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--wxt-c-green-1: #67d45e;
|
||||
--wxt-c-green-2: #4fa048;
|
||||
--wxt-c-green-3: #447e3f;
|
||||
--wxt-c-green-2: #329929;
|
||||
--wxt-c-green-3: #21651b;
|
||||
--wxt-c-green-soft: rgba(103, 212, 94, 0.14);
|
||||
}
|
||||
|
||||
/* 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-c-brand-soft: var(--wxt-c-green-soft);
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
/* Customize Individual Components */
|
||||
|
||||
.vp-doc .no-vertical-dividers th,
|
||||
.vp-doc .no-vertical-dividers td {
|
||||
@@ -97,3 +53,16 @@ body {
|
||||
vertical-align: middle;
|
||||
background-color: var(--vp-c-default-2);
|
||||
}
|
||||
|
||||
.light-netlify {
|
||||
display: inline;
|
||||
}
|
||||
.dark .light-netlify {
|
||||
display: none;
|
||||
}
|
||||
.dark-netlify {
|
||||
display: none;
|
||||
}
|
||||
.dark .dark-netlify {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
@@ -3,14 +3,18 @@ import Icon from '../components/Icon.vue';
|
||||
import EntrypointPatterns from '../components/EntrypointPatterns.vue';
|
||||
import UsingWxtSection from '../components/UsingWxtSection.vue';
|
||||
import ExampleSearch from '../components/ExampleSearch.vue';
|
||||
import BlogLayout from '../components/BlogLayout.vue';
|
||||
import './custom.css';
|
||||
import 'virtual:group-icons.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('ExampleSearch', ExampleSearch);
|
||||
ctx.app
|
||||
.component('Icon', Icon)
|
||||
.component('EntrypointPatterns', EntrypointPatterns)
|
||||
.component('UsingWxtSection', UsingWxtSection)
|
||||
.component('ExampleSearch', ExampleSearch)
|
||||
.component('blog', BlogLayout);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -28,13 +28,13 @@ export function menuRoot(items: SidebarItem[]) {
|
||||
export function menuGroup(
|
||||
text: string,
|
||||
items: SidebarItem[],
|
||||
collapsable?: boolean,
|
||||
collapsible?: boolean,
|
||||
): SidebarItem;
|
||||
export function menuGroup(
|
||||
text: string,
|
||||
base: string,
|
||||
items: SidebarItem[],
|
||||
collapsable?: boolean,
|
||||
collapsible?: boolean,
|
||||
): SidebarItem;
|
||||
export function menuGroup(
|
||||
text: string,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<!--@include: ../packages/analytics/README.md-->
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 131 KiB |
@@ -0,0 +1,9 @@
|
||||
---
|
||||
layout: page
|
||||
---
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BlogHome from './.vitepress/components/BlogHome.vue';
|
||||
</script>
|
||||
|
||||
<BlogHome />
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
layout: blog
|
||||
title: Real World Messaging
|
||||
description: |
|
||||
The extension messaging APIs are difficult to learn. Let's go beyond the simple examples from Chrome and Firefox's documentation to build our own simple messaging system from scratch.
|
||||
authors:
|
||||
- name: Aaron Klinker
|
||||
github: aklinker1
|
||||
date: 2024-10-20T04:54:23.601Z
|
||||
---
|
||||
|
||||
Test content **bold** _italic_
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
layout: blog
|
||||
title: Introducing <code>#imports</code>
|
||||
description: Learn how WXT's new <code>#imports</code> module works and how to use it.
|
||||
authors:
|
||||
- name: Aaron Klinker
|
||||
github: aklinker1
|
||||
date: 2024-12-06T14:39:00.000Z
|
||||
---
|
||||
|
||||
WXT v0.20 introduced a new way of manually importing its APIs: **the `#imports` module**. This module was introduced to simplify import statements and provide more visibility into all the APIs WXT provides.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
import { browser } from 'wxt/browser'; // [!code --]
|
||||
import { createShadowRootUi } from 'wxt/utils/content-script-ui/shadow-root'; // [!code --]
|
||||
import { defineContentScript } from 'wxt/utils/define-content-script'; // [!code --]
|
||||
import { injectScript } from 'wxt/utils/inject-script'; // [!code --]
|
||||
import { // [!code ++]
|
||||
browser, createShadowRootUi, defineContentScript, injectScript // [!code ++]
|
||||
} from '#imports'; // [!code ++]
|
||||
```
|
||||
|
||||
The `#imports` module is considered a "virtual module", because the file doesn't actually exist. At build-time, imports are split into individual statements for each API:
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [What you write]
|
||||
import { defineContentScript, injectScript } from '#imports';
|
||||
```
|
||||
|
||||
```ts [What the bundler sees]
|
||||
import { defineContentScript } from 'wxt/utils/define-content-script';
|
||||
import { injectScript } from 'wxt/utils/inject-script';
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Think of `#imports` as a convenient way to access all of WXT's APIs from one place, without impacting performance or bundle size.
|
||||
|
||||
This enables better tree-shaking compared to v0.19 and below.
|
||||
|
||||
:::tip Need to lookup the full import path of an API?
|
||||
Open up your project's `.wxt/types/imports-module.d.ts` file.
|
||||
:::
|
||||
|
||||
## Mocking
|
||||
|
||||
When writing tests, you might need to mock APIs from the `#imports` module. While mocking these APIs is very easy, it may not be immediately clear how to accomplish it.
|
||||
|
||||
Let's look at an example using Vitest. When [configured with `wxt/testing`](/guide/essentials/unit-testing#vitest), Vitest sees the same transformed code as the bundler. That means to mock an API from `#imports`, you need to call `vi.mock` with the real import path, not `#imports`:
|
||||
|
||||
```ts
|
||||
import { injectScript } from '#imports';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
vi.mock('wxt/utils/inject-script')
|
||||
const injectScriptMock = vi.mocked(injectScript);
|
||||
|
||||
injectScriptMock.mockReturnValueOnce(...);
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
You don't have to use `#imports` if you don't like - you can continue importing APIs from their submodules. However, using `#imports` is the recommended approach moving forwards.
|
||||
|
||||
- As more APIs are added, you won't have to memorize additional import paths.
|
||||
- If breaking changes are made to import paths in future major versions, `#imports` won't break.
|
||||
|
||||
Happy Coding 😄
|
||||
|
||||
> P.S. Yes, this is exactly how [Nuxt's `#imports`](https://nuxt.com/docs/guide/concepts/auto-imports#explicit-imports) works! We use the exact same library, [`unimport`](https://github.com/unjs/unimport).
|
||||
|
||||
---
|
||||
|
||||
[Discuss this blog post on Github](https://github.com/wxt-dev/wxt/discussions/1543).
|
||||
@@ -16,7 +16,8 @@ img.src = imageUrl;
|
||||
```
|
||||
|
||||
```html [HTML]
|
||||
<img src="~/assets/image.png" />
|
||||
<!-- In HTML tags, you must use the relative path --->
|
||||
<img src="../assets/image.png" />
|
||||
```
|
||||
|
||||
```css [CSS]
|
||||
@@ -25,11 +26,27 @@ img.src = imageUrl;
|
||||
}
|
||||
```
|
||||
|
||||
```vue [Vue]
|
||||
<script>
|
||||
import image from '~/assets/image.png';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img :src="image" />
|
||||
</template>
|
||||
```
|
||||
|
||||
```jsx [JSX]
|
||||
import image from '~/assets/image.png';
|
||||
|
||||
<img src={image} />;
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## `/public` Directory
|
||||
|
||||
Files inside `<srcDir>/public/` are copied into the output folder as-is, without being processed by WXT's bundler.
|
||||
Files inside `<rootDir>/public/` are copied into the output folder as-is, without being processed by WXT's bundler.
|
||||
|
||||
Here's how you access them:
|
||||
|
||||
@@ -52,6 +69,20 @@ img.src = imageUrl;
|
||||
}
|
||||
```
|
||||
|
||||
```vue [Vue]
|
||||
<template>
|
||||
<img src="/image.png" />
|
||||
</template>
|
||||
```
|
||||
|
||||
```jsx [JSX]
|
||||
<img src="/image.png" />
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::warning
|
||||
Assets in the `public/` directory are **_not_** accessible in content scripts by default. To use a public asset in a content script, you must add it to your manifest's [`web_accessible_resources` array](/api/reference/wxt/type-aliases/UserManifest#web-accessible-resources).
|
||||
:::
|
||||
|
||||
## Inside Content Scripts
|
||||
@@ -62,8 +93,7 @@ But, inside content scripts, the hostname is whatever the tab is set to. So if y
|
||||
|
||||
To fix this, you need to convert the image to a full URL using `browser.runtime.getURL`:
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
```ts [entrypoints/content.ts]
|
||||
import iconUrl from '/icon/128.png';
|
||||
|
||||
export default defineContentScript({
|
||||
@@ -77,6 +107,82 @@ export default defineContentScript({
|
||||
|
||||
## WASM
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
How a `.wasm` file is loaded varies greatly between packages, but most follow a basic setup: Use a JS API to load and execute the `.wasm` file.
|
||||
|
||||
For an extension, that means two things:
|
||||
|
||||
1. The `.wasm` file needs to be present in output folder so it can be loaded.
|
||||
2. You must import the JS API to load and initialize the `.wasm` file, usually provided by the NPM package.
|
||||
|
||||
For an example, let's say you have a content script needs to parse TS code into AST. We'll use [`@oxc-parser/wasm`](https://www.npmjs.com/package/@oxc-parser/wasm) to do it!
|
||||
|
||||
First, we need to copy the `.wasm` file to the output directory. We'll do it with a [WXT module](/guide/essentials/wxt-modules):
|
||||
|
||||
```ts
|
||||
// modules/oxc-parser-wasm.ts
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
export default defineWxtModule((wxt) => {
|
||||
wxt.hook('build:publicAssets', (_, assets) => {
|
||||
assets.push({
|
||||
absoluteSrc: resolve(
|
||||
'node_modules/@oxc-parser/wasm/web/oxc_parser_wasm_bg.wasm',
|
||||
),
|
||||
relativeDest: 'oxc_parser_wasm_bg.wasm',
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Run `wxt build`, and you should see the WASM file copied into your `.output/chrome-mv3` folder!
|
||||
|
||||
Next, since this is in a content script and we'll be fetching the WASM file over the network to load it, we need to add the file to the `web_accessible_resources`:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
web_accessible_resources: [
|
||||
{
|
||||
// We'll use this matches in the content script as well
|
||||
matches: ['*://*.github.com/*'],
|
||||
// Use the same path as `relativeDest` from the WXT module
|
||||
resources: ['/oxc_parser_wasm_bg.wasm'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
And finally, we need to load and initialize the `.wasm` file inside the content script to use it:
|
||||
|
||||
```ts [entrypoints/content.ts]
|
||||
import initWasm, { parseSync } from '@oxc-parser/wasm';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: '*://*.github.com/*',
|
||||
async main(ctx) {
|
||||
if (!location.pathname.endsWith('.ts')) return;
|
||||
|
||||
// Get text from GitHub
|
||||
const code = document.getElementById(
|
||||
'read-only-cursor-text-area',
|
||||
)?.textContent;
|
||||
if (!code) return;
|
||||
const sourceFilename = document.getElementById('file-name-id')?.textContent;
|
||||
if (!sourceFilename) return;
|
||||
|
||||
// Load the WASM file:
|
||||
await initWasm({
|
||||
module_or_path: browser.runtime.getURL('/oxc_parser_wasm_bg.wasm'),
|
||||
});
|
||||
|
||||
// Once loaded, we can use `parseSync`!
|
||||
const ast = parseSync(code, { sourceFilename });
|
||||
console.log(ast);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This code is taken directly from `@oxc-parser/wasm` docs with one exception: We manually pass in a file path. In a standard NodeJS or web project, the default path works just fine so you don't have to pass anything in. However, extensions are different. You should always explicitly pass in the full URL to the WASM file in your output directory, which is what `browser.runtime.getURL` returns.
|
||||
|
||||
Run your extension, and you should see OXC parse the TS file!
|
||||
|
||||
@@ -11,19 +11,7 @@ export default defineConfig({
|
||||
});
|
||||
```
|
||||
|
||||
By default, WXT automatically setups up auto-imports for all of it's own APIs:
|
||||
|
||||
- [`browser`](/api/reference/wxt/browser/variables/browser) from `wxt/browser`
|
||||
- [`defineContentScript`](/api/reference/wxt/sandbox/functions/defineContentScript) from `wxt/sandbox`
|
||||
- [`defineBackground`](/api/reference/wxt/sandbox/functions/defineBackground) from `wxt/sandbox`
|
||||
- [`defineUnlistedScript`](/api/reference/wxt/sandbox/functions/defineUnlistedScript) from `wxt/sandbox`
|
||||
- [`createIntegratedUi`](/api/reference/wxt/client/functions/createIntegratedUi) from `wxt/client`
|
||||
- [`createShadowRootUi`](/api/reference/wxt/client/functions/createShadowRootUi) from `wxt/client`
|
||||
- [`createIframeUi`](/api/reference/wxt/client/functions/createIframeUi) from `wxt/client`
|
||||
- [`fakeBrowser`](/api/reference/wxt/testing/variables/fakeBrowser) from `wxt/testing`
|
||||
- And more!
|
||||
|
||||
WXT also adds some project directories as auto-import sources automatically:
|
||||
By default, WXT automatically sets up auto-imports for all of it's own APIs and some of your project directories:
|
||||
|
||||
- `<srcDir>/components/*`
|
||||
- `<srcDir>/composables/*`
|
||||
@@ -32,6 +20,8 @@ WXT also adds some project directories as auto-import sources automatically:
|
||||
|
||||
All named and default exports from files in these directories are available everywhere else in your project without having to import them.
|
||||
|
||||
To see the complete list of auto-imported APIs, run [`wxt prepare`](/api/cli/wxt-prepare) and look at your project's `.wxt/types/imports-module.d.ts` file.
|
||||
|
||||
## TypeScript
|
||||
|
||||
For TypeScript and your editor to recognize auto-imported variables, you need to run the [`wxt prepare` command](/api/cli/wxt-prepare).
|
||||
@@ -110,3 +100,19 @@ export default defineConfig({
|
||||
imports: false, // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
## Explicit Imports (`#imports`)
|
||||
|
||||
You can manually import all of WXT's APIs via the `#imports` module:
|
||||
|
||||
```ts
|
||||
import {
|
||||
createShadowRootUi,
|
||||
ContentScriptContext,
|
||||
MatchPattern,
|
||||
} from '#imports';
|
||||
```
|
||||
|
||||
To learn more about how the `#imports` module works, read the [related blog post](/blog/2024-12-06-using-imports-module).
|
||||
|
||||
If you've disabled auto-imports, you should still use `#imports` to import all of WXT's APIs from a single place.
|
||||
|
||||
@@ -4,9 +4,9 @@ outline: deep
|
||||
|
||||
# Browser Startup
|
||||
|
||||
> See the [API Reference](/api/reference/wxt/interfaces/ExtensionRunnerConfig) for a full list of config.
|
||||
> See the [API Reference](/api/reference/wxt/interfaces/WebExtConfig) for a full list of config.
|
||||
|
||||
During development WXT uses [`web-ext` by Mozilla](https://www.npmjs.com/package/web-ext) to automatically open a browser window with your extension installed.
|
||||
During development, WXT uses [`web-ext` by Mozilla](https://www.npmjs.com/package/web-ext) to automatically open a browser window with your extension installed.
|
||||
|
||||
## Config Files
|
||||
|
||||
@@ -14,15 +14,15 @@ You can configure browser startup in 3 places:
|
||||
|
||||
1. `<rootDir>/web-ext.config.ts`: Ignored from version control, this file lets you configure your own options for a specific project without affecting other developers
|
||||
|
||||
```ts
|
||||
import { defineRunnerConfig } from 'wxt';
|
||||
```ts [web-ext.config.ts]
|
||||
import { defineWebExtConfig } from 'wxt';
|
||||
|
||||
export default defineRunnerConfig({
|
||||
export default defineWebExtConfig({
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
2. `<rootDir>/wxt.config.ts`: Via the [`runner` config](/api/reference/wxt/interfaces/InlineConfig#runner), included in version control
|
||||
2. `<rootDir>/wxt.config.ts`: Via the [`webExt` config](/api/reference/wxt/interfaces/InlineConfig#webext), included in version control
|
||||
3. `$HOME/web-ext.config.ts`: Provide default values for all WXT projects on your computer
|
||||
|
||||
## Recipes
|
||||
@@ -31,8 +31,10 @@ You can configure browser startup in 3 places:
|
||||
|
||||
To set or customize the browser opened during development:
|
||||
|
||||
```ts
|
||||
export default defineRunnerConfig({
|
||||
```ts [web-ext.config.ts]
|
||||
import { defineWebExtConfig } from 'wxt';
|
||||
|
||||
export default defineWebExtConfig({
|
||||
binaries: {
|
||||
chrome: '/path/to/chrome-beta', // Use Chrome Beta instead of regular Chrome
|
||||
firefox: 'firefoxdeveloperedition', // Use Firefox Developer Edition instead of regular Firefox
|
||||
@@ -41,28 +43,46 @@ export default defineRunnerConfig({
|
||||
});
|
||||
```
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
webExt: {
|
||||
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"
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
By default, WXT will try to automatically discover where Chrome/Firefox are installed. However, if you have chrome installed in a non-standard location, you need to set it manually as shown above.
|
||||
|
||||
### Persist Data
|
||||
|
||||
By default, to keep from modifying your browser's existing profiles, `web-ext` creates a brand new profile every time you run the `dev` script.
|
||||
|
||||
Right now, Chromium based browsers are the only browsers that support overriding this behavior and persisting data when running the `dev` script multiple times.
|
||||
|
||||
To persist data, set the `--user-data-dir` flag:
|
||||
To persist data, set the `--user-data-dir` flag in any of the config files mentioned above:
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [Mac/Linux]
|
||||
export default defineRunnerConfig({
|
||||
import { defineWebExtConfig } from 'wxt';
|
||||
|
||||
export default defineWebExtConfig({
|
||||
chromiumArgs: ['--user-data-dir=./.wxt/chrome-data'],
|
||||
});
|
||||
```
|
||||
|
||||
```ts [Windows]
|
||||
import { resolve } from 'node:path';
|
||||
import { defineWebExtConfig } from 'wxt';
|
||||
|
||||
export default defineRunnerConfig({
|
||||
export default defineWebExtConfig({
|
||||
// On Windows, the path must be absolute
|
||||
chromiumArgs: [`--user-data-dir="${resolve(".wxt/chrome-data")}"`
|
||||
chromiumProfile: resolve('.wxt/chrome-data'),
|
||||
keepProfileChanges: true,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -71,15 +91,38 @@ export default defineRunnerConfig({
|
||||
Now, next time you run the `dev` script, a persistent profile will be created in `.wxt/chrome-data/{profile-name}`. With a persistent profile, you can install devtools extensions to help with development, allow the browser to remember logins, etc, without worrying about the profile being reset the next time you run the `dev` script.
|
||||
|
||||
:::tip
|
||||
You can use any directory you'd like for `--user-data-dir`, the examples above create a persistent profile for each WXT project. To create a profile for all WXT projects, you can put the `chrome-data` directory inside you're user's home directory.
|
||||
You can use any directory you'd like for `--user-data-dir`, the examples above create a persistent profile for each WXT project. To create a profile for all WXT projects, you can put the `chrome-data` directory inside your user's home directory.
|
||||
:::
|
||||
|
||||
### Disable Opening Browser
|
||||
|
||||
If you prefer to load the extension into your browser manually, you can disable the auto-open behavior:
|
||||
|
||||
```ts
|
||||
export default defineRunnerConfig({
|
||||
```ts [web-ext.config.ts]
|
||||
import { defineWebExtConfig } from 'wxt';
|
||||
|
||||
export default defineWebExtConfig({
|
||||
disabled: true,
|
||||
});
|
||||
```
|
||||
|
||||
### Enabling Chrome Features
|
||||
|
||||
Some APIs are disabled in Chrome during development because of the default flags `web-ext` uses to launch Chrome, like the [Prompt API](https://developer.chrome.com/docs/ai/prompt-api).
|
||||
|
||||
If your extension depends on new features or services, you can enable them via `chromiumArgs`:
|
||||
|
||||
```ts
|
||||
import { defineWebExtConfig } from 'wxt';
|
||||
|
||||
export default defineWebExtConfig({
|
||||
chromiumArgs: [
|
||||
// For example, this flag enables the Prompt API
|
||||
'--disable-features=DisableLoadExtensionCommandLineSwitch',
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
There is no comprehensive list of what feature flags enable what APIs and services.
|
||||
|
||||
Alternatively, if you can't find a flag that enables a feature you're looking for, [disable the opening the browser during development](#disable-opening-browser) and use your regular chrome profile for development.
|
||||
|
||||
@@ -10,70 +10,10 @@ WXT does several pre-processing steps to try and prevent errors during this proc
|
||||
2. Use `@webext-core/fake-browser` to create a fake version of the `chrome` and `browser` globals expected by extensions.
|
||||
3. Pre-process the JS/TS code, stripping out the `main` function then tree-shaking unused code from the file
|
||||
|
||||
However, this process is not perfect. It doesn't setup all the globals found in the browser and the APIs may behave differently. As such, **_you should avoid using browser or extension APIs outside the `main` function of your entrypoints!_**
|
||||
However, this process is not perfect. It doesn't setup all the globals found in the browser and the APIs may behave differently. As such, **_you should avoid using browser or extension APIs outside the `main` function of your entrypoints!_** See [Entrypoint Limitations](/guide/essentials/extension-apis#entrypoint-limitations) for more details.
|
||||
|
||||
:::tip
|
||||
If you're running into errors while importing entrypoints, run `wxt prepare --debug` to see more details about this process. When debugging, WXT will print out the pre-processed code to help you identify issues.
|
||||
:::
|
||||
|
||||
Once the environment has been polyfilled and your code pre-processed, it's up the entrypoint loader to import your code, extracting the options from the default export.
|
||||
|
||||
There are two options for loading your entrypoints:
|
||||
|
||||
1. `vite-node` - default as of `v0.19.0`
|
||||
2. `jiti` (**DEPRECATED, will be removed in `v0.20.0`**) - Default before `v0.19.0`
|
||||
|
||||
## vite-node
|
||||
|
||||
Since 0.19.0, WXT uses `vite-node`, the same tool that powers Vitest and Nuxt, to import your entrypoint files. It re-uses the same vite config used when building your extension, making it the most stable entrypoint loader.
|
||||
|
||||
## jiti
|
||||
|
||||
To enable `jiti`:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
entrypointLoader: 'jiti',
|
||||
});
|
||||
```
|
||||
|
||||
This is the original method WXT used to import TS files. However, because it doesn't support vite plugins like `vite-node`, it does one additional pre-processing step: It removes **_ALL_** imports from your code.
|
||||
|
||||
That means you cannot use imported variables outside the `main` function in JS entrypoints, like for content script `matches` or other options:
|
||||
|
||||
```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.
|
||||
```
|
||||
|
||||
Usually, this error occurs when you try to extract options into a shared file or when running 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() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
WXT supports [dotenv files the same way as Vite](https://vite.dev/guide/env-and-mode.html#env-files). Create any of the following files:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
.env
|
||||
.env.local
|
||||
.env.[mode]
|
||||
@@ -42,6 +42,8 @@ WXT provides some custom environment variables based on the current command:
|
||||
| `import.meta.env.EDGE` | `boolean` | Equivalent to `import.meta.env.BROWSER === "edge"` |
|
||||
| `import.meta.env.OPERA` | `boolean` | Equivalent to `import.meta.env.BROWSER === "opera"` |
|
||||
|
||||
You can set the [`targetBrowsers`](/api/reference/wxt/interfaces/InlineConfig#targetbrowsers) option to make the `BROWSER` variable a more specific type, like `"chrome" | "firefox"`.
|
||||
|
||||
You can also access all of [Vite's environment variables](https://vite.dev/guide/env-and-mode.html#env-variables):
|
||||
|
||||
| Usage | Type | Description |
|
||||
@@ -56,3 +58,38 @@ Vite provides two other environment variables, but they aren't useful in WXT pro
|
||||
- `import.meta.env.BASE_URL`: Use `browser.runtime.getURL` instead.
|
||||
- `import.meta.env.SSR`: Always `false`.
|
||||
:::
|
||||
|
||||
## Manifest
|
||||
|
||||
To use environment variables in the manifest, you need to use the function syntax:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/module-vue'],
|
||||
manifest: { // [!code --]
|
||||
oauth2: { // [!code --]
|
||||
client_id: import.meta.env.WXT_APP_CLIENT_ID // [!code --]
|
||||
} // [!code --]
|
||||
} // [!code --]
|
||||
manifest: () => ({ // [!code ++]
|
||||
oauth2: { // [!code ++]
|
||||
client_id: import.meta.env.WXT_APP_CLIENT_ID // [!code ++]
|
||||
} // [!code ++]
|
||||
}), // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
WXT can't load your `.env` files until after the config file has been loaded. So by using the function syntax for `manifest`, it defers creating the object until after the `.env` files are loaded into the process.
|
||||
|
||||
Note that Vite's runtime environment variables, like `import.meta.env.DEV`, will not be defined. Instead, access the `mode` like this:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: ({ mode }) => {
|
||||
const isDev = mode === 'development';
|
||||
console.log('Is development mode:', isDev);
|
||||
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -6,13 +6,12 @@ WXT includes a system that lets you hook into the build process and make changes
|
||||
|
||||
The easiest way to add a hook is via the `wxt.config.ts`. Here's an example hook that modifies the `manifest.json` file before it is written to the output directory:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
hooks: {
|
||||
'build:manifestGenerated': (wxt, manifest) => {
|
||||
if (wxt.config.mode === 'development') {
|
||||
manifest.title += ' (DEV)';
|
||||
manifest.name += ' (DEV)';
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -21,6 +20,8 @@ export default defineConfig({
|
||||
|
||||
Most hooks provide the `wxt` object as the first argument. It contains the resolved config and other info about the current build. The other arguments can be modified by reference to change different parts of the build system.
|
||||
|
||||
You can find the [list of all available hooks](/api/reference/wxt/interfaces/WxtHooks) in the API reference.
|
||||
|
||||
Putting one-off hooks like this in your config file is simple, but if you find yourself writing lots of hooks, you should extract them into [WXT Modules](/guide/essentials/wxt-modules) instead.
|
||||
|
||||
## Execution Order
|
||||
@@ -33,7 +34,7 @@ Because hooks can be defined in multiple places, including [WXT Modules](/guide/
|
||||
|
||||
To see the order for your project, run `wxt prepare --debug` flag and search for the "Hook execution order":
|
||||
|
||||
```
|
||||
```plaintext
|
||||
⚙ Hook execution order:
|
||||
⚙ 1. wxt:built-in:unimport
|
||||
⚙ 2. src/modules/auto-icons.ts
|
||||
@@ -51,7 +52,9 @@ Changing execution order is simple:
|
||||
📄 0.my-module.ts
|
||||
📄 1.another-module.ts
|
||||
```
|
||||
|
||||
- If you need to run an NPM module after user modules, just make it a user module and prefix the filename with a number!
|
||||
|
||||
```ts
|
||||
// modules/2.i18n.ts
|
||||
export { default } from '@wxt-dev/i18n/module';
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
# Manifest
|
||||
|
||||
In WXT, there is no `manifest.json` file in your source code. Instead, WXT generates it during the build process based off files in your project.
|
||||
In WXT, there is no `manifest.json` file in your source code. Instead, WXT generates the manifest from multiple sources:
|
||||
|
||||
## Manifest Config
|
||||
- Global options [defined in your `wxt.config.ts` file](#global-options)
|
||||
- Entrypoint-specific options [defined in your entrypoints](/guide/essentials/entrypoints#defining-manifest-options)
|
||||
- [WXT Modules](/guide/essentials/wxt-modules) added to your project can modify your manifest
|
||||
- [Hooks](/guide/essentials/config/hooks) defined in your project can modify your manifest
|
||||
|
||||
To manually add a property to the `manifest.json` output during builds, use the `manifest` config inside `wxt.config.ts`:
|
||||
Your extension's `manifest.json` will be output to `.output/{target}/manifest.json` when running `wxt build`.
|
||||
|
||||
## Global Options
|
||||
|
||||
To add a property to your manifest, use the `manifest` config inside your `wxt.config.ts`:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
@@ -121,7 +128,7 @@ If a version is not present in your `package.json`, it defaults to `"0.0.0"`.
|
||||
|
||||
WXT automatically discovers your extension's icon by looking at files in the `public/` directory:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
public/
|
||||
├─ icon-16.png
|
||||
├─ icon-24.png
|
||||
@@ -130,7 +137,7 @@ public/
|
||||
└─ icon-128.png
|
||||
```
|
||||
|
||||
Specifically, if an icon must match one of these regex to be discovered:
|
||||
Specifically, an icon must match one of these regex to be discovered:
|
||||
|
||||
<<< @/../packages/wxt/src/core/utils/manifest.ts#snippet
|
||||
|
||||
@@ -156,6 +163,11 @@ Alternatively, you can use [`@wxt-dev/auto-icons`](https://www.npmjs.com/package
|
||||
|
||||
> [Chrome docs](https://developer.chrome.com/docs/extensions/reference/permissions/)
|
||||
|
||||
Most of the time, you need to manually add permissions to your manifest. Only in a few specific situations are permissions added automatically:
|
||||
|
||||
- During development: the `tabs` and `scripting` permissions will be added to enable hot reloading.
|
||||
- When a `sidepanel` entrypoint is present: The `sidepanel` permission is added.
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
@@ -171,7 +183,7 @@ export default defineConfig({
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ['storage', 'tabs'],
|
||||
host_permissions: ['https://www.google.com/*'],
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -211,22 +223,7 @@ By default, whenever an `action` is generated, WXT falls back to `browser_action
|
||||
|
||||
### Action With Popup
|
||||
|
||||
To generate a manifest where a UI appears after clicking the icon, just create a [Popup entrypoint](/guide/essentials/entrypoints#popup).
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
hooks: {
|
||||
build: {
|
||||
manifestGenerated(manifest) {
|
||||
// Update the manifest variable by reference
|
||||
manifest.name = 'Overriden name';
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
If you want to use a `page_action` for MV2, add the following meta tag to the HTML document's head:
|
||||
To generate a manifest where a UI appears after clicking the icon, just create a [Popup entrypoint](/guide/essentials/entrypoints#popup). If you want to use a `page_action` for MV2, add the following meta tag to the HTML document's head:
|
||||
|
||||
```html
|
||||
<meta name="manifest.type" content="page_action" />
|
||||
@@ -238,6 +235,7 @@ If you want to use the `activeTab` permission or the `browser.action.onClicked`
|
||||
|
||||
1. Delete the [Popup entrypoint](/guide/essentials/entrypoints#popup) if it exists
|
||||
2. Add the `action` key to your manifest:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
Define runtime configuration in a single place, `<srcDir>/app.config.ts`:
|
||||
|
||||
```ts
|
||||
import { defineAppConfig } from 'wxt/sandbox';
|
||||
import { defineAppConfig } from '#imports';
|
||||
|
||||
// Define types for your config
|
||||
declare module 'wxt/sandbox' {
|
||||
declare module 'wxt/utils/define-app-config' {
|
||||
export interface WxtAppConfig {
|
||||
theme?: 'light' | 'dark';
|
||||
}
|
||||
@@ -20,13 +20,13 @@ export default defineAppConfig({
|
||||
```
|
||||
|
||||
:::warning
|
||||
This file is committed to the repo, so don't put any secrets here. Instead, use [Environment Variables](#environment-variables)
|
||||
This file is committed to the repo, so don't put any secrets here. Instead, use [Environment Variables](/guide/essentials/config/environment-variables)
|
||||
:::
|
||||
|
||||
To access runtime config, WXT provides the `useAppConfig` function:
|
||||
|
||||
```ts
|
||||
import { useAppConfig } from 'wxt/sandbox';
|
||||
import { useAppConfig } from '#imports';
|
||||
|
||||
console.log(useAppConfig()); // { theme: "dark" }
|
||||
```
|
||||
@@ -36,7 +36,7 @@ console.log(useAppConfig()); // { theme: "dark" }
|
||||
You can use environment variables in the `app.config.ts` file.
|
||||
|
||||
```ts
|
||||
declare module 'wxt/sandbox' {
|
||||
declare module 'wxt/utils/define-app-config' {
|
||||
export interface WxtAppConfig {
|
||||
apiKey?: string;
|
||||
skipWelcome: boolean;
|
||||
|
||||
@@ -14,7 +14,7 @@ At a minimum, you need to create a TSConfig in your root directory that looks li
|
||||
Or if you're in a monorepo, you may not want to extend the config. If you don't extend it, you need to add `.wxt/wxt.d.ts` to the TypeScript project:
|
||||
|
||||
```ts
|
||||
/// <reference types="./.wxt/wxt.d.ts" />
|
||||
/// <reference path="./.wxt/wxt.d.ts" />
|
||||
```
|
||||
|
||||
## Compiler Options
|
||||
|
||||
@@ -12,8 +12,7 @@ In most cases, you shouldn't change Vite's build settings. WXT provides sensible
|
||||
|
||||
You can change Vite's config via the `wxt.config.ts` file:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
@@ -28,8 +27,7 @@ export default defineConfig({
|
||||
|
||||
To add a plugin, install the NPM package and add it to the Vite config:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
import { defineConfig } from 'wxt';
|
||||
import VueRouter from 'unplugin-vue-router/vite';
|
||||
|
||||
@@ -47,8 +45,7 @@ export default defineConfig({
|
||||
:::warning
|
||||
Due to the way WXT orchestrates Vite builds, some plugins may not work as expected. For example, `vite-plugin-remove-console` normally only runs when you build for production (`vite build`). However, WXT uses a combination of dev server and builds during development, so you need to manually tell it when to run:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
import { defineConfig } from 'wxt';
|
||||
import removeConsole from 'vite-plugin-remove-console';
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@ outline: deep
|
||||
|
||||
# Content Scripts
|
||||
|
||||
> To create a content script, see [Entrypoint Types](/guide/essentials/entrypoints#content-scripts).
|
||||
|
||||
## Context
|
||||
|
||||
The first argument to a content script's `main` function is it's "context".
|
||||
The first argument to a content script's `main` function is its "context".
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
// entrypoints/example.content.ts
|
||||
export default defineContentScript({
|
||||
main(ctx) {},
|
||||
});
|
||||
@@ -17,7 +19,7 @@ export default defineContentScript({
|
||||
|
||||
This object is responsible for tracking whether or not the content script's context is "invalidated". Most browsers, by default, do not stop content scripts if the extension is uninstalled, updated, or disabled. When this happens, content scripts start reporting this error:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
Error: Extension context invalidated.
|
||||
```
|
||||
|
||||
@@ -62,7 +64,7 @@ In regular web extensions, CSS for content scripts is usually a separate CSS fil
|
||||
In WXT, to add CSS to a content script, simply import the CSS file into your JS entrypoint, and WXT will automatically add the bundled CSS output to the `css` array.
|
||||
|
||||
```ts
|
||||
// entrypoints/content/index.ts
|
||||
// entrypoints/example.content/index.ts
|
||||
import './style.css';
|
||||
|
||||
export default defineContentScript({
|
||||
@@ -74,20 +76,20 @@ To create a standalone content script that only includes a CSS file:
|
||||
|
||||
1. Create the CSS file: `entrypoints/example.content.css`
|
||||
2. Use the `build:manifestGenerated` hook to add the content script to the manifest:
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
hooks: {
|
||||
"build:manifestGenerated": (wxt, manifest) => {
|
||||
'build:manifestGenerated': (wxt, manifest) => {
|
||||
manifest.content_scripts ??= [];
|
||||
manifest.content_scripts.push({
|
||||
// Build extension once to see where your CSS get's written to
|
||||
css: ["content-scripts/example.css"],
|
||||
matches: ["*://*/*"]
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
css: ['content-scripts/example.css'],
|
||||
matches: ['*://*/*'],
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## UI
|
||||
@@ -198,6 +200,7 @@ export default defineContentScript({
|
||||
```ts [Svelte]
|
||||
// entrypoints/example-ui.content/index.ts
|
||||
import App from './App.svelte';
|
||||
import { mount, unmount } from 'svelte';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
@@ -208,14 +211,11 @@ export default defineContentScript({
|
||||
anchor: 'body',
|
||||
onMount: (container) => {
|
||||
// Create the Svelte app inside the UI container
|
||||
const app = new App({
|
||||
target: container,
|
||||
});
|
||||
return app;
|
||||
return mount(App, { target: container });
|
||||
},
|
||||
onRemove: (app) => {
|
||||
// Destroy the app when the UI is removed
|
||||
app.$destroy();
|
||||
unmount(app);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -239,6 +239,7 @@ export default defineContentScript({
|
||||
onMount: (container) => {
|
||||
// Render your app to the UI container
|
||||
const unmount = render(() => <div>...</div>, container);
|
||||
return unmount;
|
||||
},
|
||||
onRemove: (unmount) => {
|
||||
// Unmount the app when the UI is removed
|
||||
@@ -254,13 +255,13 @@ export default defineContentScript({
|
||||
|
||||
:::
|
||||
|
||||
See the [API Reference](/api/reference/wxt/client/functions/createIntegratedUi) for the complete list of options.
|
||||
See the [API Reference](/api/reference/wxt/utils/content-script-ui/integrated/functions/createIntegratedUi) for the complete list of options.
|
||||
|
||||
### 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/reference/wxt/client/functions/createShadowRootUi) abstracts all the `ShadowRoot` setup away, making it easy to create UIs whose styles are isolated from the page. It also supports an optional `isolateEvents` parameter to further isolate user interactions.
|
||||
WXT's [`createShadowRootUi`](/api/reference/wxt/utils/content-script-ui/shadow-root/functions/createShadowRootUi) abstracts all the `ShadowRoot` setup away, making it easy to create UIs whose styles are isolated from the page. It also supports an optional `isolateEvents` parameter to further isolate user interactions.
|
||||
|
||||
To use `createShadowRootUi`, follow these steps:
|
||||
|
||||
@@ -378,6 +379,7 @@ export default defineContentScript({
|
||||
// 1. Import the style
|
||||
import './style.css';
|
||||
import App from './App.svelte';
|
||||
import { mount, unmount } from 'svelte';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
@@ -392,14 +394,11 @@ export default defineContentScript({
|
||||
anchor: 'body',
|
||||
onMount: (container) => {
|
||||
// Create the Svelte app inside the UI container
|
||||
const app = new App({
|
||||
target: container,
|
||||
});
|
||||
return app;
|
||||
return mount(App, { target: container });
|
||||
},
|
||||
onRemove: (app) => {
|
||||
// Destroy the app when the UI is removed
|
||||
app?.$destroy();
|
||||
unmount(app);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -443,7 +442,7 @@ export default defineContentScript({
|
||||
|
||||
:::
|
||||
|
||||
See the [API Reference](/api/reference/wxt/client/functions/createShadowRootUi) for the complete list of options.
|
||||
See the [API Reference](/api/reference/wxt/utils/content-script-ui/shadow-root/functions/createShadowRootUi) for the complete list of options.
|
||||
|
||||
Full examples:
|
||||
|
||||
@@ -454,9 +453,10 @@ Full examples:
|
||||
|
||||
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/reference/wxt/client/functions/createIframeUi), which simplifies setting up the IFrame.
|
||||
WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/utils/content-script-ui/iframe/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>
|
||||
@@ -471,9 +471,10 @@ WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/client/fun
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
1. Add the page to the manifest's `web_accessible_resources`:
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
web_accessible_resources: [
|
||||
@@ -485,6 +486,7 @@ WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/client/fun
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
1. Create and mount the IFrame:
|
||||
|
||||
```ts
|
||||
@@ -509,7 +511,7 @@ WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/client/fun
|
||||
});
|
||||
```
|
||||
|
||||
See the [API Reference](/api/reference/wxt/client/functions/createIframeUi) for the complete list of options.
|
||||
See the [API Reference](/api/reference/wxt/utils/content-script-ui/iframe/functions/createIframeUi) for the complete list of options.
|
||||
|
||||
## Isolated World vs Main World
|
||||
|
||||
@@ -547,16 +549,35 @@ To use `injectScript`, we need two entrypoints, one content script and one unlis
|
||||
```ts
|
||||
// entrypoints/example-main-world.ts
|
||||
export default defineUnlistedScript(() => {
|
||||
console.log('Hello from the main world!');
|
||||
console.log('Hello from the main world');
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
// entrypoints/example.content.ts
|
||||
export default defineContentScript(async () => {
|
||||
await injectScript('/example-main-world.js', {
|
||||
keepInDom: true,
|
||||
});
|
||||
export default defineContentScript({
|
||||
matches: ['*://*/*'],
|
||||
async main() {
|
||||
console.log('Injecting script...');
|
||||
await injectScript('/example-main-world.js', {
|
||||
keepInDom: true,
|
||||
});
|
||||
console.log('Done!');
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```json
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
// ...
|
||||
web_accessible_resources: [
|
||||
{
|
||||
resources: ["example-main-world.js"],
|
||||
matches: ["*://*/*"],
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@@ -570,6 +591,109 @@ For MV3, `injectScript` is synchronous and the injected script will be evaluated
|
||||
However for MV2, `injectScript` has to `fetch` the script's text content and create an inline `<script>` block. This means for MV2, your script is injected asynchronously and it will not be evaluated at the same time as your content script's `run_at`.
|
||||
:::
|
||||
|
||||
The `script` element can be modified just before it is added to the DOM by using the `modifyScript` option. This can be used to e.g. modify `script.async`/`script.defer`, add event listeners to the element, or pass data to the script via `script.dataset`. An example:
|
||||
|
||||
```ts
|
||||
// entrypoints/example.content.ts
|
||||
export default defineContentScript({
|
||||
matches: ['*://*/*'],
|
||||
async main() {
|
||||
await injectScript('/example-main-world.js', {
|
||||
modifyScript(script) {
|
||||
script.dataset['greeting'] = 'Hello there';
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
// entrypoints/example-main-world.ts
|
||||
export default defineUnlistedScript(() => {
|
||||
console.log(document.currentScript?.dataset['greeting']);
|
||||
});
|
||||
```
|
||||
|
||||
`injectScript` returns the created script element. It can be used to e.g. send messages to the script in the form of custom events. The script can add an event listener for them via `document.currentScript`. An example of bidirectional communication:
|
||||
|
||||
```ts
|
||||
// entrypoints/example.content.ts
|
||||
export default defineContentScript({
|
||||
matches: ['*://*/*'],
|
||||
async main() {
|
||||
const { script } = await injectScript('/example-main-world.js', {
|
||||
modifyScript(script) {
|
||||
// Add a listener before the injected script is loaded.
|
||||
script.addEventListener('from-injected-script', (event) => {
|
||||
if (event instanceof CustomEvent) {
|
||||
console.log(`${event.type}:`, event.detail);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Send an event after the injected script is loaded.
|
||||
script.dispatchEvent(
|
||||
new CustomEvent('from-content-script', {
|
||||
detail: 'General Kenobi',
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
// entrypoints/example-main-world.ts
|
||||
export default defineUnlistedScript(() => {
|
||||
const script = document.currentScript;
|
||||
|
||||
script?.addEventListener('from-content-script', (event) => {
|
||||
if (event instanceof CustomEvent) {
|
||||
console.log(`${event.type}:`, event.detail);
|
||||
}
|
||||
});
|
||||
|
||||
script?.dispatchEvent(
|
||||
new CustomEvent('from-injected-script', {
|
||||
detail: 'Hello there',
|
||||
}),
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
## Mounting UI to dynamic element
|
||||
|
||||
In many cases, you may need to mount a UI to a DOM element that does not exist at the time the web page is initially loaded. To handle this, use the `autoMount` API to automatically mount the UI when the target element appears dynamically and unmount it when the element disappears. In WXT, the `anchor` option is used to target the element, enabling automatic mounting and unmounting based on its appearance and removal.
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
|
||||
main(ctx) {
|
||||
const ui = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
// It observes the anchor
|
||||
anchor: '#your-target-dynamic-element',
|
||||
onMount: (container) => {
|
||||
// Append children to the container
|
||||
const app = document.createElement('p');
|
||||
app.textContent = '...';
|
||||
container.append(app);
|
||||
},
|
||||
});
|
||||
|
||||
// Call autoMount to observe anchor element for add/remove.
|
||||
ui.autoMount();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::tip
|
||||
When the `ui.remove` is called, `autoMount` also stops.
|
||||
:::
|
||||
|
||||
See the [API Reference](/api/reference/wxt/utils/content-script-ui/types/interfaces/ContentScriptUi#automount) for the complete list of options.
|
||||
|
||||
## Dealing with SPAs
|
||||
|
||||
It is difficult to write content scripts for SPAs (single page applications) and websites using HTML5 history mode for navigation because content scripts are only ran on full page reloads. SPAs and websites that take advantage of HTML5 history mode **_do not perform a full reload when changing paths_**, and thus your content script isn't going to be ran when you expect it to be.
|
||||
@@ -602,7 +726,7 @@ export default defineContentScript({
|
||||
matches: ['*://*.youtube.com/*'],
|
||||
main(ctx) {
|
||||
ctx.addEventListener(window, 'wxt:locationchange', ({ newUrl }) => {
|
||||
if (watchPattern.matches(newUrl)) mainWatch(ctx);
|
||||
if (watchPattern.includes(newUrl)) mainWatch(ctx);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,50 +4,30 @@ outline: deep
|
||||
|
||||
# Entrypoints
|
||||
|
||||
WXT uses the files inside the `entrypoints/` directory as inputs when bundling your extension. They can be HTML, JS, CSS, or any variant of those file types supported by Vite (Pug, TS, JSX, SCSS, etc).
|
||||
WXT uses the files inside the `entrypoints/` directory as inputs when bundling your extension. They can be HTML, JS, CSS, or any variant of those file types supported by Vite (TS, JSX, SCSS, etc).
|
||||
|
||||
Here's an example set of entrypoints:
|
||||
## Folder Structure
|
||||
|
||||
Inside the `entrypoints/` directory, an entrypoint is defined as a single file or directory (with an `index` file) inside it.
|
||||
|
||||
:::code-group
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
```html [Single File]
|
||||
📂 entrypoints/
|
||||
📂 popup/
|
||||
📄 index.html
|
||||
📄 main.ts
|
||||
📄 style.css
|
||||
📄 background.ts
|
||||
📄 content.ts
|
||||
📄 {name}.{ext}
|
||||
```
|
||||
|
||||
[[toc]]
|
||||
<!-- prettier-ignore -->
|
||||
```html [Directory]
|
||||
📂 entrypoints/
|
||||
📂 {name}/
|
||||
📄 index.{ext}
|
||||
```
|
||||
|
||||
## Listed vs Unlisted
|
||||
|
||||
For web extensions, there are two types of entrypoints:
|
||||
|
||||
- **Listed**: Referenced in the `manifest.json`
|
||||
- **Unlisted**: Not referenced in the `manifest.json`
|
||||
|
||||
Throughout the rest of WXT's documentation, listed files are referred to by name. For example:
|
||||
|
||||
- Popup
|
||||
- Options
|
||||
- Background
|
||||
- Content Scripts
|
||||
- Etc.
|
||||
|
||||
Some examples of "unlisted" entrypoints:
|
||||
|
||||
- A welcome page shown when the extension is installed
|
||||
- JS files injected by content scripts into the page's main world
|
||||
|
||||
:::tip
|
||||
Regardless of whether a entrypoint is listed or unlisted, it will still be bundled into your extension and be available at runtime.
|
||||
:::
|
||||
|
||||
## Adding Entrypoints
|
||||
|
||||
An entrypoint can be defined as a single file or directory with an `index` file inside it.
|
||||
The entrypoint's `name` dictates the type of entrypoint. For example, to add a ["Background" entrypoint](#background), either of these files would work:
|
||||
|
||||
:::code-group
|
||||
|
||||
@@ -66,18 +46,103 @@ An entrypoint can be defined as a single file or directory with an `index` file
|
||||
|
||||
:::
|
||||
|
||||
The entrypoint's name dictates the type of entrypoint, listed vs unlisted. In this example, "background" is the name of the ["Background" entrypoint](#background).
|
||||
|
||||
Refer to the [Entrypoint Types](#entrypoint-types) section for the full list of listed entrypoints and their filename patterns.
|
||||
|
||||
### Including Other Files
|
||||
|
||||
When using an entrypoint directory, `entrypoints/{name}/index.{ext}`, you can add related files next to the `index` file.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
📂 entrypoints/
|
||||
📂 popup/
|
||||
📄 index.html ← This file is the entrypoint
|
||||
📄 main.ts
|
||||
📄 style.css
|
||||
📂 background/
|
||||
📄 index.ts ← This file is the entrypoint
|
||||
📄 alarms.ts
|
||||
📄 messaging.ts
|
||||
📂 youtube.content/
|
||||
📄 index.ts ← This file is the entrypoint
|
||||
📄 style.css
|
||||
```
|
||||
|
||||
:::danger
|
||||
**DO NOT** put files related to an entrypoint directly inside the `entrypoints/` directory. WXT will treat them as entrypoints and try to build them, usually resulting in an error.
|
||||
|
||||
Instead, use a directory for that entrypoint:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
📂 entrypoints/
|
||||
📄 popup.html <!-- [!code --] -->
|
||||
📄 popup.ts <!-- [!code --] -->
|
||||
📄 popup.css <!-- [!code --] -->
|
||||
📂 popup/ <!-- [!code ++] -->
|
||||
📄 index.html <!-- [!code ++] -->
|
||||
📄 main.ts <!-- [!code ++] -->
|
||||
📄 style.css <!-- [!code ++] -->
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Deeply Nested Entrypoints
|
||||
|
||||
While the `entrypoints/` directory might resemble the `pages/` directory of other web frameworks, like Nuxt or Next.js, **it does not support deeply nesting entrypoints** in the same way.
|
||||
|
||||
Entrypoints must be zero or one levels deep for WXT to discover and build them:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
📂 entrypoints/
|
||||
📂 youtube/ <!-- [!code --] -->
|
||||
📂 content/ <!-- [!code --] -->
|
||||
📄 index.ts <!-- [!code --] -->
|
||||
📄 ... <!-- [!code --] -->
|
||||
📂 injected/ <!-- [!code --] -->
|
||||
📄 index.ts <!-- [!code --] -->
|
||||
📄 ... <!-- [!code --] -->
|
||||
📂 youtube.content/ <!-- [!code ++] -->
|
||||
📄 index.ts <!-- [!code ++] -->
|
||||
📄 ... <!-- [!code ++] -->
|
||||
📂 youtube-injected/ <!-- [!code ++] -->
|
||||
📄 index.ts <!-- [!code ++] -->
|
||||
📄 ... <!-- [!code ++] -->
|
||||
```
|
||||
|
||||
## Unlisted Entrypoints
|
||||
|
||||
In web extensions, there are two types of entrypoints:
|
||||
|
||||
1. **Listed**: Referenced in the `manifest.json`
|
||||
2. **Unlisted**: Not referenced in the `manifest.json`
|
||||
|
||||
Throughout the rest of WXT's documentation, listed entrypoints are referred to by name. For example:
|
||||
|
||||
- Popup
|
||||
- Options
|
||||
- Background
|
||||
- Content Script
|
||||
|
||||
However, not all entrypoints in web extensions are listed in the manifest. Some are not listed in the manifest, but are still used by extensions. For example:
|
||||
|
||||
- A welcome page shown in a new tab when the extension is installed
|
||||
- JS files injected by content scripts into the main world
|
||||
|
||||
For more details on how to add unlisted entrypoints, see:
|
||||
|
||||
- [Unlisted Pages](#unlisted-pages)
|
||||
- [Unlisted Scripts](#unlisted-scripts)
|
||||
- [Unlisted CSS](#unlisted-css)
|
||||
|
||||
## Defining Manifest Options
|
||||
|
||||
Most listed entrypoints have options that need to be added to the `manifest.json`. With WXT however, instead of defining the options in a separate file, _you define these options inside the entrypoint file itself_.
|
||||
Most listed entrypoints have options that need to be added to the `manifest.json`. However with WXT, instead of defining the options in a separate file, _you define these options inside the entrypoint file itself_.
|
||||
|
||||
For example, here's how to define `matches` for content scripts:
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
```ts [entrypoints/content.ts]
|
||||
export default defineContentScript({
|
||||
matches: ['*://*.wxt.dev/*'],
|
||||
main() {
|
||||
@@ -86,6 +151,17 @@ export default defineContentScript({
|
||||
});
|
||||
```
|
||||
|
||||
For HTML entrypoints, options are configured as `<meta>` tags. For example, to use a `page_action` for your MV2 popup:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="manifest.type" content="page_action" />
|
||||
</head>
|
||||
</html>
|
||||
```
|
||||
|
||||
> Refer to the [Entrypoint Types](#entrypoint-types) sections for a list of options configurable inside each entrypoint, and how to define them.
|
||||
|
||||
When building your extension, WXT will look at the options defined in your entrypoints, and generate the manifest accordingly.
|
||||
@@ -96,8 +172,6 @@ When building your extension, WXT will look at the options defined in your entry
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/background/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/background)
|
||||
|
||||
For MV2, the background is added as a script to the background page. For MV3, the background becomes a service worker.
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['background.[jt]s', 'background.js'],
|
||||
@@ -131,6 +205,25 @@ export default defineBackground({
|
||||
|
||||
:::
|
||||
|
||||
For MV2, the background is added as a script to the background page. For MV3, the background becomes a service worker.
|
||||
|
||||
When defining your background entrypoint, keep in mind that WXT will import this file in a NodeJS environment during the build process. That means you cannot place any runtime code outside the `main` function.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
browser.action.onClicked.addListener(() => { // [!code --]
|
||||
// ... // [!code --]
|
||||
}); // [!code --]
|
||||
|
||||
export default defineBackground(() => {
|
||||
browser.action.onClicked.addListener(() => { // [!code ++]
|
||||
// ... // [!code ++]
|
||||
}); // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
Refer to the [Entrypoint Loaders](/guide/essentials/config/entrypoint-loaders) documentation for more details.
|
||||
|
||||
### Bookmarks
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
|
||||
@@ -159,18 +252,18 @@ export default defineBackground({
|
||||
</html>
|
||||
```
|
||||
|
||||
When you define a Bookmarks entrypoint, WXT will automatically update the manifest to override the browser's bookmarks page with your own HTML page.
|
||||
|
||||
### 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)
|
||||
|
||||
See [Content Script UI](/guide/essentials/content-scripts) for more info on creating UIs and including CSS in content scripts.
|
||||
|
||||
<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'],
|
||||
['{name}.content.[jt]sx?', 'content-scripts/{name}.js'],
|
||||
['{name}.content/index.[jt]sx?', 'content-scripts/{name}.js'],
|
||||
]"
|
||||
/>
|
||||
|
||||
@@ -203,12 +296,29 @@ export default defineContentScript({
|
||||
});
|
||||
```
|
||||
|
||||
When defining content script entrypoints, keep in mind that WXT will import this file in a NodeJS environment during the build process. That means you cannot place any runtime code outside the `main` function.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
const container = document.createElement('div'); // [!code --]
|
||||
document.body.append(container); // [!code --]
|
||||
|
||||
export default defineContentScript({
|
||||
main: function () {
|
||||
const container = document.createElement('div'); // [!code ++]
|
||||
document.body.append(container); // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Refer to the [Entrypoint Loaders](/guide/essentials/config/entrypoint-loaders) documentation for more details.
|
||||
|
||||
See [Content Script UI](/guide/essentials/content-scripts) for more info on creating UIs and including CSS in content scripts.
|
||||
|
||||
### 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)
|
||||
|
||||
Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/examples/devtools-extension#readme) to add different panels and panes.
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['devtools.html', 'devtools.html'],
|
||||
@@ -232,6 +342,8 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
|
||||
</html>
|
||||
```
|
||||
|
||||
Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/examples/devtools-extension#readme) to add different panels and panes.
|
||||
|
||||
### History
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
|
||||
@@ -260,6 +372,8 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
|
||||
</html>
|
||||
```
|
||||
|
||||
When you define a History entrypoint, WXT will automatically update the manifest to override the browser's history page with your own HTML page.
|
||||
|
||||
### Newtab
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
|
||||
@@ -288,6 +402,8 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
|
||||
</html>
|
||||
```
|
||||
|
||||
When you define a Newtab entrypoint, WXT will automatically update the manifest to override the browser's new tab page with your own HTML page.
|
||||
|
||||
### Options
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/options/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/options_ui)
|
||||
@@ -304,14 +420,14 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewportYou 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."
|
||||
content="width=device-width, initial-scale=1.0"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Options Title</title>
|
||||
|
||||
<!-- Customize the manifest options -->
|
||||
<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', ...]" />
|
||||
@@ -339,7 +455,11 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- Set the `action.default_title` in the manifest -->
|
||||
<title>Default Popup Title</title>
|
||||
|
||||
<!-- Customize the manifest options -->
|
||||
<meta
|
||||
name="manifest.default_icon"
|
||||
content="{
|
||||
@@ -350,6 +470,20 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
|
||||
/>
|
||||
<meta name="manifest.type" content="page_action|browser_action" />
|
||||
<meta name="manifest.browser_style" content="true|false" />
|
||||
<!-- Firefox only: where to place the action button -->
|
||||
<meta
|
||||
name="manifest.default_area"
|
||||
content="navbar|menupanel|tabstrip|personaltoolbar"
|
||||
/>
|
||||
<!-- Firefox only: icons for light/dark themes -->
|
||||
<meta
|
||||
name="manifest.theme_icons"
|
||||
content="[
|
||||
{ light: '/icon-light-16.png', dark: '/icon-dark-16.png', size: 16 },
|
||||
{ light: '/icon-light-32.png', dark: '/icon-dark-32.png', size: 32 }
|
||||
]"
|
||||
/>
|
||||
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
@@ -372,8 +506,8 @@ Firefox does not support sandboxed pages.
|
||||
:patterns="[
|
||||
['sandbox.html', 'sandbox.html'],
|
||||
['sandbox/index.html', 'sandbox.html'],
|
||||
['<name>.sandbox.html', '<name>.html'],
|
||||
['<name>.sandbox/index.html', '<name>.html'],
|
||||
['{name}.sandbox.html', '{name}.html'],
|
||||
['{name}.sandbox/index.html', '{name}.html'],
|
||||
]"
|
||||
/>
|
||||
|
||||
@@ -384,6 +518,7 @@ Firefox does not support sandboxed pages.
|
||||
<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', ...]" />
|
||||
@@ -398,14 +533,12 @@ Firefox does not support sandboxed pages.
|
||||
|
||||
[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.
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['sidepanel.html', 'sidepanel.html'],
|
||||
['sidepanel/index.html', 'sidepanel.html'],
|
||||
['<name>.sidepanel.html', '<name>.html` '],
|
||||
['<name>.sidepanel/index.html', '<name>.html` '],
|
||||
['{name}.sidepanel.html', '{name}.html` '],
|
||||
['{name}.sidepanel/index.html', '{name}.html` '],
|
||||
]"
|
||||
/>
|
||||
|
||||
@@ -416,6 +549,8 @@ In Chrome, side panels use the `side_panel` API, while Firefox uses the `sidebar
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Default Side Panel Title</title>
|
||||
|
||||
<!-- Customize the manifest options -->
|
||||
<meta
|
||||
name="manifest.default_icon"
|
||||
content="{
|
||||
@@ -426,6 +561,7 @@ In Chrome, side panels use the `side_panel` API, while Firefox uses the `sidebar
|
||||
/>
|
||||
<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', ...]" />
|
||||
@@ -436,20 +572,18 @@ In Chrome, side panels use the `side_panel` API, while Firefox uses the `sidebar
|
||||
</html>
|
||||
```
|
||||
|
||||
In Chrome, side panels use the `side_panel` API, while Firefox uses the `sidebar_action` API.
|
||||
|
||||
### Unlisted CSS
|
||||
|
||||
Follow Vite's guide to setup your preprocessor of choice: https://vitejs.dev/guide/features.html#css-pre-processors
|
||||
|
||||
CSS entrypoints are always unlisted. To add CSS to a content script, see the [Content Script](/guide/essentials/content-scripts#css) docs.
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['<name>.(css|scss|sass|less|styl|stylus)', '<name>.css'],
|
||||
['<name>/index.(css|scss|sass|less|styl|stylus)', '<name>.css'],
|
||||
['{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'],
|
||||
['{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'],
|
||||
]"
|
||||
/>
|
||||
|
||||
@@ -459,12 +593,16 @@ body {
|
||||
}
|
||||
```
|
||||
|
||||
Follow Vite's guide to setup your preprocessor of choice: <https://vitejs.dev/guide/features.html#css-pre-processors>
|
||||
|
||||
CSS entrypoints are always unlisted. To add CSS to a content script, see the [Content Script](/guide/essentials/content-scripts#css) docs.
|
||||
|
||||
### Unlisted Pages
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['<name>.html', '<name>.html'],
|
||||
['<name>/index.html', '<name>.html'],
|
||||
['{name}.html', '{name}.html'],
|
||||
['{name}/index.html', '{name}.html'],
|
||||
]"
|
||||
/>
|
||||
|
||||
@@ -475,6 +613,7 @@ body {
|
||||
<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', ...]" />
|
||||
@@ -485,20 +624,21 @@ body {
|
||||
</html>
|
||||
```
|
||||
|
||||
Pages are accessible at `/<name>.html`:
|
||||
At runtime, unlisted pages are accessible at `/{name}.html`:
|
||||
|
||||
```ts
|
||||
const url = browser.runtime.getURL('/<name>.html');
|
||||
const url = browser.runtime.getURL('/{name}.html');
|
||||
|
||||
console.log(url); // "chrome-extension://<id>/<name>.html"
|
||||
console.log(url); // "chrome-extension://{id}/{name}.html"
|
||||
window.open(url); // Open the page in a new tab
|
||||
```
|
||||
|
||||
### Unlisted Scripts
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['<name>.[jt]sx?', '<name>.js'],
|
||||
['<name>/index.[jt]sx?', '<name>.js'],
|
||||
['{name}.[jt]sx?', '{name}.js'],
|
||||
['{name}/index.[jt]sx?', '{name}.js'],
|
||||
]"
|
||||
/>
|
||||
|
||||
@@ -524,12 +664,29 @@ export default defineUnlistedScript({
|
||||
|
||||
:::
|
||||
|
||||
Scripts are accessible from `/<name>.js`:
|
||||
At runtime, unlisted scripts are accessible from `/{name}.js`:
|
||||
|
||||
```ts
|
||||
const url = browser.runtime.getURL('/<name>.js');
|
||||
const url = browser.runtime.getURL('/{name}.js');
|
||||
|
||||
console.log(url); // "chrome-extension://<id>/<name>.js"
|
||||
console.log(url); // "chrome-extension://{id}/{name}.js"
|
||||
```
|
||||
|
||||
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).
|
||||
You are responsible for loading/running these scripts where needed. If necessary, don't forget to add the script and/or any related assets to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources).
|
||||
|
||||
When defining an unlisted script, keep in mind that WXT will import this file in a NodeJS environment during the build process. That means you cannot place any runtime code outside the `main` function.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
document.querySelectorAll('a').forEach((anchor) => { // [!code --]
|
||||
// ... // [!code --]
|
||||
}); // [!code --]
|
||||
|
||||
export default defineUnlistedScript(() => {
|
||||
document.querySelectorAll('a').forEach((anchor) => { // [!code ++]
|
||||
// ... // [!code ++]
|
||||
}); // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
Refer to the [Entrypoint Loaders](/guide/essentials/config/entrypoint-loaders) documentation for more details.
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
# ES Modules
|
||||
|
||||
Currently, ESM entrypoints are opt-in, so you must configure each entrypoint with that in mind.
|
||||
Your source code should always be written as ESM. However, you have some control whether an entrypoint is bundled as ESM.
|
||||
|
||||
## 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"`:
|
||||
Vite only supports bundling JS from HTML pages as ESM. Ensure you have added `type="module"` to your `<script>` tags:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
@@ -16,23 +14,25 @@ To make a script ESM, add `type="module"`:
|
||||
|
||||
## Background <Badge type="warning" text="≥0.16.0" />
|
||||
|
||||
In your background script, set `type: "module"`:
|
||||
By default, your background will be bundled into a single file as IIFE. You can change this by setting `type: "module"` in your background entrypoint:
|
||||
|
||||
```ts
|
||||
export default defineBackground({
|
||||
type: 'module', // !code ++
|
||||
type: 'module', // [!code ++]
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This will change the output format to ESM, enable code-spliting between your background script and HTML pages, and set `"type": "module"` in your manifest.
|
||||
|
||||
:::warning
|
||||
Only MV3 supports 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
|
||||
|
||||
WXT does not yet include built-in support for ESM content scripts. The plan is to add support for chunking to reduce bundle size, but not support HMR for now. There are several technical issues that make implementing a generic solution for HMR impossible. See [Content Script ESM Support #357](https://github.com/wxt-dev/wxt/issues/357) for details.
|
||||
WXT does not yet include built-in support for bundling content scripts as ESM. The plan is to add support for chunking to reduce bundle size, but not support HMR for now. There are several technical issues that make implementing a generic solution for HMR impossible. See [Content Script ESM Support #357](https://github.com/wxt-dev/wxt/issues/357) for details.
|
||||
|
||||
If you can't wait, and need ESM support right now, you can implement ESM support manually. See the [ESM Content Script UI](https://github.com/wxt-dev/examples/tree/main/examples/esm-content-script-ui) example to get started.
|
||||
If you can't wait, and need ESM support right now, you can implement ESM support manually. See the [ESM Content Script UI](https://github.com/wxt-dev/examples/tree/main/examples/esm-content-script-ui) example to learn how.
|
||||
|
||||
@@ -4,62 +4,47 @@
|
||||
|
||||
Different browsers provide different global variables for accessing the extension APIs (chrome provides `chrome`, firefox provides `browser`, etc).
|
||||
|
||||
WXT simplifies this - always use `browser`:
|
||||
WXT merges these two into a unified API accessed through the `browser` variable.
|
||||
|
||||
```ts
|
||||
import { browser } from 'wxt/browser';
|
||||
|
||||
browser.action.onClicked.addListener(() => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Other than that, refer to Chrome and Mozilla's documentation for how to use specific APIs. Everything a normal extension can do, WXT can do as well, just via `browser` instead of `chrome`.
|
||||
:::tip
|
||||
With auto-imports enabled, you don't even need to import this variable from `wxt/browser`!
|
||||
:::
|
||||
|
||||
## Webextension Polyfill
|
||||
The `browser` variable WXT provides is a simple export of the `browser` or `chrome` globals provided by the browser at runtime:
|
||||
|
||||
> Since `v0.1.0`
|
||||
<<< @/../packages/browser/src/index.mjs#snippet
|
||||
|
||||
By default, WXT uses the [`webextension-polyfill` by Mozilla](https://www.npmjs.com/package/webextension-polyfill) to make the extension API consistent between browsers.
|
||||
This means you can use the promise-style API for both MV2 and MV3, and it will work across all browsers (Chromium, Firefox, Safari, etc).
|
||||
|
||||
To access types, you should import the relevant namespace from `wxt/browser`:
|
||||
## Accessing Types
|
||||
|
||||
All types can be accessed via WXT's `Browser` namespace:
|
||||
|
||||
```ts
|
||||
import { Runtime } from 'wxt/browser';
|
||||
import { type Browser } from 'wxt/browser';
|
||||
|
||||
function handleMessage(message: any, sender: Runtime.Sender) {
|
||||
function handleMessage(message: any, sender: Browser.runtime.MessageSender) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Disabling the polyfill
|
||||
## Using `webextension-polyfill`
|
||||
|
||||
> Since `v0.19.0`
|
||||
If you want to use the `webextension-polyfill` when importing `browser`, you can do so by installing the `@wxt-dev/webextension-polyfill` package.
|
||||
|
||||
After the release of MV3 and Chrome's official deprecation of MV2 in June 2024, the polyfill isn't really doing anything useful anymore.
|
||||
|
||||
You can disable it with a single line:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
extensionApi: 'chrome',
|
||||
});
|
||||
```
|
||||
|
||||
This will change `wxt/browser` to simply export the `browser` or `chrome` globals based on browser at runtime:
|
||||
|
||||
<<< @/../packages/wxt/src/browser/chrome.ts#snippet
|
||||
|
||||
Accessing types is a little different with the polyfill disabled. They do not need to be imported; they're available on the `browser` object itself:
|
||||
|
||||
```ts
|
||||
function handleMessage(message: any, sender: browser.runtime.Sender) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
See it's [Installation Guide](https://github.com/wxt-dev/wxt/blob/main/packages/webextension-polyfill/README.md) to get started.
|
||||
|
||||
## Feature Detection
|
||||
|
||||
Depending on the manifest version and browser, some APIs are not available at runtime. If an API is not available, it will be `undefined`.
|
||||
Depending on the manifest version, browser, and permissions, some APIs are not available at runtime. If an API is not available, it will be `undefined`.
|
||||
|
||||
:::warning
|
||||
Types will not help you here. The types WXT provides for `browser` assume all APIs exist. You are responsible for knowing whether an API is available or not.
|
||||
@@ -90,3 +75,63 @@ Alternatively, if you're trying to use similar APIs under different names (to su
|
||||
//
|
||||
});
|
||||
```
|
||||
|
||||
## Entrypoint Limitations
|
||||
|
||||
Because WXT imports your entrypoint files into a NodeJS, non-extension environment, the `chrome`/`browser` variables provided to extensions by the browser **will not be available**.
|
||||
|
||||
To prevent some basic errors, WXT polyfills these globals with the same in-memory, fake implementation it uses for testing: [`@webext-core/fake-browser`](https://webext-core.aklinker1.io/fake-browser/installation/). However, not all the APIs have been implemented.
|
||||
|
||||
So it is extremely important to NEVER use `browser.*` extension APIs outside the main function of any JS/TS entrypoints (background, content scripts, and unlisted scripts). If you do, you'll see an error like this:
|
||||
|
||||
```plaintext
|
||||
✖ Command failed after 440 ms
|
||||
|
||||
ERROR Browser.action.onClicked.addListener not implemented.
|
||||
```
|
||||
|
||||
The fix is simple, just move your API usage into the entrypoint's main function:
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [background.ts]
|
||||
browser.action.onClicked.addListener(() => {
|
||||
/* ... */
|
||||
}); // [!code --]
|
||||
|
||||
export default defineBackground(() => {
|
||||
browser.action.onClicked.addListener(() => {
|
||||
/* ... */
|
||||
}); // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
```ts [content.ts]
|
||||
browser.runtime.onMessage.addListener(() => {
|
||||
/* ... */
|
||||
}); // [!code --]
|
||||
|
||||
export default defineContentScript({
|
||||
main() {
|
||||
browser.runtime.onMessage.addListener(() => {
|
||||
/* ... */
|
||||
}); // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts [unlisted.ts]
|
||||
browser.runtime.onMessage.addListener(() => {
|
||||
/* ... */
|
||||
}); // [!code --]
|
||||
|
||||
export default defineUnlistedScript(() => {
|
||||
browser.runtime.onMessage.addListener(() => {
|
||||
/* ... */
|
||||
}); // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Read [Entrypoint Loaders](/guide/essentials/config/entrypoint-loaders) for more technical details about this limitation.
|
||||
|
||||
@@ -9,6 +9,7 @@ This page discusses how to setup internationalization using the vanilla `browser
|
||||
## Usage
|
||||
|
||||
1. Add `default_locale` to your manifest:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
@@ -16,11 +17,12 @@ This page discusses how to setup internationalization using the vanilla `browser
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
2. Create `messages.json` files in the `public/` directory:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
📂 {srcDir}/
|
||||
📂 {rootDir}/
|
||||
📂 public/
|
||||
📂 _locales/
|
||||
📂 en/
|
||||
@@ -41,9 +43,11 @@ This page discusses how to setup internationalization using the vanilla `browser
|
||||
```
|
||||
|
||||
3. Get the translation:
|
||||
|
||||
```ts
|
||||
browser.i18n.getMessage('helloWorld');
|
||||
```
|
||||
|
||||
4. _Optional_: Add translations for extension name and description:
|
||||
|
||||
```json
|
||||
@@ -81,6 +85,10 @@ However, it is recommended you stick with the vanilla API (or a package based on
|
||||
- Translations are not bundled multiple times, keeping your extension small
|
||||
- Zero configuration
|
||||
|
||||
However, there is one major downside to the vanilla API and any packages built on top of it:
|
||||
|
||||
- Language cannot be changed without changing your browser/system language
|
||||
|
||||
Here are some examples of how to setup a third party i18n library:
|
||||
|
||||
- [vue-i18n](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vue-i18n)
|
||||
|
||||
@@ -14,3 +14,4 @@ Here are some popular messaging libraries that support all browsers and work wit
|
||||
- [`webext-bridge`](https://www.npmjs.com/package/webext-bridge) - Messaging in WebExtensions made super easy. Out of the box.
|
||||
- [`@webext-core/messaging`](https://www.npmjs.com/package/@webext-core/messaging) - Light weight, type-safe wrapper around the web extension messaging APIs
|
||||
- [`@webext-core/proxy-service`](https://www.npmjs.com/package/@webext-core/proxy-service) - A type-safe wrapper around the web extension messaging APIs that lets you call a function from anywhere, but execute it in the background.
|
||||
- [`Comctx`](https://github.com/molvqingtai/comctx) - Cross-context RPC solution with type safety and flexible adapters.
|
||||
|
||||
@@ -30,9 +30,10 @@ Here's a brief summary of each of these files and directories:
|
||||
- `.wxt/`: Generated by WXT, it contains TS config
|
||||
- `assets/`: Contains all CSS, images, and other assets that should be processed by WXT
|
||||
- `components/`: Auto-imported by default, contains UI components
|
||||
- `composables/`: Auto-imported by default, contains composable functions for Vue
|
||||
- `composables/`: Auto-imported by default, contains source code for your project's composable functions for Vue
|
||||
- `entrypoints/`: Contains all the entrypoints that get bundled into your extension
|
||||
- `hooks/`: Auto-imported by default, contains hooks for React and Solid
|
||||
- `hooks/`: Auto-imported by default, contains source code for your project's hooks for React and Solid
|
||||
- `modules/`: Contains [local WXT Modules](/guide/essentials/wxt-modules) for your project
|
||||
- `public/`: Contains any files you want to copy into the output folder as-is, without being processed by WXT
|
||||
- `utils/`: Auto-imported by default, contains generic utilities used throughout your project
|
||||
- `.env`: Contains [Environment Variables](/guide/essentials/config/environment-variables)
|
||||
@@ -47,8 +48,7 @@ Here's a brief summary of each of these files and directories:
|
||||
|
||||
Many developers like having a `src/` directory to separate source code from configuration files. You can enable it inside the `wxt.config.ts` file:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
srcDir: 'src',
|
||||
});
|
||||
@@ -59,24 +59,24 @@ After enabling it, your project structure should look like this:
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
📂 {rootDir}/
|
||||
📁 .output/
|
||||
📁 .wxt/
|
||||
📂 src/
|
||||
📁 assets/
|
||||
📁 components/
|
||||
📁 composables/
|
||||
📁 entrypoints/
|
||||
📁 hooks/
|
||||
📁 .output/
|
||||
📁 .wxt/
|
||||
📁 modules/
|
||||
📁 public/
|
||||
📁 utils/
|
||||
📄 app.config.ts
|
||||
📄 .env
|
||||
📄 .env.publish
|
||||
📄 package.json
|
||||
📄 tsconfig.json
|
||||
📄 web-ext.config.ts
|
||||
📄 wxt.config.ts
|
||||
📂 src/
|
||||
📁 assets/
|
||||
📁 components/
|
||||
📁 composables/
|
||||
📁 entrypoints/
|
||||
📁 hooks/
|
||||
📁 utils/
|
||||
📄 app.config.ts
|
||||
📄 .env
|
||||
📄 .env.publish
|
||||
📄 package.json
|
||||
📄 tsconfig.json
|
||||
📄 web-ext.config.ts
|
||||
📄 wxt.config.ts
|
||||
```
|
||||
|
||||
## Customizing Other Directories
|
||||
@@ -84,17 +84,16 @@ After enabling it, your project structure should look like this:
|
||||
You can configure the following directories:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
// Relative to project root
|
||||
srcDir: "src", // default: "."
|
||||
modulesDir: "wxt-modules", // default: "modules"
|
||||
outDir: "dist", // default: ".output"
|
||||
publicDir: "static", // default: "public"
|
||||
|
||||
// Relative to srcDir
|
||||
entrypointsDir: "entries", // default: "entrypoints"
|
||||
modulesDir: "wxt-modules", // default: "modules"
|
||||
publicDir: "static", // default: "public"
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ WXT provides two commands to help automate submitting a new version for review a
|
||||
- `wxt submit init`: Setup all the required secrets and options for the `wxt submit` command
|
||||
- `wxt submit`: Submit new versions of your extension for review (and publish them automatically once approved)
|
||||
|
||||
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.
|
||||
To get started, run `wxt submit init` and follow the prompts, or run `wxt submit --help` to view all available options. 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.
|
||||
|
||||
@@ -72,13 +72,13 @@ jobs:
|
||||
submit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -129,12 +129,11 @@ wxt zip
|
||||
|
||||
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/).
|
||||
|
||||
When running `wxt zip -b firefox`, WXT will zip both your extension and sources. Certain files (such as config files, hidden files, and tests) are automatically excluded from your sources. However, it's important to manually check the ZIP to ensure it only contains the files necessary to rebuild your extension.
|
||||
When running `wxt zip -b firefox`, WXT will zip both your extension and sources. Certain files (such as config files, hidden files, tests, and excluded entrypoints) are automatically excluded from your sources. 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
|
||||
```ts [wxt.config.ts]
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
@@ -184,8 +183,7 @@ See Issue [#377](https://github.com/wxt-dev/wxt/issues/377) for more details.
|
||||
|
||||
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
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
zip: {
|
||||
downloadPackages: [
|
||||
|
||||
@@ -12,15 +12,18 @@ When using `browser.scripting.executeScript`, you can execute content scripts or
|
||||
// entrypoints/background.ts
|
||||
const res = await browser.scripting.executeScript({
|
||||
target: { tabId },
|
||||
files: ['injected.js'],
|
||||
files: ['content-scripts/example.js'],
|
||||
});
|
||||
console.log(res); // "Hello John!"
|
||||
```
|
||||
|
||||
```ts
|
||||
// entrypoints/injected.js
|
||||
export default defineUnlistedScript(() => {
|
||||
console.log('Script was injected!');
|
||||
return 'Hello John!';
|
||||
// entrypoints/example.content.ts
|
||||
export default defineContentScript({
|
||||
registration: 'runtime',
|
||||
main(ctx) {
|
||||
console.log('Script was executed!');
|
||||
return 'Hello John!';
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -6,7 +6,7 @@ You can use the vanilla APIs (see docs above), use [WXT's built-in storage API](
|
||||
|
||||
## Alternatives
|
||||
|
||||
1. [`wxt/storage`](/storage) (recommended): WXT ships with its own wrapper around the vanilla storage APIs that simplifies common use cases
|
||||
1. [`wxt/utils/storage`](/storage) (recommended): WXT ships with its own wrapper around the vanilla storage APIs that simplifies common use cases
|
||||
|
||||
2. DIY: If you're migrating to WXT and already have a storage wrapper, keep using it. In the future, if you want to delete that code, you can use one of these alternatives, but there's no reason to replace working code during a migration.
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ Here are some examples:
|
||||
```
|
||||
|
||||
- HTML file only built for all targets other than `chrome`:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
@@ -75,3 +76,5 @@ Here are some examples:
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Alternatively, you can use the [`filterEntrypoints` config](/api/reference/wxt/interfaces/InlineConfig#filterentrypoints) to list all the entrypoints you want to build.
|
||||
|
||||
@@ -9,7 +9,7 @@ WXT provides first class support for Vitest for unit testing:
|
||||
```ts
|
||||
// vitest.config.ts
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { WxtVitest } from 'wxt/testing';
|
||||
import { WxtVitest } from 'wxt/testing/vitest-plugin';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [WxtVitest()],
|
||||
@@ -32,11 +32,11 @@ Here are real projects with unit testing setup. Look at the code and tests to se
|
||||
|
||||
### Example Tests
|
||||
|
||||
This example demonstrates that you don't have to mock `browser.storage` (used by `wxt/storage`) in tests - [`@webext-core/fake-browser`](https://webext-core.aklinker1.io/fake-browser/installation) implements storage in-memory so it behaves like it would in a real extension!
|
||||
This example demonstrates that you don't have to mock `browser.storage` (used by `wxt/utils/storage`) in tests - [`@webext-core/fake-browser`](https://webext-core.aklinker1.io/fake-browser/installation) implements storage in-memory so it behaves like it would in a real extension!
|
||||
|
||||
```ts
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { fakeBrowser } from 'wxt/testing';
|
||||
import { fakeBrowser } from 'wxt/testing/fake-browser';
|
||||
|
||||
const accountStorage = storage.defineItem<Account>('local:account');
|
||||
|
||||
@@ -71,10 +71,38 @@ describe('isLoggedIn', () => {
|
||||
});
|
||||
```
|
||||
|
||||
### Mocking WXT APIs
|
||||
|
||||
First, you need to understand how the `#imports` module works. When WXT (and vitest) sees this import during a preprocessing step, the import is replaced with multiple imports pointing to their "real" import path.
|
||||
|
||||
For example, this is what your write in your source code:
|
||||
|
||||
```ts
|
||||
// What you write
|
||||
import { injectScript, createShadowRootUi } from '#imports';
|
||||
```
|
||||
|
||||
But Vitest sees this:
|
||||
|
||||
```ts
|
||||
import { injectScript } from 'wxt/utils/inject-script';
|
||||
import { createShadowRootUi } from 'wxt/utils/content-script-ui/shadow-root';
|
||||
```
|
||||
|
||||
So in this case, if you wanted to mock `injectScript`, you need to pass in `"wxt/utils/inject-script"`, not `"#imports"`.
|
||||
|
||||
```ts
|
||||
vi.mock("wxt/utils/inject-script", () => ({
|
||||
injectScript: ...
|
||||
}))
|
||||
```
|
||||
|
||||
Refer to your project's `.wxt/types/imports-module.d.ts` file to lookup real import paths for `#imports`. If the file doesn't exist, run [`wxt prepare`](/guide/essentials/config/typescript).
|
||||
|
||||
## Other Testing Frameworks
|
||||
|
||||
To use a different framework, you will likely have to disable auto-imports, setup import aliases, manually mock the extension APIs, and setup the test environment to support all of WXT's features that you use.
|
||||
|
||||
It is possible to do, but will require a bit more setup. Refer to Vitest's setup for an example of how to setup a test environment:
|
||||
|
||||
https://github.com/wxt-dev/wxt/blob/main/packages/wxt/src/testing/wxt-vitest-plugin.ts
|
||||
<https://github.com/wxt-dev/wxt/blob/main/packages/wxt/src/testing/wxt-vitest-plugin.ts>
|
||||
|
||||
@@ -9,19 +9,23 @@ WXT provides a "module system" that let's you run code at different steps in the
|
||||
There are two ways to add a module to your project:
|
||||
|
||||
1. **NPM**: install an NPM package, like [`@wxt-dev/auto-icons`](https://www.npmjs.com/package/@wxt-dev/auto-icons) and add it to your config:
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/auto-icons'],
|
||||
});
|
||||
```
|
||||
|
||||
> Searching for ["wxt module"](https://www.npmjs.com/search?q=wxt%20module) on NPM is a good way to find published WXT modules.
|
||||
|
||||
2. **Local**: add a file to your project's `modules/` directory:
|
||||
```
|
||||
<srcDir>/
|
||||
|
||||
```plaintext
|
||||
<rootDir>/
|
||||
modules/
|
||||
my-module.ts
|
||||
```
|
||||
|
||||
> To learn more about writing your own modules, read the [Writing Modules](/guide/essentials/wxt-modules) docs.
|
||||
|
||||
## Module Options
|
||||
@@ -62,7 +66,7 @@ Each module's setup function is executed after the `wxt.config.ts` file is loade
|
||||
|
||||
Refer to the [API reference](/api/reference/wxt/interfaces/Wxt) for a complete list of properties and functions available.
|
||||
|
||||
Also to make sure and read about all the [hooks that are available](https://wxt.dev/api/reference/wxt/interfaces/WxtHooks) - they are essential to writing modules.
|
||||
Also make sure to read about [all the hooks that are available](/api/reference/wxt/interfaces/WxtHooks) - they are essential to writing modules.
|
||||
|
||||
### Recipes
|
||||
|
||||
@@ -75,7 +79,7 @@ import { defineWxtModule } from 'wxt/modules';
|
||||
|
||||
export default defineWxtModule({
|
||||
setup(wxt) {
|
||||
wxt.hook('ready', () => {
|
||||
wxt.hook('config:resolved', () => {
|
||||
wxt.config.outDir = 'dist';
|
||||
});
|
||||
},
|
||||
@@ -112,12 +116,12 @@ export default defineWxtModule<AnalyticModuleOptions>({
|
||||
|
||||
```ts
|
||||
import { defineWxtModule } from 'wxt/modules';
|
||||
import 'wxt/sandbox';
|
||||
import 'wxt/utils/define-app-config';
|
||||
|
||||
export interface MyModuleRuntimeOptions {
|
||||
// Add your runtime options here...
|
||||
}
|
||||
declare module 'wxt/sandbox' {
|
||||
declare module 'wxt/utils/define-app-config' {
|
||||
export interface WxtAppConfig {
|
||||
myModule: MyModuleOptions;
|
||||
}
|
||||
@@ -167,6 +171,31 @@ This file could then be loaded at runtime:
|
||||
const res = await fetch(browser.runtime.getURL('/some-text.txt'));
|
||||
```
|
||||
|
||||
#### Add custom entrypoints
|
||||
|
||||
Once the existing files under the `entrypoints/` directory have been discovered, the `entrypoints:found` hook can be used to add custom entrypoints.
|
||||
|
||||
:::info
|
||||
The `entrypoints:found` hook is triggered before validation is carried out on the list of entrypoints. Thus, any custom entrypoints will still be checked for duplicate names and logged during debugging.
|
||||
:::
|
||||
|
||||
```ts
|
||||
import { defineWxtModule } from 'wxt/modules';
|
||||
|
||||
export default defineWxtModule({
|
||||
setup(wxt) {
|
||||
wxt.hook('entrypoints:found', (_, entrypointInfos) => {
|
||||
// Add your new entrypoint
|
||||
entrypointInfos.push({
|
||||
name: 'my-custom-script',
|
||||
inputPath: 'path/to/custom-script.js',
|
||||
type: 'content-script',
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Generate runtime module
|
||||
|
||||
Create a file in `.wxt`, add an alias to import it, and add auto-imports for exported variables.
|
||||
|
||||
@@ -45,48 +45,64 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
|
||||
|
||||
1. Create a new project
|
||||
:::code-group
|
||||
|
||||
```sh [PNPM]
|
||||
cd my-project
|
||||
pnpm init
|
||||
```
|
||||
|
||||
```sh [Bun]
|
||||
cd my-project
|
||||
bun init
|
||||
```
|
||||
|
||||
```sh [NPM]
|
||||
cd my-project
|
||||
npm init
|
||||
```
|
||||
|
||||
```sh [Yarn]
|
||||
cd my-project
|
||||
yarn init
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
2. Install WXT:
|
||||
:::code-group
|
||||
|
||||
```sh [PNPM]
|
||||
pnpm i -D wxt
|
||||
```
|
||||
|
||||
```sh [Bun]
|
||||
bun i -D wxt
|
||||
```
|
||||
|
||||
```sh [NPM]
|
||||
npm i -D wxt
|
||||
```
|
||||
|
||||
```sh [Yarn]
|
||||
yarn add --dev wxt
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
3. Add an entrypoint, `my-project/entrypoints/background.ts`:
|
||||
:::code-group
|
||||
|
||||
```ts
|
||||
export default defineBackground(() => {
|
||||
console.log('Hello world!');
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
4. Add scripts to your `package.json`:
|
||||
```json
|
||||
|
||||
```json [package.json]
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "wxt", // [!code ++]
|
||||
@@ -99,20 +115,26 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5. Run your extension in dev mode
|
||||
:::code-group
|
||||
|
||||
```sh [PNPM]
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
```sh [Bun]
|
||||
bun run dev
|
||||
```
|
||||
|
||||
```sh [NPM]
|
||||
npm run dev
|
||||
```
|
||||
|
||||
```sh [Yarn]
|
||||
yarn dev
|
||||
```
|
||||
|
||||
:::
|
||||
WXT will automatically open a browser window with your extension installed.
|
||||
|
||||
@@ -121,3 +143,4 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
|
||||
- Keep reading on about WXT's [Project Structure](/guide/essentials/project-structure) and other essential concepts to learn
|
||||
- Configure [automatic browser startup](/guide/essentials/config/browser-startup) during dev mode
|
||||
- Explore [WXT's example library](/examples) to see how to use specific APIs or perform common tasks
|
||||
- Checkout the [community page](/guide/resources/community) for a list of resources made by the community!
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# Welcome to WXT!
|
||||
# Welcome to WXT
|
||||
|
||||
WXT is a modern, open-source framework for building web extensions. Inspired by Nuxt, its goals are to:
|
||||
|
||||
- Provide an awesome [DX](https://about.gitlab.com/topics/devops/what-is-developer-experience/)
|
||||
- Provide first-class support for all major browsers
|
||||
|
||||
Check out the [comparison](/guide/resources/compare) to see how WXT compares to other tools for building web extnesions.
|
||||
Check out the [comparison](/guide/resources/compare) to see how WXT compares to other tools for building web extensions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
These docs assume you have a basic knowledge of how Chrome extensions are structured and how you access the extension APIs.
|
||||
These docs assume you have a basic knowledge of how web extensions are structured and how you access the extension APIs.
|
||||
|
||||
:::warning New to extension development?
|
||||
If you have never written an extension before, follow Chrome's [Hello World tutorial](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world) to first **_create an extension without WXT_**, then come back here.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Community
|
||||
|
||||
This page is dedicated to all the awesome people how have made something for WXT or that works with WXT. Blog posts, YouTube videos, NPM packages, etc. If a section doesn't exist for the thing you made, add one!
|
||||
|
||||
[[toc]]
|
||||
|
||||
## Blog Posts
|
||||
|
||||
- [Building Modern Cross Browser Web Extensions](https://aabidk.dev/tags/wxt/) by Aabid ([@aabidk20](https://github.com/aabidk20))
|
||||
- [Building Browser Extensions with WXT](https://rxliuli.com/blog/browser-extension-dev-01-introduction-to-basic-concepts/) by rxliuli ([@rxliuli](https://github.com/rxliuli))
|
||||
|
||||
## NPM Packages
|
||||
|
||||
- [`@webext-core/*`](https://webext-core.aklinker1.io/): Easy-to-use utilities for writing and testing web extensions that work on all browsers.
|
||||
- [`Comctx`](https://github.com/molvqingtai/comctx): Cross-context RPC solution with type safety and flexible adapters.
|
||||
@@ -10,6 +10,7 @@ Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework)
|
||||
|
||||
| Features | WXT | Plasmo | CRXJS |
|
||||
| ------------------------------------------------------- | :-----: | :-----: | :-----: |
|
||||
| Maintained | ✅ | 🟡 [^n] | 🟡 [^m] |
|
||||
| Supports all browsers | ✅ | ✅ | 🟡 [^j] |
|
||||
| MV2 Support | ✅ | ✅ | 🟡 [^a] |
|
||||
| MV3 Support | ✅ | ✅ | 🟡 [^a] |
|
||||
@@ -64,3 +65,7 @@ Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework)
|
||||
[^k]: There is no built-in wrapper around this API. However, you can still access the standard APIs via `chrome`/`browser` globals or use any 3rd party NPM package.
|
||||
|
||||
[^l]: WIP, moving very slowly. Follow [wxt-dev/wxt#357](https://github.com/wxt-dev/wxt/issues/357) for updates.
|
||||
|
||||
[^m]: See [crxjs/chrome-extension-tools#974](https://github.com/crxjs/chrome-extension-tools/discussions/974)
|
||||
|
||||
[^n]: Appears to be in maintenance mode with little to no maintainers nor feature development happening and _(see [wxt-dev/wxt#1404 (comment)](https://github.com/wxt-dev/wxt/pull/1404#issuecomment-2643089518))_
|
||||
|
||||
+204
-3
@@ -8,9 +8,7 @@ Commonly asked questions about how to use WXT or why it behaves the way it does.
|
||||
|
||||
[[toc]]
|
||||
|
||||
---
|
||||
|
||||
### Why are content scripts not showing up in the manifest?
|
||||
## Why aren't content scripts added to the manifest?
|
||||
|
||||
During development, WXT registers content scripts dynamically so they can be reloaded individually when a file is saved without reloading your entire extension.
|
||||
|
||||
@@ -19,3 +17,206 @@ To list the content scripts registered during development, open the service work
|
||||
```js
|
||||
await chrome.scripting.getRegisteredContentScripts();
|
||||
```
|
||||
|
||||
## How do I disable opening the browser automatically during development?
|
||||
|
||||
See <https://wxt.dev/guide/essentials/config/browser-startup.html#disable-opening-browser>
|
||||
|
||||
## How do I stay logged into a website during development?
|
||||
|
||||
See <https://wxt.dev/guide/essentials/config/browser-startup.html#persist-data>
|
||||
|
||||
## My component library doesn't work in content scripts
|
||||
|
||||
This is usually caused by one of two things (or both) when using `createShadowRootUi`:
|
||||
|
||||
1. Styles are added outside the `ShadowRoot`
|
||||
|
||||
:::details
|
||||
Some component libraries manually add CSS to the page by adding a `<style>` or `<link>` element. They place this element in the document's `<head>` by default. This causes your styles to be placed outside the `ShadowRoot` and it's isolation blocks the styles from being applied to your UI.
|
||||
|
||||
When a library does this, **you need to tell the library where to put its styles**. Here's the documentation for a few popular component libraries:
|
||||
- Ant Design: [`StyleProvider`](https://ant.design/docs/react/compatible-style#shadow-dom-usage)
|
||||
- Mantine: [`MantineProvider#getRootElement` and `MantineProvider#cssVariablesSelector`](https://mantine.dev/theming/mantine-provider/)
|
||||
|
||||
> If your library isn't listed above, try searching it's docs/issues for "shadow root", "shadow dom", or "css container". Not all libraries support shadow DOMs, you may have to open an issue to request this feature.
|
||||
|
||||
Here's an example of configuring Antd's styles:
|
||||
|
||||
```tsx
|
||||
import { StyleProvider } from '@ant-design/cssinjs';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
|
||||
const ui = await create`ShadowRoot`Ui(ctx, {
|
||||
// ...
|
||||
onMount: (container, shadow) => {
|
||||
const cssContainer = shadow.querySelector('head')!;
|
||||
const root = ReactDOM.createRoot(container);
|
||||
root.render(
|
||||
<StyleProvider container={cssContainer}>
|
||||
<App />
|
||||
</StyleProvider>,
|
||||
);
|
||||
return root;
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
2. UI elements are added outside the `ShadowRoot`
|
||||
|
||||
::::::details
|
||||
This is mostly caused by `Teleport` or `Portal` components that render an element somewhere else in the DOM, usually in the document's `<body>`. This is usually done for dialogs or popover components. This renders the element is outside the `ShadowRoot`, so styles are not applied to it.
|
||||
|
||||
To fix this, **you need to both provide a target to your app AND pass the target to the `Teleport`/`Portal`**.
|
||||
|
||||
First, store the reference to the `ShadowRoot`'s `<body>` element (not the document's `<body>`):
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [Vue]
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
|
||||
const ui = await create`ShadowRoot`Ui(ctx, {
|
||||
// ...
|
||||
onMount: (container, shadow) => {
|
||||
const teleportTarget = shadow.querySelector('body')!;
|
||||
const app = createApp(App)
|
||||
.provide('TeleportTarget', teleportTarget)
|
||||
.mount(container);
|
||||
return app;
|
||||
},
|
||||
});
|
||||
ui.mount();
|
||||
```
|
||||
|
||||
```tsx [React]
|
||||
// hooks/PortalTargetContext.ts
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const PortalTargetContext = createContext<HTMLElement>();
|
||||
|
||||
// entrypoints/example.content.ts
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
import PortalTargetContext from '~/hooks/PortalTargetContext';
|
||||
|
||||
const ui = await create`ShadowRoot`Ui(ctx, {
|
||||
// ...
|
||||
onMount: (container, shadow) => {
|
||||
const portalTarget = shadow.querySelector('body')!;
|
||||
const root = ReactDOM.createRoot(container);
|
||||
root.render(
|
||||
<PortalTargetContext.Provider value={portalTarget}>
|
||||
<App />
|
||||
</PortalTargetContext.Provider>,
|
||||
);
|
||||
return root;
|
||||
},
|
||||
});
|
||||
ui.mount();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Then use the reference when teleporting/portaling part of your UI to a different place in the DOM:
|
||||
|
||||
:::code-group
|
||||
|
||||
```vue [Vue]
|
||||
<script lang="ts" setup>
|
||||
import { Teleport } from 'vue';
|
||||
|
||||
const teleportTarget = inject('TeleportTarget');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Teleport :to="teleportTarget">
|
||||
<dialog>My dialog</dialog>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
```tsx [React]
|
||||
import { useContext } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import PortalTargetContext from '~/hooks/PortalTargetContext';
|
||||
|
||||
const MyComponent = () => {
|
||||
const portalTarget = useContext(PortalTargetContext);
|
||||
|
||||
return <div>{createPortal(<dialog>My dialog</dialog>, portalTarget)}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
If you use ShadCN, [see this blog post](https://aabidk.dev/blog/building-modern-cross-browser-web-extensions-content-scripts-and-ui/#using-radixui-portals-to-move-the-dialog-to-shadow-dom).
|
||||
|
||||
::::::
|
||||
|
||||
Both issues have the same cause: the library puts something outside the `ShadowRoot`, and the `ShadowRoot`'s isolation prevents CSS from being applied to your UI.
|
||||
|
||||
Both issues have the same fix: tell the library to put elements inside the `ShadowRoot`, not outside it. See the details above for more information and example fixes for each problem.
|
||||
|
||||
## Does WXT provide docs for LLMs?
|
||||
|
||||
Yes, WXT's documentation provides markdown files based on the [the /llms.txt proposal](https://llmstxt.org/).
|
||||
|
||||
## Is there an LLM trained on WXT's docs that I chat with?
|
||||
|
||||
Yes! There's a "Ask AI" button in the bottom right of the page, try it out! Or visit <https://knowledge.wxt.dev/> for a fullscreen experience.
|
||||
|
||||
Additionally, if you want to train your own model or provide context to your editor, you can use the LLM knowledge files hosted by the site:
|
||||
|
||||
<https://wxt.dev/knowledge/index.json>
|
||||
|
||||
You don't need to crawl the entire website, these files already contain all the relevant docs for training a LLM on WXT. But feel free to crawl it and generate your own files if you want!
|
||||
|
||||
## How do I run my WXT project with docker / [devcontainers](https://containers.dev)?
|
||||
|
||||
To run the WXT dev server in a devcontainer, but load the dev build of your extension in your browser:
|
||||
|
||||
1. **Bind-mount your project directory to your host**
|
||||
If you're using VS Code, you can open your project folder with the `Dev Containers: Open Folder in Container...` command. This keeps the folder synchronized between your host and the devcontainer, ensuring that the extension `dist` directory remains accessible from the host.
|
||||
|
||||
2. **Disable auto-opening the browser**
|
||||
WXT automatically opens your browser during development, but since you're running inside a container, it won't be able to access it. Follow [the guide here](https://wxt.dev/guide/essentials/config/browser-startup.html#disable-opening-browser) to disable browser auto-opening in your `wxt.config.ts`.
|
||||
|
||||
3. **Tell WXT to listen on all network interfaces**
|
||||
To enable hot-reloading, your extension has to connect to the WXT dev server running inside your container. WXT will only listen on `localhost` by default, which prevents connections from outside the devcontainer. To fix this you can instruct WXT to listen on all interfaces with `wxt --host 0.0.0.0`.
|
||||
|
||||
## How do I use the new Prompt API in Chrome?
|
||||
|
||||
The service responsible for the [Prompt API](https://developer.chrome.com/docs/ai/prompt-api) is not enabled by default if you let WXT open the browser during dev mode. When checking `LanguageModel.availability`, you will always receive "unavailable".
|
||||
|
||||
You have two options:
|
||||
|
||||
1. Pass the `--disable-features=DisableLoadExtensionCommandLineSwitch` feature flag to enable the service in the browser WXT opens:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
webExt: {
|
||||
chromiumArgs: [
|
||||
'--disable-features=DisableLoadExtensionCommandLineSwitch',
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
2. Disable the runner and install your extension in your regular chrome profile manually:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
webExt: {
|
||||
disabled: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -15,10 +15,15 @@ cd path/to/your/project
|
||||
pnpm dlx wxt@latest init example-wxt --template vanilla
|
||||
```
|
||||
|
||||
:::tip
|
||||
We recommend reviewing [project structure](/guide/essentials/project-structure.md) before you get started.
|
||||
You can customize directory names in `wxt.config.ts` to match your project's needs.
|
||||
:::
|
||||
|
||||
In general, you'll need to:
|
||||
|
||||
 <input type="checkbox" /> Install `wxt`<br />
|
||||
 <input type="checkbox" /> [Extend `.wxt/tsconfig.json`](/guide/essentials/config/typescript.html#typescript-configuration) in your project's `tsconfig.json`<br />
|
||||
 <input type="checkbox" /> [Extend `.wxt/tsconfig.json`](/guide/essentials/config/typescript#typescript-configuration) in your project's `tsconfig.json`<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 />
|
||||
@@ -26,7 +31,7 @@ In general, you'll need to:
|
||||
 <input type="checkbox" /> Convert custom import syntax to be compatible with Vite<br />
|
||||
 <input type="checkbox" /> Add a default export to JS entrypoints (`defineBackground`, `defineContentScript`, or `defineUnlistedScript`)<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" /> ⚠️ Compare final `manifest.json` files, making sure permissions and host permissions are unchanged<br/>
|
||||
:::warning
|
||||
If your extension is already live on the Chrome Web Store, use [Google's update testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool) to make sure no new permissions are being requested.
|
||||
:::
|
||||
@@ -48,7 +53,25 @@ Here's specific steps for other popular frameworks/build tools.
|
||||
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. Replace your [Plasmo tags](https://docs.plasmo.com/framework/workflows/build#with-a-custom-tag) (`--tag`) with [WXT build modes](/guide/essentials/config/build-mode) (`--mode`)
|
||||
8. 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.
|
||||
8. ⚠️ Compare the old production manifest to `.output/*/manifest.json`. They should have the same content as before. If not, tweak your entrypoints and config until they are the same.
|
||||
|
||||
### CRXJS
|
||||
|
||||
If you used CRXJS's vite plugin, it's a simple refactor! The main difference between CRXJS and WXT is how the tools decide which entrypoints to build. CRXJS looks at your `manifest` (and vite config for "unlisted" entries), while WXT looks at files in the `entrypoints` directory.
|
||||
|
||||
To migrate:
|
||||
|
||||
1. Move all entrypoints into the `entrypoints` directory, refactoring to WXT's style (TS files have a default export).
|
||||
2. Move [entrypoint specific options out of the manifest](/guide/essentials/entrypoints#defining-manifest-options) and into the entrypoint files themselves (like content script `matches` or `run_at`).
|
||||
3. Move any other `manifest.json` options [into the `wxt.config.ts` file](/guide/essentials/config/manifest), like permissions.
|
||||
4. For simplicity, you'll probably want to [disable auto-imports](/guide/essentials/config/auto-imports#disabling-auto-imports) at first (unless you were already using them via `unimport` or `unplugin-auto-imports`). If you like the feature, you can enable it later once you've finished the migration.
|
||||
5. Update your `package.json` to include all of [WXT's suggested scripts (see step 4)](/guide/installation#from-scratch)
|
||||
6. Specifically, make sure you add the `"postinstall": "wxt prepare"` script to your `package.json`.
|
||||
7. Delete your `vite.config.ts` file. Move any plugins into the `wxt.config.ts` file. If you use a frontend framework, [install the relevant WXT module](/guide/essentials/frontend-frameworks).
|
||||
8. Update your typescript project. [Extend WXT's generated config](/guide/essentials/config/typescript), and [add any path aliases to your `wxt.config.ts` file](/guide/essentials/config/typescript#tsconfig-paths).
|
||||
9. ⚠️ Compare the old production manifest to `.output/*/manifest.json`. They should have the same content as before. If not, tweak your entrypoints and config until they are the same.
|
||||
|
||||
Here's an example migration: [GitHub Better Line Counts - CRXJS → WXT](https://github.com/aklinker1/github-better-line-counts/commit/39d766d2ba86866efefc2e9004af554ee434e2a8)
|
||||
|
||||
### `vite-plugin-web-extension`
|
||||
|
||||
@@ -60,4 +83,4 @@ Since you're already using Vite, it's a simple refactor.
|
||||
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.
|
||||
7. ⚠️ Compare the old production manifest to `.output/*/manifest.json`. They should have the same content as before. If not, tweak your entrypoints and config until they are the same.
|
||||
|
||||
@@ -1,17 +1,304 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Upgrading WXT
|
||||
|
||||
## Overview
|
||||
|
||||
To upgrade WXT to the latest version... just install it!
|
||||
To upgrade WXT to the latest major version:
|
||||
|
||||
1. Install it, skipping scripts so `wxt prepare` doesn't run - it will probably throw an error after a major version change (we'll run it later).
|
||||
|
||||
```sh
|
||||
pnpm i wxt@latest --ignore-scripts
|
||||
```
|
||||
|
||||
2. Follow the upgrade steps below to fix any breaking changes.
|
||||
3. Run `wxt prepare`. It should succeed and type errors will go away afterwords.
|
||||
|
||||
```sh
|
||||
pnpm wxt prepare
|
||||
```
|
||||
|
||||
4. Manually test to make sure both dev mode and production builds work.
|
||||
|
||||
For minor or patch version updates, there are no special steps. Just update it with your package manager:
|
||||
|
||||
```sh
|
||||
pnpm i wxt@latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Listed below are all the breaking changes you should address when upgrading to a new version of WXT.
|
||||
|
||||
Currently, WXT is in pre-release. This means changes to the second digit, `v0.X`, are considered major and have breaking changes. Once v1 is released, only major version bumps will have breaking changes.
|
||||
|
||||
## v0.19.0 → v0.20.0
|
||||
|
||||
v0.20 is a big release! There are lots of breaking changes because this version is intended to be a release candidate for v1.0. If all goes well, v1.0 will be released with no additional breaking changes.
|
||||
|
||||
:::tip
|
||||
Read through all the changes once before updating your code.
|
||||
:::
|
||||
|
||||
### `webextension-polyfill` Removed
|
||||
|
||||
WXT's `browser` no longer uses the `webextension-polyfill`!
|
||||
|
||||
:::details Why?
|
||||
See <https://github.com/wxt-dev/wxt/issues/784>
|
||||
:::
|
||||
|
||||
To upgrade, you have two options:
|
||||
|
||||
1. **Stop using the polyfill**
|
||||
- If you're already using `extensionApi: "chrome"`, then you're not using the polyfill and there is nothing to change!
|
||||
- Otherwise there is only one change: `browser.runtime.onMessage` no longer supports using promises to return a response:
|
||||
|
||||
```ts
|
||||
browser.runtime.onMessage.addListener(async () => { // [!code --]
|
||||
const res = await someAsyncWork(); // [!code --]
|
||||
return res; // [!code --]
|
||||
browser.runtime.onMessage.addListener(async (_message, _sender, sendResponse) => { // [!code ++]
|
||||
someAsyncWork().then((res) => { // [!code ++]
|
||||
sendResponse(res); // [!code ++]
|
||||
}); // [!code ++]
|
||||
return true; // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
2. **Continue using the polyfill** - If you want to keep using the polyfill, you can! One less thing to worry about during this upgrade.
|
||||
- Install `webextension-polyfill` and WXT's [new polyfill module](https://www.npmjs.com/package/@wxt-dev/webextension-polyfill):
|
||||
|
||||
```sh
|
||||
pnpm i webextension-polyfill @wxt-dev/webextension-polyfill
|
||||
```
|
||||
|
||||
- Add the WXT module to your config:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/webextension-polyfill'],
|
||||
});
|
||||
```
|
||||
|
||||
The new `browser` object (and types) is backed by WXT's new package: [`@wxt-dev/browser`](https://www.npmjs.com/package/@wxt-dev/browser). This package continues WXT's mission of providing useful packages for the whole community. Just like [`@wxt-dev/storage`](https://www.npmjs.com/package/@wxt-dev/storage), [`@wxt-dev/i18n`](https://www.npmjs.com/package/@wxt-dev/i18n), [`@wxt-dev/analytics`](https://www.npmjs.com/package/@wxt-dev/analytics), it is designed to be easy to use in any web extension project, not just those using WXT, and provides a consistent API across all browsers and manifest versions.
|
||||
|
||||
### `extensionApi` Config Removed
|
||||
|
||||
The `extensionApi` config has been removed. Before, this config provided a way to opt into using the new `browser` object prior to v0.20.0.
|
||||
|
||||
Remove it from your `wxt.config.ts` file if present:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
extensionApi: 'chrome', // [!code --]
|
||||
});
|
||||
```
|
||||
|
||||
### Extension API Type Changes
|
||||
|
||||
With the new `browser` introduced in v0.20, how you access types has changed. WXT now provides types based on `@types/chrome` instead of `@types/webextension-polyfill`.
|
||||
|
||||
These types are more up-to-date with MV3 APIs, contain less bugs, are better organized, and don't have any auto-generated names.
|
||||
|
||||
To access types, use the new `Browser` namespace from `wxt/browser`:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
import type { Runtime } from 'wxt/browser'; // [!code --]
|
||||
import type { Browser } from 'wxt/browser'; // [!code ++]
|
||||
|
||||
function getMessageSenderUrl(sender: Runtime.MessageSender): string { // [!code --]
|
||||
function getMessageSenderUrl(sender: Browser.runtime.MessageSender): string { // [!code ++]
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
> If you use auto-imports, `Browser` will be available without manually importing it.
|
||||
|
||||
Not all type names will be the same as what `@types/webextension-polyfill` provides. You'll have to find the new type names by looking at the types of the `browser.*` API's you use.
|
||||
|
||||
### `public/` and `modules/` Directories Moved
|
||||
|
||||
The default location for the `public/` and `modules/` directories have changed to better align with standards set by other frameworks (Nuxt, Next, Astro, etc). Now, each path is relative to the project's **root directory**, not the src directory.
|
||||
|
||||
- If you follow the default folder structure, you don't need to make any changes.
|
||||
- If you set a custom `srcDir`, you have two options:
|
||||
1. Move the your `public/` and `modules/` directories to the project root:
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
📂 {rootDir}/
|
||||
📁 modules/ <!-- [!code ++] -->
|
||||
📁 public/ <!-- [!code ++] -->
|
||||
📂 src/
|
||||
📁 components/
|
||||
📁 entrypoints/
|
||||
📁 modules/ <!-- [!code --] -->
|
||||
📁 public/ <!-- [!code --] -->
|
||||
📁 utils/
|
||||
📄 app.config.ts
|
||||
📄 wxt.config.ts
|
||||
```
|
||||
|
||||
2. Keep the folders in the same place and update your project config:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
srcDir: 'src',
|
||||
publicDir: 'src/public', // [!code ++]
|
||||
modulesDir: 'src/modules', // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
### Import Path Changes and `#imports`
|
||||
|
||||
The APIs exported by `wxt/sandbox`, `wxt/client`, or `wxt/storage` have moved to individual exports under the `wxt/utils/*` path.
|
||||
|
||||
:::details Why?
|
||||
As WXT grows and more utilities are added, any helper with side-effects will not be tree-shaken out of your final bundle.
|
||||
|
||||
This can cause problems because not every API used by these side-effects is available in every type of entrypoint. Some APIs can only be used in the background, sandboxed pages can't use any extension API, etc. This was leading to JS throwing errors in the top-level scope, preventing your code from running.
|
||||
|
||||
Splitting each util into it's own module solves this problem, making sure you're only importing APIs and side-effects into entrypoints they can run in.
|
||||
:::
|
||||
|
||||
Refer to the updated [API Reference](/api/reference/) to see the list of new import paths.
|
||||
|
||||
However, you don't need to memorize or learn the new import paths! v0.20 introduces a new virtual module, `#imports`, that abstracts all this away from developers. See the [blog post](/blog/2024-12-06-using-imports-module) for more details about how this module works.
|
||||
|
||||
So to upgrade, just replace any imports from `wxt/storage`, `wxt/client`, and `wxt/sandbox` with an import to the new `#imports` module:
|
||||
|
||||
```ts
|
||||
import { storage } from 'wxt/storage'; // [!code --]
|
||||
import { defineContentScript } from 'wxt/sandbox'; // [!code --]
|
||||
import { ContentScriptContext, useAppConfig } from 'wxt/client'; // [!code --]
|
||||
import { storage } from '#imports'; // [!code ++]
|
||||
import { defineContentScript } from '#imports'; // [!code ++]
|
||||
import { ContentScriptContext, useAppConfig } from '#imports'; // [!code ++]
|
||||
```
|
||||
|
||||
You can combine the imports into a single import statement, but it's easier to just find/replace each statement.
|
||||
|
||||
```ts
|
||||
import { storage } from 'wxt/storage'; // [!code --]
|
||||
import { defineContentScript } from 'wxt/sandbox'; // [!code --]
|
||||
import { ContentScriptContext, useAppConfig } from 'wxt/client'; // [!code --]
|
||||
import {
|
||||
// [!code ++]
|
||||
storage, // [!code ++]
|
||||
defineContentScript, // [!code ++]
|
||||
ContentScriptContext, // [!code ++]
|
||||
useAppConfig, // [!code ++]
|
||||
} from '#imports'; // [!code ++]
|
||||
```
|
||||
|
||||
:::tip
|
||||
Before types will work, you'll need to run `wxt prepare` after installing v0.20 to generate the new TypeScript declarations.
|
||||
:::
|
||||
|
||||
### `createShadowRootUi` CSS Changes
|
||||
|
||||
WXT now resets styles inherited from the webpage (`visibility`, `color`, `font-size`, etc.) by setting `all: initial` inside the shadow root.
|
||||
|
||||
:::warning
|
||||
This doesn't effect `rem` units. You should continue using `postcss-rem-to-px` or an equivalent library if the webpage sets the HTML element's `font-size`.
|
||||
:::
|
||||
|
||||
If you use `createShadowRootUi`:
|
||||
|
||||
1. Remove any manual CSS overrides that reset the style of specific websites. For example:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```css [entrypoints/reddit.content/style.css]
|
||||
body { /* [!code --] */
|
||||
/* Override Reddit's default "hidden" visibility on elements */ /* [!code --] */
|
||||
visibility: visible !important; /* [!code --] */
|
||||
} /* [!code --] */
|
||||
```
|
||||
|
||||
2. Double check that your UI looks the same as before.
|
||||
|
||||
If you run into problems with the new behavior, you can disable it and continue using your current CSS:
|
||||
|
||||
```ts
|
||||
const ui = await createShadowRootUi({
|
||||
inheritStyles: true, // [!code ++]
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
### Default Output Directories Changed
|
||||
|
||||
The default value for the [`outDirTemplate`](/api/reference/wxt/interfaces/InlineConfig#outdirtemplate) config has changed. Now, different build modes are output to different directories:
|
||||
|
||||
- `--mode production` → `.output/chrome-mv3`: Production builds are unchanged
|
||||
- `--mode development` → `.output/chrome-mv3-dev`: Dev mode now has a `-dev` suffix so it doesn't overwrite production builds
|
||||
- `--mode custom` → `.output/chrome-mv3-custom`: Other custom modes end with a `-[mode]` suffix
|
||||
|
||||
To use the old behavior, writing all output to the same directory, set the `outDirTemplate` option:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
outDirTemplate: '{{browser}}-mv{{manifestVersion}}', // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
:::warning
|
||||
If you've previously loaded the extension into your browser manually for development, you'll need to uninstall and re-install it from the new dev output directory.
|
||||
:::
|
||||
|
||||
### Deprecated APIs Removed
|
||||
|
||||
- `entrypointLoader` option: WXT now uses `vite-node` for importing entrypoints during the build process.
|
||||
<!-- markdownlint-disable-next-line MD051 -->
|
||||
> This was deprecated in v0.19.0, see the [v0.19 section](#v0-18-5-rarr-v0-19-0) for migration steps.
|
||||
- `transformManifest` option: Use the `build:manifestGenerated` hook to transform the manifest instead:
|
||||
<!-- prettier-ignore -->
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
transformManifest(manifest) { // [!code --]
|
||||
hooks: { // [!code ++]
|
||||
'build:manifestGenerated': (_, manifest) => { // [!code ++]
|
||||
// ...
|
||||
}, // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### New Deprecations
|
||||
|
||||
#### `runner` APIs Renamed
|
||||
|
||||
To improve consistency with the `web-ext.config.ts` filename, the "runner" API and config options have been renamed. You can continue using the old names, but they have been deprecated and will be removed in a future version:
|
||||
|
||||
1. The `runner` option has been renamed to `webExt`:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
runner: { // [!code --]
|
||||
webExt: { // [!code ++]
|
||||
startUrls: ["https://wxt.dev"],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
2. `defineRunnerConfig` has been renamed to `defineWebExtConfig`:
|
||||
|
||||
```ts [web-ext.config.ts]
|
||||
import { defineRunnerConfig } from 'wxt'; // [!code --]
|
||||
import { defineWebExtConfig } from 'wxt'; // [!code ++]
|
||||
```
|
||||
|
||||
3. The `ExtensionRunnerConfig` type has been renamed to `WebExtConfig`
|
||||
|
||||
```ts
|
||||
import type { ExtensionRunnerConfig } from 'wxt'; // [!code --]
|
||||
import type { WebExtConfig } from 'wxt'; // [!code ++]
|
||||
```
|
||||
|
||||
## v0.18.5 → v0.19.0
|
||||
|
||||
### `vite-node` Entrypoint Loader
|
||||
@@ -19,7 +306,7 @@ Currently, WXT is in pre-release. This means changes to the second digit, `v0.X`
|
||||
The default entrypoint loader has changed to `vite-node`. If you use any NPM packages that depend on the `webextension-polyfill`, you need to add them to Vite's `ssr.noExternal` option:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
vite: () => ({ // [!code ++]
|
||||
ssr: { // [!code ++]
|
||||
@@ -35,8 +322,7 @@ export default defineConfig({
|
||||
|
||||
Importing variables and using them in the entrypoint options:
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
```ts [entrypoints/content.ts]
|
||||
import { GOOGLE_MATCHES } from '~/utils/constants'
|
||||
|
||||
export default defineContentScript({
|
||||
@@ -47,8 +333,7 @@ export default defineContentScript({
|
||||
|
||||
Using Vite-specific APIs like `import.meta.glob` to define entrypoint options:
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
```ts [entrypoints/content.ts]
|
||||
const providers: Record<string, any> = import.meta.glob('../providers/*', {
|
||||
eager: true,
|
||||
});
|
||||
@@ -69,7 +354,7 @@ Basically, you can now import and do things outside the `main` function of the e
|
||||
|
||||
To continue using the old approach, add the following to your `wxt.config.ts` file:
|
||||
|
||||
```ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
entrypointLoader: 'jiti', // [!code ++]
|
||||
});
|
||||
@@ -86,7 +371,7 @@ WXT no longer ships with Common JS support. If you're using CJS, here's your mig
|
||||
1. Add [`"type": "module"`](https://nodejs.org/api/packages.html#type) to your `package.json`.
|
||||
2. Change the file extension of any `.js` files that use CJS syntax to `.cjs`, or update them to use EMS syntax.
|
||||
|
||||
Vite also provides steps for migrating to ESM. Check them out for more details: https://vitejs.dev/guide/migration#deprecate-cjs-node-api
|
||||
Vite also provides steps for migrating to ESM. Check them out for more details: <https://vitejs.dev/guide/migration#deprecate-cjs-node-api>
|
||||
|
||||
## v0.18.0 → v0.18.5
|
||||
|
||||
@@ -101,33 +386,13 @@ If you already have `<srcDir>/modules` or `<srcDir>/Modules` directory, `wxt pre
|
||||
You have two options:
|
||||
|
||||
1. [Recommended] Keep your files where they are and tell WXT to look in a different folder:
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
modulesDir: 'wxt-modules', // defaults to "modules"
|
||||
});
|
||||
```
|
||||
2. Rename your `modules` directory to something else.
|
||||
|
||||
## v0.18.0 → v0.18.5
|
||||
|
||||
> When this version was released, it was not considered a breaking change... but it should have been.
|
||||
|
||||
### New `modules/` Directory
|
||||
|
||||
WXT now recognizes the `modules/` directory as a folder containing [WXT modules](/guide/essentials/wxt-modules).
|
||||
|
||||
If you already have `<srcDir>/modules` or `<srcDir>/Modules` directory, `wxt prepare` and other commands will fail.
|
||||
|
||||
You have two options:
|
||||
|
||||
1. [Recommended] Keep your files where they are and tell WXT to look in a different folder:
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
modulesDir: 'wxt-modules', // defaults to "modules"
|
||||
});
|
||||
```
|
||||
2. Rename your `modules` directory to something else.
|
||||
|
||||
## v0.17.0 → v0.18.0
|
||||
@@ -181,7 +446,7 @@ item.watch((newValue: number, oldValue: number) => { // [!code ++]
|
||||
|
||||
JS entrypoints in the output directory have been moved. Unless you're doing some kind of post-build work referencing files, you don't have to make any changes.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
.output/
|
||||
<target>/
|
||||
chunks/
|
||||
@@ -196,8 +461,7 @@ JS entrypoints in the output directory have been moved. Unless you're doing some
|
||||
|
||||
### Renamed `zip.ignoredSources` to `zip.excludeSources`
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
zip: {
|
||||
ignoredSources: [
|
||||
@@ -212,7 +476,7 @@ export default defineConfig({
|
||||
|
||||
### Renamed Undocumented Constants
|
||||
|
||||
Renamed undocumented constants for detecting the build config at runtime in [#380](https://github.com/wxt-dev/wxt/pull/380). Now documented here: https://wxt.dev/guide/multiple-browsers.html#runtime
|
||||
Renamed undocumented constants for detecting the build config at runtime in [#380](https://github.com/wxt-dev/wxt/pull/380). Now documented here: <https://wxt.dev/guide/multiple-browsers.html#runtime>
|
||||
|
||||
- `__BROWSER__` → `import.meta.env.BROWSER`
|
||||
- `__COMMAND__` → `import.meta.env.COMMAND`
|
||||
@@ -239,7 +503,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
### New `wxt/storage` APIs
|
||||
|
||||
`wxt/storage` no longer relies on [`unstorage`](https://www.npmjs.com/package/unstorage). Some `unstorage` APIs, like `prefixStorage`, have been removed, while others, like `snapshot`, are methods on the new `storage` object. Most of the standard usage remains the same. See https://wxt.dev/guide/storage and https://wxt.dev/api/reference/wxt/storage/ for more details ([#300](https://github.com/wxt-dev/wxt/pull/300))
|
||||
`wxt/storage` no longer relies on [`unstorage`](https://www.npmjs.com/package/unstorage). Some `unstorage` APIs, like `prefixStorage`, have been removed, while others, like `snapshot`, are methods on the new `storage` object. Most of the standard usage remains the same. See <https://wxt.dev/guide/storage> and <https://wxt.dev/api/reference/wxt/storage/> for more details ([#300](https://github.com/wxt-dev/wxt/pull/300))
|
||||
|
||||
## v0.11.0 → v0.12.0
|
||||
|
||||
@@ -249,6 +513,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
- If you use auto-imports, no changes are required.
|
||||
- If you have disabled auto-imports, you'll need to manually update your import statements:
|
||||
|
||||
```ts
|
||||
import { defineBackground, defineContentScript } from 'wxt/client'; // [!code --]
|
||||
import { defineBackground, defineContentScript } from 'wxt/sandbox'; // [!code ++]
|
||||
|
||||
+9
-3
@@ -6,7 +6,7 @@ title: Next-gen Web Extension Framework
|
||||
hero:
|
||||
name: WXT
|
||||
text: Next-gen Web Extension Framework
|
||||
tagline: An open source tool that makes Chrome Extension development faster than ever before.
|
||||
tagline: An open source tool that makes web extension development faster than ever before.
|
||||
image:
|
||||
src: /hero-logo.svg
|
||||
alt: WXT
|
||||
@@ -73,9 +73,15 @@ features:
|
||||
linkText: Read docs
|
||||
---
|
||||
|
||||
## Sponsors
|
||||
|
||||
WXT is a [MIT-licensed](https://github.com/wxt-dev/wxt/blob/main/LICENSE) open source project with its ongoing development made possible entirely by the support of these awesome backers. If you'd like to join them, please consider [sponsoring WXT's development](https://github.com/sponsors/wxt-dev).
|
||||
|
||||
<a href="https://github.com/sponsors/wxt-dev"><img alt="WXT Sponsors" src="https://raw.githubusercontent.com/wxt-dev/static/refs/heads/main/sponsorkit/sponsors-wide.svg"></a>
|
||||
|
||||
## Put <span style="color: var(--vp-c-brand-1)">Developer Experience</span> First
|
||||
|
||||
WXT 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.
|
||||
WXT simplifies the web 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.
|
||||
|
||||
<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>
|
||||
@@ -87,7 +93,7 @@ WXT simplifies the chrome extension development process by providing tools for z
|
||||
|
||||
## Who's Using WXT?
|
||||
|
||||
Battle tested and ready for production. Explore chrome extensions made with WXT.
|
||||
Battle tested and ready for production. Explore web extensions made with WXT.
|
||||
|
||||
<ClientOnly>
|
||||
<UsingWxtSection />
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
/guide/build-targets.html /guide/multiple-browsers.html
|
||||
/guide/installation.html /get-started/installation.html
|
||||
/guide/introduction.html /get-started/introduction.html
|
||||
/guide/upgrade-guide/wxt /guide/resources/upgrading.html
|
||||
/guide/upgrade-guide/wxt.html /guide/resources/upgrading.html
|
||||
|
||||
# 0.19.0
|
||||
/guide/go-further/entrypoint-side-effects.html /guide/go-further/entrypoint-loaders.html
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<!--@include: ../packages/runner/README.md-->
|
||||
+70
-24
@@ -6,23 +6,46 @@ outline: deep
|
||||
|
||||
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md)
|
||||
|
||||
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:
|
||||
A simplified wrapper around the extension storage APIs.
|
||||
|
||||
## Installation
|
||||
|
||||
### With WXT
|
||||
|
||||
This module is built-in to WXT, so you don't need to install anything.
|
||||
|
||||
```ts
|
||||
import { storage } from 'wxt/storage';
|
||||
import { storage } from '#imports';
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 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'],
|
||||
> },
|
||||
> });
|
||||
> ```
|
||||
If you use auto-imports, `storage` is auto-imported for you, so you don't even need to import it!
|
||||
|
||||
### Without WXT
|
||||
|
||||
Install the NPM package:
|
||||
|
||||
```sh
|
||||
npm i @wxt-dev/storage
|
||||
pnpm add @wxt-dev/storage
|
||||
yarn add @wxt-dev/storage
|
||||
bun add @wxt-dev/storage
|
||||
```
|
||||
|
||||
```ts
|
||||
import { storage } from '@wxt-dev/storage';
|
||||
```
|
||||
|
||||
## Storage Permission
|
||||
|
||||
To use the `@wxt-dev/storage` API, the `"storage"` permission must be added to the manifest:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ['storage'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
@@ -51,11 +74,11 @@ await storage.watch<number>(
|
||||
await storage.getMeta<{ v: number }>('local:installDate');
|
||||
```
|
||||
|
||||
For a full list of methods available, see the [API reference](/api/reference/wxt/storage/interfaces/WxtStorage).
|
||||
For a full list of methods available, see the [API reference](/api/reference/wxt/utils/storage/interfaces/WxtStorage).
|
||||
|
||||
## Watchers
|
||||
|
||||
To listen for storage changes, use the `storage.watch` function. It lets you setup a listener for a single key:
|
||||
To listen for storage changes, use the `storage.watch` function. It lets you set up a listener for a single key:
|
||||
|
||||
```ts
|
||||
const unwatch = storage.watch<number>('local:counter', (newCount, oldCount) => {
|
||||
@@ -74,7 +97,7 @@ 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.
|
||||
`@wxt-dev/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:
|
||||
|
||||
@@ -100,7 +123,7 @@ You can remove all metadata associated with a key, or just specific properties:
|
||||
// Remove all properties
|
||||
await storage.removeMeta('local:preference');
|
||||
|
||||
// Remove one property
|
||||
// Remove only the "lastModified" property
|
||||
await storage.removeMeta('local:preference', 'lastModified');
|
||||
|
||||
// Remove multiple properties
|
||||
@@ -134,7 +157,7 @@ const unwatch = showChangelogOnUpdate.watch((newValue) => {
|
||||
});
|
||||
```
|
||||
|
||||
For a full list of properties and methods available, see the [API reference](/api/reference/wxt/storage/interfaces/WxtStorageItem).
|
||||
For a full list of properties and methods available, see the [API reference](/api/reference/wxt/utils/storage/interfaces/WxtStorageItem).
|
||||
|
||||
### Versioning
|
||||
|
||||
@@ -225,13 +248,13 @@ export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV3[]>( // [!code
|
||||
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.
|
||||
In this case, we thought that the ignored website list might change in the future, and were able to set up 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.
|
||||
Realistically, you won't know an item needs versioning until you need to change its 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:
|
||||
Let's look at the same ignored websites example from before, but start with an unversioned item this time:
|
||||
|
||||
:::code-group
|
||||
|
||||
@@ -275,13 +298,13 @@ export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>( // [!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.
|
||||
As soon as `storage.defineItem` is called, WXT checks if migrations need to be run, 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.
|
||||
|
||||
### Default Values
|
||||
|
||||
With `storage.defineItem`, there are multiple ways of defining default values:
|
||||
|
||||
1. `fallback` - Return this value from `getValue` instead of `null` if the value is missing.
|
||||
1. **`fallback`** - Return this value from `getValue` instead of `null` if the value is missing.
|
||||
|
||||
This option is great for providing default values for settings:
|
||||
|
||||
@@ -294,7 +317,7 @@ With `storage.defineItem`, there are multiple ways of defining default values:
|
||||
});
|
||||
```
|
||||
|
||||
2. `init` - Initialize and save a value in storage if it is not already saved.
|
||||
2. **`init`** - Initialize and save a value in storage if it is not already saved.
|
||||
|
||||
This is great for values that need to be initialized or set once:
|
||||
|
||||
@@ -308,3 +331,26 @@ With `storage.defineItem`, there are multiple ways of defining default values:
|
||||
```
|
||||
|
||||
The value is initialized in storage immediately.
|
||||
|
||||
## Bulk Operations
|
||||
|
||||
When getting or setting multiple values in storage, you can perform bulk operations to improve performance by reducing the number of individual storage calls. The `storage` API provides several methods for performing bulk operations:
|
||||
|
||||
- **`getItems`** - Get multiple values at once.
|
||||
- **`getMetas`** - Get metadata for multiple items at once.
|
||||
- **`setItems`** - Set multiple values at once.
|
||||
- **`setMetas`** - Set metadata for multiple items at once.
|
||||
- **`removeItems`** - Remove multiple values (and optionally metadata) at once.
|
||||
|
||||
All these APIs support both string keys and defined storage items:
|
||||
|
||||
```ts
|
||||
const userId = storage.defineItem('local:userId');
|
||||
|
||||
await storage.setItems([
|
||||
{ key: 'local:installDate', value: Date.now() },
|
||||
{ item: userId, value: generateUserId() },
|
||||
]);
|
||||
```
|
||||
|
||||
Refer to the [API Reference](/api/reference/wxt/utils/storage/interfaces/WxtStorage) for types and examples of how to use all the bulk APIs.
|
||||
|
||||
@@ -20,7 +20,7 @@ Set Framerate 50
|
||||
# Terminal theme with WXT brand colors (which was taken from the website)
|
||||
# Based on the standard charmbracelet/vhs theme:
|
||||
# https://github.com/charmbracelet/vhs/blob/88e634f4a10bbe305b6aea9a12b4d8dc3dd7f31c/style.go#L7-L28
|
||||
Set Theme {"background": "#1a1b1a", "foreground": "#dddddd", "black": "#282a2e", "brightBlack": "#4d4d4d", "red": "#D74E6F", "brightRed": "#FE5F86", "green": "#67d45e", "brightGreen": "#67d45e", "yellow": "#D3E561", "brightYellow": "#EBFF71", "blue": "#8056FF", "brightBlue": "#9B79FF", "magenta": "#ED61D7", "brightMagenta": "#FF7AEA", "cyan": "#04D7D7", "brightCyan": "#00FEFE", "white": "#bfbfbf", "brightWhite": "#e6e6e6", "indigo": "#5B56E0"}
|
||||
Set Theme {"background": "#161618", "foreground": "#dddddd", "black": "#282a2e", "brightBlack": "#4d4d4d", "red": "#D74E6F", "brightRed": "#FE5F86", "green": "#67d45e", "brightGreen": "#67d45e", "yellow": "#D3E561", "brightYellow": "#EBFF71", "blue": "#8056FF", "brightBlue": "#9B79FF", "magenta": "#ED61D7", "brightMagenta": "#FF7AEA", "cyan": "#04D7D7", "brightCyan": "#00FEFE", "white": "#bfbfbf", "brightWhite": "#e6e6e6", "indigo": "#5B56E0"}
|
||||
Set FontSize 32
|
||||
|
||||
# Terminal settings
|
||||
|
||||
+9
-2
@@ -2,11 +2,18 @@
|
||||
"$schema": "https://typedoc.org/schema.json",
|
||||
"entryPointStrategy": "packages",
|
||||
"entryPoints": ["../packages/wxt"],
|
||||
"plugin": ["typedoc-plugin-markdown", "typedoc-vitepress-theme"],
|
||||
"plugin": [
|
||||
"typedoc-plugin-markdown",
|
||||
"typedoc-vitepress-theme",
|
||||
"typedoc-plugin-frontmatter"
|
||||
],
|
||||
"out": "./api/reference",
|
||||
"githubPages": false,
|
||||
"excludePrivate": true,
|
||||
"excludeProtected": true,
|
||||
"excludeInternal": true,
|
||||
"readme": "none"
|
||||
"readme": "none",
|
||||
"frontmatterGlobals": {
|
||||
"editLink": false
|
||||
}
|
||||
}
|
||||
|
||||
+29
-29
@@ -4,40 +4,51 @@
|
||||
"engines": {
|
||||
"node": ">=18.20.3"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.0",
|
||||
"packageManager": "pnpm@10.29.2+sha512.bef43fa759d91fd2da4b319a5a0d13ef7a45bb985a3d7342058470f9d2051a3ba8674e629672654686ef9443ad13a82da2beb9eeb3e0221c87b8154fff9d74b8",
|
||||
"scripts": {
|
||||
"check": "check && pnpm -r --sequential run check",
|
||||
"test": "pnpm -r --sequential run test run",
|
||||
"test:coverage": "pnpm -r --sequential run test:coverage",
|
||||
"prepare": "simple-git-hooks",
|
||||
"prepublish": "pnpm -s build",
|
||||
"docs:gen": "typedoc --options docs/typedoc.json",
|
||||
"docs:dev": "pnpm -s docs:gen && vitepress dev docs",
|
||||
"docs:build": "pnpm -s docs:gen && vitepress build docs",
|
||||
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.4",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@aklinker1/buildc": "^1.1.6",
|
||||
"@aklinker1/check": "^2.2.0",
|
||||
"@commitlint/config-conventional": "^20.4.1",
|
||||
"@commitlint/types": "^20.4.0",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@vitest/coverage-v8": "^2.1.2",
|
||||
"changelogen": "^0.5.7",
|
||||
"consola": "^3.2.3",
|
||||
"fast-glob": "^3.3.2",
|
||||
"fs-extra": "^11.2.0",
|
||||
"lint-staged": "^15.2.10",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"changelogen": "^0.6.2",
|
||||
"consola": "^3.4.2",
|
||||
"fast-glob": "^3.3.3",
|
||||
"feed": "^5.2.0",
|
||||
"fs-extra": "^11.3.3",
|
||||
"lint-staged": "^16.2.7",
|
||||
"markdown-it-footnote": "^4.0.0",
|
||||
"nano-spawn": "^0.1.0",
|
||||
"prettier": "^3.3.3",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"tsx": "4.15.7",
|
||||
"markdownlint-cli": "^0.47.0",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"p-map": "^7.0.4",
|
||||
"prettier": "^3.8.1",
|
||||
"semver": "^7.7.4",
|
||||
"simple-git-hooks": "^2.13.1",
|
||||
"tsdown": "^0.20.3",
|
||||
"tsx": "4.21.0",
|
||||
"typedoc": "^0.25.4",
|
||||
"typedoc-plugin-frontmatter": "^1.3.1",
|
||||
"typedoc-plugin-markdown": "4.0.0-next.23",
|
||||
"typedoc-vitepress-theme": "1.0.0-next.3",
|
||||
"typescript": "^5.6.2",
|
||||
"vitepress": "^1.3.4",
|
||||
"vitest-mock-extended": "^2.0.2",
|
||||
"vue": "^3.5.11",
|
||||
"typescript": "^5.9.3",
|
||||
"vitepress": "^1.6.4",
|
||||
"vitepress-knowledge": "^0.4.1",
|
||||
"vitepress-plugin-group-icons": "^1.7.1",
|
||||
"vitepress-plugin-llms": "^1.11.0",
|
||||
"vitest-mock-extended": "^3.1.0",
|
||||
"vue": "^3.5.27",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
@@ -45,16 +56,5 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": "prettier --ignore-unknown --write"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"ignoreMissing": [
|
||||
"@algolia/client-search",
|
||||
"search-insights"
|
||||
]
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"markdown-it-footnote": "patches/markdown-it-footnote.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# Changelog
|
||||
|
||||
## v0.5.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.1...analytics-v0.5.2)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Normalize path for createAnalytics of analytics/index.ts ([#2013](https://github.com/wxt-dev/wxt/pull/2013))
|
||||
- Allow custom API URL in Google Analytics 4 provider options ([#1653](https://github.com/wxt-dev/wxt/pull/1653))
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- Code cleanup in analytics package ([#2084](https://github.com/wxt-dev/wxt/pull/2084))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Fix other type error after `chrome` types update ([31ebf966](https://github.com/wxt-dev/wxt/commit/31ebf966))
|
||||
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
|
||||
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
|
||||
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
|
||||
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Honwhy Wang <honwhy.wang@gmail.com>
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
|
||||
|
||||
## v0.5.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.0...analytics-v0.5.1)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Integrate latest measurement protocol changes ([#1767](https://github.com/wxt-dev/wxt/pull/1767))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Use `@wxt-dev/browser` instead of `@types/chrome` ([#1645](https://github.com/wxt-dev/wxt/pull/1645))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
|
||||
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
|
||||
- Change browser workspace dependency to `^` ([c7335add](https://github.com/wxt-dev/wxt/commit/c7335add))
|
||||
- Fix auto-fixable `markdownlint` errors ([#1710](https://github.com/wxt-dev/wxt/pull/1710))
|
||||
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
|
||||
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
|
||||
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Tanishq-aggarwal ([@tanishq-aggarwal](https://github.com/tanishq-aggarwal))
|
||||
|
||||
## v0.5.0
|
||||
|
||||
[⚠️ breaking changes](https://wxt.dev/guide/resources/upgrading.html) • [compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.4.1...analytics-v0.5.0)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- ⚠️ Update min WXT version to 0.20 ([2e8baf0](https://github.com/wxt-dev/wxt/commit/2e8baf0))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
@@ -0,0 +1,277 @@
|
||||
# WXT Analytics
|
||||
|
||||
Report analytics events from your web extension extension.
|
||||
|
||||
## Supported Analytics Providers
|
||||
|
||||
- [Google Analytics 4 (Measurement Protocol)](#google-analytics-4-measurement-protocol)
|
||||
- [Umami](#umami)
|
||||
|
||||
## Install With WXT
|
||||
|
||||
1. Install the NPM package:
|
||||
|
||||
```bash
|
||||
pnpm i @wxt-dev/analytics
|
||||
```
|
||||
|
||||
2. In your `wxt.config.ts`, add the WXT module:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/analytics/module'],
|
||||
});
|
||||
```
|
||||
|
||||
3. In your `<srcDir>/app.config.ts`, add a provider:
|
||||
|
||||
```ts
|
||||
// <srcDir>/app.config.ts
|
||||
import { umami } from '@wxt-dev/analytics/providers/umami';
|
||||
|
||||
export default defineAppConfig({
|
||||
analytics: {
|
||||
debug: true,
|
||||
providers: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
4. Then use the `#analytics` module to report events:
|
||||
|
||||
```ts
|
||||
import { analytics } from '#analytics';
|
||||
|
||||
await analytics.track('some-event');
|
||||
await analytics.page();
|
||||
await analytics.identify('some-user-id');
|
||||
analytics.autoTrack(document.body);
|
||||
```
|
||||
|
||||
## Install Without WXT
|
||||
|
||||
1. Install the NPM package:
|
||||
|
||||
```bash
|
||||
pnpm i @wxt-dev/analytics
|
||||
```
|
||||
|
||||
2. Create an `analytics` instance:
|
||||
|
||||
```ts
|
||||
// utils/analytics.ts
|
||||
import { createAnalytics } from '@wxt-dev/analytics';
|
||||
|
||||
export const analytics = createAnalytics({
|
||||
providers: [
|
||||
// ...
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
3. Import your analytics module in the background to initialize the message listener:
|
||||
|
||||
```ts
|
||||
// background.ts
|
||||
import './utils/analytics';
|
||||
```
|
||||
|
||||
4. Then use your `analytics` instance to report events:
|
||||
|
||||
```ts
|
||||
import { analytics } from './utils/analytics';
|
||||
|
||||
await analytics.track('some-event');
|
||||
await analytics.page();
|
||||
await analytics.identify('some-user-id');
|
||||
analytics.autoTrack(document.body);
|
||||
```
|
||||
|
||||
## Providers
|
||||
|
||||
### Google Analytics 4 (Measurement Protocol)
|
||||
|
||||
The [Measurement Protocol](https://developers.google.com/analytics/devguides/collection/protocol/ga4) is an alternative to GTag for reporting events to Google Analytics for MV3 extensions.
|
||||
|
||||
> [Why use the Measurement Protocol instead of GTag?](https://developer.chrome.com/docs/extensions/how-to/integrate/google-analytics-4#measurement-protocol)
|
||||
|
||||
Follow [Google's documentation](https://developer.chrome.com/docs/extensions/how-to/integrate/google-analytics-4#setup-credentials) to obtain your credentials and put them in your `.env` file:
|
||||
|
||||
```dotenv
|
||||
WXT_GA_API_SECRET='...'
|
||||
```
|
||||
|
||||
Then add the `googleAnalytics4` provider to your `<srcDir>/app.config.ts` file:
|
||||
|
||||
```ts
|
||||
import { googleAnalytics4 } from '@wxt-dev/analytics/providers/google-analytics-4';
|
||||
|
||||
export default defineAppConfig({
|
||||
analytics: {
|
||||
providers: [
|
||||
googleAnalytics4({
|
||||
apiSecret: import.meta.env.WXT_GA_API_SECRET,
|
||||
measurementId: '...',
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Umami
|
||||
|
||||
[Umami](https://umami.is/) is a privacy-first, open source analytics platform.
|
||||
|
||||
In Umami's dashboard, create a new website. The website's name and domain can be anything. Obviously, an extension doesn't have a domain, so make one up if you don't have one.
|
||||
|
||||
After the website has been created, save the website ID and domain to your `.env` file:
|
||||
|
||||
```dotenv
|
||||
WXT_UMAMI_WEBSITE_ID='...'
|
||||
WXT_UMAMI_DOMAIN='...'
|
||||
```
|
||||
|
||||
Then add the `umami` provider to your `<srcDir>/app.config.ts` file:
|
||||
|
||||
```ts
|
||||
import { umami } from '@wxt-dev/analytics/providers/umami';
|
||||
|
||||
export default defineAppConfig({
|
||||
analytics: {
|
||||
providers: [
|
||||
umami({
|
||||
apiUrl: 'https://<your-umami-instance>/api',
|
||||
websiteId: import.meta.env.WXT_UMAMI_WEBSITE_ID,
|
||||
domain: import.meta.env.WXT_UMAMI_DOMAIN,
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Custom Provider
|
||||
|
||||
If your analytics platform is not supported, you can provide an implementation of the `AnalyticsProvider` type in your `app.config.ts` instead:
|
||||
|
||||
```ts
|
||||
import { defineAnalyticsProvider } from '@wxt-dev/analytics/client';
|
||||
|
||||
interface CustomAnalyticsOptions {
|
||||
// ...
|
||||
}
|
||||
|
||||
const customAnalytics = defineAnalyticsProvider<CustomAnalyticsOptions>(
|
||||
(analytics, analyticsConfig, providerOptions) => {
|
||||
// ...
|
||||
},
|
||||
);
|
||||
|
||||
export default defineAppConfig({
|
||||
analytics: {
|
||||
providers: [
|
||||
customAnalytics({
|
||||
// ...
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Example `AnalyticsProvider` implementations can be found at [`./modules/analytics/providers`](https://github.com/wxt-dev/wxt/tree/main/packages/analytics/modules/analytics/providers).
|
||||
|
||||
## User Properties
|
||||
|
||||
User ID and properties are stored in `browser.storage.local`. To change this or customize where these values are stored, use the `userId` and `userProperties` config:
|
||||
|
||||
```ts
|
||||
// app.config.ts
|
||||
import { storage } from 'wxt/storage';
|
||||
|
||||
export default defineAppConfig({
|
||||
analytics: {
|
||||
userId: storage.defineItem('local:custom-user-id-key'),
|
||||
userProperties: storage.defineItem('local:custom-user-properties-key'),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
To set the values at runtime, use the `identify` function:
|
||||
|
||||
```ts
|
||||
await analytics.identify(userId, userProperties);
|
||||
```
|
||||
|
||||
Alternatively, a common pattern is to use a random string as the user ID. This keeps the actual user information private, while still providing useful metrics in your analytics platform. This can be done very easily using WXT's storage API:
|
||||
|
||||
```ts
|
||||
// app.config.ts
|
||||
import { storage } from 'wxt/storage';
|
||||
|
||||
export default defineAppConfig({
|
||||
analytics: {
|
||||
userId: storage.defineItem('local:custom-user-id-key', {
|
||||
init: () => crypto.randomUUID(),
|
||||
}),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
If you aren't using `wxt` or `@wxt-dev/storage`, you can define custom implementations for the `userId` and `userProperties` config:
|
||||
|
||||
```ts
|
||||
const analytics = createAnalytics({
|
||||
userId: {
|
||||
getValue: () => ...,
|
||||
setValue: (userId) => ...,
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Auto-track UI events
|
||||
|
||||
Call `analytics.autoTrack(container)` to automatically track UI events so you don't have to manually add them. Currently it:
|
||||
|
||||
- Tracks clicks to elements inside the `container`
|
||||
|
||||
In your extension's HTML pages, you'll want to call it with `document`:
|
||||
|
||||
```ts
|
||||
analytics.autoTrack(document);
|
||||
```
|
||||
|
||||
But in content scripts, you usually only care about interactions with your own UI:
|
||||
|
||||
```ts
|
||||
const ui = createIntegratedUi({
|
||||
// ...
|
||||
onMount(container) {
|
||||
analytics.autoTrack(container);
|
||||
},
|
||||
});
|
||||
ui.mount();
|
||||
```
|
||||
|
||||
## Enabling/Disabling
|
||||
|
||||
By default, **analytics is disabled**. You can configure how the value is stored (and change the default value) via the `enabled` config:
|
||||
|
||||
```ts
|
||||
// app.config.ts
|
||||
import { storage } from 'wxt/storage';
|
||||
|
||||
export default defineAppConfig({
|
||||
analytics: {
|
||||
enabled: storage.defineItem('local:analytics-enabled', {
|
||||
fallback: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
At runtime, you can call `setEnabled` to change the value:
|
||||
|
||||
```ts
|
||||
analytics.setEnabled(true);
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
import { defineAppConfig } from 'wxt/utils/define-app-config';
|
||||
import { googleAnalytics4 } from './modules/analytics/providers/google-analytics-4';
|
||||
import { umami } from './modules/analytics/providers/umami';
|
||||
|
||||
export default defineAppConfig({
|
||||
analytics: {
|
||||
debug: true,
|
||||
providers: [
|
||||
googleAnalytics4({
|
||||
apiSecret: '...',
|
||||
measurementId: '...',
|
||||
}),
|
||||
umami({
|
||||
apiUrl: 'https://umami.aklinker1.io/api',
|
||||
domain: 'analytics.wxt.dev',
|
||||
websiteId: '8f1c2aa4-fad3-406e-a5b2-33e8d4501716',
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Popup</title>
|
||||
</head>
|
||||
<body>
|
||||
<label>
|
||||
<input id="enabledCheckbox" type="checkbox" />
|
||||
 Analytics enabled
|
||||
</label>
|
||||
<button id="button1">Button 1</button>
|
||||
<button class="cool-button">Button 2</button>
|
||||
<script type="module" src="./main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { analytics } from '#analytics';
|
||||
|
||||
declare const enabledCheckbox: HTMLInputElement;
|
||||
|
||||
analytics.autoTrack(document);
|
||||
|
||||
enabledCheckbox.oninput = () => {
|
||||
void analytics.setEnabled(enabledCheckbox.checked);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
import '#analytics';
|
||||
|
||||
export default () => {};
|
||||
@@ -0,0 +1,290 @@
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
import type {
|
||||
Analytics,
|
||||
AnalyticsConfig,
|
||||
AnalyticsPageViewEvent,
|
||||
AnalyticsStorageItem,
|
||||
AnalyticsTrackEvent,
|
||||
BaseAnalyticsEvent,
|
||||
AnalyticsEventMetadata,
|
||||
AnalyticsProvider,
|
||||
} from './types';
|
||||
import { browser } from '@wxt-dev/browser';
|
||||
|
||||
const ANALYTICS_PORT = '@wxt-dev/analytics';
|
||||
|
||||
const INTERACTIVE_TAGS = new Set([
|
||||
'A',
|
||||
'BUTTON',
|
||||
'INPUT',
|
||||
'SELECT',
|
||||
'TEXTAREA',
|
||||
]);
|
||||
const INTERACTIVE_ROLES = new Set([
|
||||
'button',
|
||||
'link',
|
||||
'checkbox',
|
||||
'menuitem',
|
||||
'tab',
|
||||
'radio',
|
||||
]);
|
||||
|
||||
export function createAnalytics(config?: AnalyticsConfig): Analytics {
|
||||
if (!browser?.runtime?.id)
|
||||
throw Error(
|
||||
'Cannot use WXT analytics in contexts without access to the browser.runtime APIs',
|
||||
);
|
||||
if (config == null) {
|
||||
console.warn(
|
||||
"[@wxt-dev/analytics] Config not provided to createAnalytics. If you're using WXT, add the 'analytics' property to '<srcDir>/app.config.ts'.",
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: This only works for standard WXT extensions, add a more generic
|
||||
// background script detector that works with non-WXT projects.
|
||||
if (location.pathname === '/background.js')
|
||||
return createBackgroundAnalytics(config);
|
||||
|
||||
return createFrontendAnalytics();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an analytics client in the background responsible for uploading events to the server to avoid CORS errors.
|
||||
*/
|
||||
function createBackgroundAnalytics(
|
||||
config: AnalyticsConfig | undefined,
|
||||
): Analytics {
|
||||
// User properties storage
|
||||
const userIdStorage =
|
||||
config?.userId ?? defineStorageItem<string>('wxt-analytics:user-id');
|
||||
const userPropertiesStorage =
|
||||
config?.userProperties ??
|
||||
defineStorageItem<Record<string, string>>(
|
||||
'wxt-analytics:user-properties',
|
||||
{},
|
||||
);
|
||||
const enabled =
|
||||
config?.enabled ??
|
||||
defineStorageItem<boolean>('local:wxt-analytics:enabled', false);
|
||||
|
||||
// Cached values
|
||||
const platformInfo = browser.runtime.getPlatformInfo();
|
||||
const userAgent = UAParser();
|
||||
let userId = Promise.resolve(userIdStorage.getValue()).then(
|
||||
(id) => id ?? globalThis.crypto.randomUUID(),
|
||||
);
|
||||
let userProperties = userPropertiesStorage.getValue();
|
||||
const manifest = browser.runtime.getManifest();
|
||||
|
||||
const getBackgroundMeta = () => ({
|
||||
timestamp: Date.now(),
|
||||
// Don't track sessions for the background, it can be running indefinitely
|
||||
// and will inflate session duration stats.
|
||||
sessionId: undefined,
|
||||
language: navigator.language,
|
||||
referrer: undefined,
|
||||
screen: undefined,
|
||||
url: location.href,
|
||||
title: undefined,
|
||||
});
|
||||
|
||||
const getBaseEvent = async (
|
||||
meta: AnalyticsEventMetadata,
|
||||
): Promise<BaseAnalyticsEvent> => {
|
||||
const { arch, os } = await platformInfo;
|
||||
return {
|
||||
meta,
|
||||
user: {
|
||||
id: await userId,
|
||||
properties: {
|
||||
version: config?.version ?? manifest.version_name ?? manifest.version,
|
||||
wxtMode: import.meta.env.MODE,
|
||||
wxtBrowser: import.meta.env.BROWSER,
|
||||
arch,
|
||||
os,
|
||||
browser: userAgent.browser.name,
|
||||
browserVersion: userAgent.browser.version,
|
||||
...(await userProperties),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const analytics = {
|
||||
identify: async (
|
||||
newUserId: string,
|
||||
newUserProperties: Record<string, string> = {},
|
||||
meta: AnalyticsEventMetadata = getBackgroundMeta(),
|
||||
) => {
|
||||
// Update in-memory cache for all providers
|
||||
userId = Promise.resolve(newUserId);
|
||||
userProperties = Promise.resolve(newUserProperties);
|
||||
// Persist user info to storage
|
||||
await Promise.all([
|
||||
userIdStorage.setValue?.(newUserId),
|
||||
userPropertiesStorage.setValue?.(newUserProperties),
|
||||
]);
|
||||
// Notify providers
|
||||
const event = await getBaseEvent(meta);
|
||||
if (config?.debug) console.debug('[@wxt-dev/analytics] identify', event);
|
||||
if (await enabled.getValue()) {
|
||||
await Promise.allSettled(
|
||||
providers.map((provider) => provider.identify(event)),
|
||||
);
|
||||
} else if (config?.debug) {
|
||||
console.debug(
|
||||
'[@wxt-dev/analytics] Analytics disabled, identify() not uploaded',
|
||||
);
|
||||
}
|
||||
},
|
||||
page: async (
|
||||
location: string,
|
||||
meta: AnalyticsEventMetadata = getBackgroundMeta(),
|
||||
) => {
|
||||
const baseEvent = await getBaseEvent(meta);
|
||||
const event: AnalyticsPageViewEvent = {
|
||||
...baseEvent,
|
||||
page: {
|
||||
url: meta?.url ?? globalThis.location?.href,
|
||||
location,
|
||||
title: meta?.title ?? globalThis.document?.title,
|
||||
},
|
||||
};
|
||||
if (config?.debug) console.debug('[@wxt-dev/analytics] page', event);
|
||||
if (await enabled.getValue()) {
|
||||
await Promise.allSettled(
|
||||
providers.map((provider) => provider.page(event)),
|
||||
);
|
||||
} else if (config?.debug) {
|
||||
console.debug(
|
||||
'[@wxt-dev/analytics] Analytics disabled, page() not uploaded',
|
||||
);
|
||||
}
|
||||
},
|
||||
track: async (
|
||||
eventName: string,
|
||||
eventProperties?: Record<string, string>,
|
||||
meta: AnalyticsEventMetadata = getBackgroundMeta(),
|
||||
) => {
|
||||
const baseEvent = await getBaseEvent(meta);
|
||||
const event: AnalyticsTrackEvent = {
|
||||
...baseEvent,
|
||||
event: { name: eventName, properties: eventProperties },
|
||||
};
|
||||
if (config?.debug) console.debug('[@wxt-dev/analytics] track', event);
|
||||
if (await enabled.getValue()) {
|
||||
await Promise.allSettled(
|
||||
providers.map((provider) => provider.track(event)),
|
||||
);
|
||||
} else if (config?.debug) {
|
||||
console.debug(
|
||||
'[@wxt-dev/analytics] Analytics disabled, track() not uploaded',
|
||||
);
|
||||
}
|
||||
},
|
||||
setEnabled: async (newEnabled) => {
|
||||
await enabled.setValue?.(newEnabled);
|
||||
},
|
||||
autoTrack: () => {
|
||||
// Noop, background doesn't have a UI
|
||||
return () => {};
|
||||
},
|
||||
} satisfies Analytics;
|
||||
|
||||
const providers =
|
||||
config?.providers?.map((provider) => provider(analytics, config)) ?? [];
|
||||
|
||||
// Listen for messages from the rest of the extension
|
||||
browser.runtime.onConnect.addListener((port) => {
|
||||
if (port.name === ANALYTICS_PORT) {
|
||||
port.onMessage.addListener(({ fn, args }) => {
|
||||
// @ts-expect-error: Untyped fn key
|
||||
void analytics[fn]?.(...args);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return analytics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an analytics client for non-background contexts.
|
||||
*/
|
||||
function createFrontendAnalytics(): Analytics {
|
||||
const port = browser.runtime.connect({ name: ANALYTICS_PORT });
|
||||
const sessionId = Date.now();
|
||||
const getFrontendMetadata = (): AnalyticsEventMetadata => ({
|
||||
sessionId,
|
||||
timestamp: Date.now(),
|
||||
language: navigator.language,
|
||||
referrer: globalThis.document?.referrer || undefined,
|
||||
screen: globalThis.window
|
||||
? `${globalThis.window.screen.width}x${globalThis.window.screen.height}`
|
||||
: undefined,
|
||||
url: location.href,
|
||||
title: document.title || undefined,
|
||||
});
|
||||
|
||||
const methodForwarder =
|
||||
(fn: string) =>
|
||||
(...args: any[]) => {
|
||||
port.postMessage({ fn, args: [...args, getFrontendMetadata()] });
|
||||
};
|
||||
|
||||
const analytics: Analytics = {
|
||||
identify: methodForwarder('identify'),
|
||||
page: methodForwarder('page'),
|
||||
track: methodForwarder('track'),
|
||||
setEnabled: methodForwarder('setEnabled'),
|
||||
autoTrack: (root) => {
|
||||
const onClick = (event: Event) => {
|
||||
const element = event.target as any;
|
||||
if (
|
||||
!element ||
|
||||
(!INTERACTIVE_TAGS.has(element.tagName) &&
|
||||
!INTERACTIVE_ROLES.has(element.getAttribute('role')))
|
||||
)
|
||||
return;
|
||||
|
||||
void analytics.track('click', {
|
||||
tagName: element.tagName?.toLowerCase(),
|
||||
id: element.id || undefined,
|
||||
className: element.className || undefined,
|
||||
textContent: element.textContent?.substring(0, 50) || undefined, // Limit text content length
|
||||
href: element.href,
|
||||
});
|
||||
};
|
||||
root.addEventListener('click', onClick, { capture: true, passive: true });
|
||||
return () => {
|
||||
root.removeEventListener('click', onClick);
|
||||
};
|
||||
},
|
||||
};
|
||||
return analytics;
|
||||
}
|
||||
|
||||
function defineStorageItem<T>(
|
||||
key: string,
|
||||
defaultValue?: NonNullable<T>,
|
||||
): AnalyticsStorageItem<T> {
|
||||
return {
|
||||
getValue: async () =>
|
||||
(await browser.storage.local.get<Record<string, any>>(key))[key] ??
|
||||
defaultValue,
|
||||
setValue: (newValue) => browser.storage.local.set({ [key]: newValue }),
|
||||
};
|
||||
}
|
||||
|
||||
export function defineAnalyticsProvider<T = never>(
|
||||
definition: (
|
||||
/** The analytics object. */
|
||||
analytics: Analytics,
|
||||
/** Config passed into the analytics module from `app.config.ts`. */
|
||||
config: AnalyticsConfig,
|
||||
/** Provider options */
|
||||
options: T,
|
||||
) => ReturnType<AnalyticsProvider>,
|
||||
): (options: T) => AnalyticsProvider {
|
||||
return (options) => (analytics, config) =>
|
||||
definition(analytics, config, options);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'wxt';
|
||||
import 'wxt/utils/define-app-config';
|
||||
import {
|
||||
addAlias,
|
||||
addViteConfig,
|
||||
addWxtPlugin,
|
||||
defineWxtModule,
|
||||
} from 'wxt/modules';
|
||||
import { relative, resolve } from 'node:path';
|
||||
import type { AnalyticsConfig } from './types';
|
||||
import { normalizePath } from 'wxt';
|
||||
|
||||
declare module 'wxt/utils/define-app-config' {
|
||||
export interface WxtAppConfig {
|
||||
analytics: AnalyticsConfig;
|
||||
}
|
||||
}
|
||||
|
||||
export default defineWxtModule({
|
||||
name: 'analytics',
|
||||
imports: [{ name: 'analytics', from: '#analytics' }],
|
||||
setup(wxt) {
|
||||
// Paths
|
||||
const wxtAnalyticsFolder = resolve(wxt.config.wxtDir, 'analytics');
|
||||
const wxtAnalyticsIndex = resolve(wxtAnalyticsFolder, 'index.ts');
|
||||
const clientModuleId = process.env.NPM
|
||||
? '@wxt-dev/analytics'
|
||||
: resolve(wxt.config.modulesDir, 'analytics/client');
|
||||
const pluginModuleId = process.env.NPM
|
||||
? '@wxt-dev/analytics/background-plugin'
|
||||
: resolve(wxt.config.modulesDir, 'analytics/background-plugin');
|
||||
|
||||
// Add required permissions
|
||||
wxt.hook('build:manifestGenerated', (_, manifest) => {
|
||||
manifest.permissions ??= [];
|
||||
if (!manifest.permissions.includes('storage')) {
|
||||
manifest.permissions.push('storage');
|
||||
}
|
||||
});
|
||||
|
||||
// Generate #analytics module
|
||||
const wxtAnalyticsCode = `import { createAnalytics } from '${
|
||||
process.env.NPM
|
||||
? clientModuleId
|
||||
: normalizePath(relative(wxtAnalyticsFolder, clientModuleId))
|
||||
}';
|
||||
import { useAppConfig } from '#imports';
|
||||
|
||||
export const analytics = createAnalytics(useAppConfig().analytics);
|
||||
`;
|
||||
addAlias(wxt, '#analytics', wxtAnalyticsIndex);
|
||||
wxt.hook('prepare:types', async (_, entries) => {
|
||||
entries.push({
|
||||
path: wxtAnalyticsIndex,
|
||||
text: wxtAnalyticsCode,
|
||||
});
|
||||
});
|
||||
|
||||
// Ensure there is a background entrypoint
|
||||
wxt.hook('entrypoints:resolved', (_, entrypoints) => {
|
||||
const hasBackground = entrypoints.find(
|
||||
(entry) => entry.type === 'background',
|
||||
);
|
||||
if (!hasBackground) {
|
||||
entrypoints.push({
|
||||
type: 'background',
|
||||
inputPath: 'virtual:user-background',
|
||||
name: 'background',
|
||||
options: {},
|
||||
outputDir: wxt.config.outDir,
|
||||
skipped: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure analytics is initialized in every context, mainly the background.
|
||||
// TODO: Once there's a way to filter which entrypoints a plugin is applied to, only apply this to the background
|
||||
addWxtPlugin(wxt, pluginModuleId);
|
||||
|
||||
// Fix issues with dependencies
|
||||
addViteConfig(wxt, () => ({
|
||||
optimizeDeps: {
|
||||
// Ensure the "#analytics" import is processed by vite in the background plugin
|
||||
exclude: ['@wxt-dev/analytics'],
|
||||
// Ensure the CJS subdependency is preprocessed into ESM
|
||||
include: ['@wxt-dev/analytics > ua-parser-js'],
|
||||
},
|
||||
}));
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import { defineAnalyticsProvider } from '../client';
|
||||
import type { BaseAnalyticsEvent } from '../types';
|
||||
|
||||
const DEFAULT_ENGAGEMENT_TIME_IN_MSEC = 100;
|
||||
|
||||
export interface GoogleAnalytics4ProviderOptions {
|
||||
apiUrl?: string;
|
||||
apiSecret: string;
|
||||
measurementId: string;
|
||||
}
|
||||
|
||||
export const googleAnalytics4 =
|
||||
defineAnalyticsProvider<GoogleAnalytics4ProviderOptions>(
|
||||
(_, config, options) => {
|
||||
const send = async (
|
||||
data: BaseAnalyticsEvent,
|
||||
eventName: string,
|
||||
eventProperties: Record<string, string | undefined> | undefined,
|
||||
): Promise<void> => {
|
||||
const url = new URL(
|
||||
config?.debug ? '/debug/mp/collect' : '/mp/collect',
|
||||
options.apiUrl ?? 'https://www.google-analytics.com',
|
||||
);
|
||||
if (options.apiSecret)
|
||||
url.searchParams.set('api_secret', options.apiSecret);
|
||||
if (options.measurementId)
|
||||
url.searchParams.set('measurement_id', options.measurementId);
|
||||
|
||||
const userProperties = {
|
||||
language: data.meta.language,
|
||||
screen: data.meta.screen,
|
||||
...data.user.properties,
|
||||
};
|
||||
const mappedUserProperties = Object.fromEntries(
|
||||
Object.entries(userProperties).map(([name, value]) => [
|
||||
name,
|
||||
value == null ? undefined : { value },
|
||||
]),
|
||||
);
|
||||
|
||||
await fetch(url.href, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
client_id: data.user.id,
|
||||
consent: {
|
||||
ad_user_data: 'DENIED',
|
||||
ad_personalization: 'DENIED',
|
||||
},
|
||||
user_properties: mappedUserProperties,
|
||||
user_agent: navigator.userAgent,
|
||||
events: [
|
||||
{
|
||||
name: eventName,
|
||||
params: {
|
||||
session_id: data.meta.sessionId,
|
||||
engagement_time_msec: DEFAULT_ENGAGEMENT_TIME_IN_MSEC,
|
||||
...eventProperties,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
identify: () => Promise.resolve(), // No-op, user data uploaded in page/track
|
||||
page: (event) =>
|
||||
send(event, 'page_view', {
|
||||
page_title: event.page.title,
|
||||
page_location: event.page.location,
|
||||
}),
|
||||
track: (event) => send(event, event.event.name, event.event.properties),
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,70 @@
|
||||
import { defineAnalyticsProvider } from '../client';
|
||||
|
||||
export interface UmamiProviderOptions {
|
||||
apiUrl: string;
|
||||
websiteId: string;
|
||||
domain: string;
|
||||
}
|
||||
|
||||
export const umami = defineAnalyticsProvider<UmamiProviderOptions>(
|
||||
(_, config, options) => {
|
||||
const send = (payload: UmamiPayload) => {
|
||||
if (config.debug) {
|
||||
console.debug('[@wxt-dev/analytics] Sending event to Umami:', payload);
|
||||
}
|
||||
return fetch(`${options.apiUrl}/send`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ type: 'event', payload }),
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
identify: () => Promise.resolve(), // No-op, user data uploaded in page/track
|
||||
page: async (event) => {
|
||||
await send({
|
||||
name: 'page_view',
|
||||
website: options.websiteId,
|
||||
url: event.page.url,
|
||||
hostname: options.domain,
|
||||
language: event.meta.language ?? '',
|
||||
referrer: event.meta.referrer ?? '',
|
||||
screen: event.meta.screen ?? '',
|
||||
title: event.page.title ?? '<blank>',
|
||||
data: event.user.properties,
|
||||
});
|
||||
},
|
||||
track: async (event) => {
|
||||
await send({
|
||||
name: event.event.name,
|
||||
website: options.websiteId,
|
||||
url: event.meta.url ?? '/',
|
||||
title: '<blank>',
|
||||
hostname: options.domain,
|
||||
language: event.meta.language ?? '',
|
||||
referrer: event.meta.referrer ?? '',
|
||||
screen: event.meta.screen ?? '',
|
||||
data: {
|
||||
...event.event.properties,
|
||||
...event.user.properties,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
/** @see https://umami.is/docs/api/sending-stats#post-/api/send */
|
||||
interface UmamiPayload {
|
||||
hostname?: string;
|
||||
language?: string;
|
||||
referrer?: string;
|
||||
screen?: string;
|
||||
title?: string;
|
||||
url?: string;
|
||||
website: string;
|
||||
name: string;
|
||||
data?: Record<string, string | undefined>;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
export interface Analytics {
|
||||
/** Report a page change. */
|
||||
page: (url: string) => void;
|
||||
/** Report a custom event. */
|
||||
track: (eventName: string, eventProperties?: Record<string, string>) => void;
|
||||
/** Save information about the user. */
|
||||
identify: (userId: string, userProperties?: Record<string, string>) => void;
|
||||
/** Automatically setup and track user interactions, returning a function to remove any listeners that were setup. */
|
||||
autoTrack: (root: Document | ShadowRoot | Element) => () => void;
|
||||
/** Calls `config.enabled.setValue`. */
|
||||
setEnabled: (enabled: boolean) => void;
|
||||
}
|
||||
|
||||
export interface AnalyticsConfig {
|
||||
/**
|
||||
* Array of providers to send analytics to.
|
||||
*/
|
||||
providers: AnalyticsProvider[];
|
||||
/**
|
||||
* Enable debug logs and other provider-specific debugging features.
|
||||
*/
|
||||
debug?: boolean;
|
||||
/**
|
||||
* Your extension's version, reported alongside events.
|
||||
* @default browser.runtime.getManifest().version`.
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
* Configure how the enabled flag is persisted. Defaults to using `browser.storage.local`.
|
||||
*/
|
||||
enabled?: AnalyticsStorageItem<boolean>;
|
||||
/**
|
||||
* Configure how the user Id is persisted. Defaults to using `browser.storage.local`.
|
||||
*/
|
||||
userId?: AnalyticsStorageItem<string>;
|
||||
/**
|
||||
* Configure how user properties are persisted. Defaults to using `browser.storage.local`.
|
||||
*/
|
||||
userProperties?: AnalyticsStorageItem<Record<string, string>>;
|
||||
}
|
||||
|
||||
export interface AnalyticsStorageItem<T> {
|
||||
getValue: () => T | Promise<T>;
|
||||
setValue?: (newValue: T) => void | Promise<void>;
|
||||
}
|
||||
|
||||
export type AnalyticsProvider = (
|
||||
analytics: Analytics,
|
||||
config: AnalyticsConfig,
|
||||
) => {
|
||||
/** Upload a page view event. */
|
||||
page: (event: AnalyticsPageViewEvent) => Promise<void>;
|
||||
/** Upload a custom event. */
|
||||
track: (event: AnalyticsTrackEvent) => Promise<void>;
|
||||
/** Upload information about the user. */
|
||||
identify: (event: BaseAnalyticsEvent) => Promise<void>;
|
||||
};
|
||||
|
||||
export interface BaseAnalyticsEvent {
|
||||
meta: AnalyticsEventMetadata;
|
||||
user: {
|
||||
id: string;
|
||||
properties: Record<string, string | undefined>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AnalyticsEventMetadata {
|
||||
/** Identifier of the session the event was fired from. */
|
||||
sessionId: number | undefined;
|
||||
/** `Date.now()` of when the event was reported. */
|
||||
timestamp: number;
|
||||
/** Ex: `"1920x1080"`. */
|
||||
screen: string | undefined;
|
||||
/** `document.referrer` */
|
||||
referrer: string | undefined;
|
||||
/** `navigator.language` */
|
||||
language: string | undefined;
|
||||
/** `location.href` */
|
||||
url: string | undefined;
|
||||
/** `document.title` */
|
||||
title: string | undefined;
|
||||
}
|
||||
|
||||
export interface AnalyticsPageInfo {
|
||||
url: string;
|
||||
title: string | undefined;
|
||||
location: string | undefined;
|
||||
}
|
||||
|
||||
export interface AnalyticsPageViewEvent extends BaseAnalyticsEvent {
|
||||
page: AnalyticsPageInfo;
|
||||
}
|
||||
|
||||
export interface AnalyticsTrackEvent extends BaseAnalyticsEvent {
|
||||
event: {
|
||||
name: string;
|
||||
properties?: Record<string, string>;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "@wxt-dev/analytics",
|
||||
"version": "0.5.2",
|
||||
"description": "Add analytics to your web extension",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wxt-dev/wxt.git",
|
||||
"directory": "packages/analytics"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.mts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"./module": {
|
||||
"types": "./dist/module.d.mts",
|
||||
"default": "./dist/module.mjs"
|
||||
},
|
||||
"./background-plugin": {
|
||||
"types": "./dist/background-plugin.d.mts",
|
||||
"default": "./dist/background-plugin.mjs"
|
||||
},
|
||||
"./types": {
|
||||
"types": "./dist/types.d.mts"
|
||||
},
|
||||
"./providers/google-analytics-4": {
|
||||
"types": "./dist/providers/google-analytics-4.d.mts",
|
||||
"default": "./dist/providers/google-analytics-4.mjs"
|
||||
},
|
||||
"./providers/umami": {
|
||||
"types": "./dist/providers/umami.d.mts",
|
||||
"default": "./dist/providers/umami.mjs"
|
||||
}
|
||||
},
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.mts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "buildc --deps-only -- wxt",
|
||||
"dev:build": "buildc --deps-only -- wxt build",
|
||||
"check": "pnpm build && check",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepack": "pnpm -s build",
|
||||
"prepare": "buildc --deps-only -- wxt prepare"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"publint": "^0.3.17",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wxt-dev/browser": "workspace:^",
|
||||
"ua-parser-js": "^1.0.40"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": ["../../tsconfig.base.json", "./.wxt/tsconfig.json"],
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"#analytics": ["./.wxt/analytics/index.ts"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from 'tsdown';
|
||||
|
||||
export default defineConfig({
|
||||
entry: {
|
||||
index: './modules/analytics/client.ts',
|
||||
module: './modules/analytics/index.ts',
|
||||
'background-plugin': './modules/analytics/background-plugin.ts',
|
||||
types: './modules/analytics/types.ts',
|
||||
'providers/google-analytics-4':
|
||||
'./modules/analytics/providers/google-analytics-4.ts',
|
||||
'providers/umami': './modules/analytics/providers/umami.ts',
|
||||
},
|
||||
external: ['#analytics'],
|
||||
define: {
|
||||
'process.env.NPM': 'true',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
// Unimport doesn't look for imports in node_modules, so when developing a
|
||||
// WXT module, we need to disable this to simplify the build process
|
||||
imports: false,
|
||||
|
||||
manifest: {
|
||||
name: 'Analytics Demo',
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,37 @@
|
||||
# Changelog
|
||||
|
||||
## v1.1.0
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.2...auto-icons-v1.1.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add overlay option for dev icons ([#1825](https://github.com/wxt-dev/wxt/pull/1825))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Rewrite and restructure the documentation website ([#933](https://github.com/wxt-dev/wxt/pull/933))
|
||||
- Use full URLs in README so they work on the docs site ([d20793d5](https://github.com/wxt-dev/wxt/commit/d20793d5))
|
||||
- Add SVG compatibility note ([#1830](https://github.com/wxt-dev/wxt/pull/1830))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add `oxlint` for linting ([#947](https://github.com/wxt-dev/wxt/pull/947))
|
||||
- **deps:** Bump sharp from 0.33.4 to 0.33.5 ([#959](https://github.com/wxt-dev/wxt/pull/959))
|
||||
- Upgrade all non-major dependencies ([#1040](https://github.com/wxt-dev/wxt/pull/1040))
|
||||
- **deps:** Upgrade all non-major dependencies ([#1164](https://github.com/wxt-dev/wxt/pull/1164))
|
||||
- **deps:** Bump dev and non-breaking major dependencies ([#1167](https://github.com/wxt-dev/wxt/pull/1167))
|
||||
- Use PNPM 10's new catelog feature ([#1493](https://github.com/wxt-dev/wxt/pull/1493))
|
||||
- Move production dependencies to PNPM 10 catelog ([#1494](https://github.com/wxt-dev/wxt/pull/1494))
|
||||
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
|
||||
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Typed SIGTERM ([@typed-sigterm](https://github.com/typed-sigterm))
|
||||
- Kuba ([@zizzfizzix](https://github.com/zizzfizzix))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v1.0.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.1...auto-icons-v1.0.2)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user