Skip to content

WXT Migration#707

Merged
killergerbah merged 34 commits intokillergerbah:mainfrom
mwojick:WXT
May 25, 2025
Merged

WXT Migration#707
killergerbah merged 34 commits intokillergerbah:mainfrom
mwojick:WXT

Conversation

@mwojick
Copy link
Copy Markdown
Collaborator

@mwojick mwojick commented Apr 29, 2025

https://wxt.dev/

Lot of changes, partly due to changes in how things are built, because you can’t build things in the same arbitrary way that was being done before (which was expected), and also partly due to the fact that firefox needs to be built for mv2, since dev mode doesn’t work for firefox mv3. So pretty much everything will have to be tested. Not sure how to go about this efficiently. Maybe we need other people willing to test it. I’ve mostly tested it with youtube and at least that seems to be fine for some common things.

General info about WXT:

  • The manifest file is now generated by wxt based on the entrypoints plus the manual entries in wxt.config.ts. So it fills out things like content scripts, background, etc. for you based on how you’ve defined them in their files. So you can now include/exclude pages or scripts based on browser without having to manually modify the manifest.
  • When you run yarn install, there’s now a postinstall script that gets run that generates a .wxt folder that has a base tsconfig and some globals defined in d.ts files. Most notably are the define functions that get auto imported, like defineContentScript, as well as additions to import.meta like import.meta.env.BROWSER, etc. Also the auto import stuff can be disabled if you don’t like it, but it’s similar to what Nuxt does apparently.
  • For entrypoint files, they need to be in the src/entrypoints folder for wxt to find them, and by default it only looks 0 or 1 level deep.
  • Static assets need to be in the public dir in order to get copied over, while the assets folder is instead processed by wxt but only if you import things from there. so I put icons in the public/icon folder and fonts in public/fonts folder instead, which I think makes more sense anyway.
  • No need for a separate package script anymore. wxt has a built in zip script that also creates source files for firefox.
  • wxt can also be used to auto publish extension updates to both chrome and firefox stores if you want at some point: https://wxt.dev/guide/essentials/publishing.html

Caveats and other findings:

  • Firefox’s mv3 doesn’t support dev mode due to a bug in firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1864284. Seems like firefox’s mv3 in general is pretty broken, and I think a lot of the permission issues people have been getting on firefox are related to firefox’s mv3 (e.g. https://www.reddit.com/r/firefox/comments/1326r7e/webextension_mv3_manifestjson_host_permissions/). So because of that I made mv2 work, which was actually relatively simple. Just had to replace chrome calls with browser (imported from wxt), which accounts for the differences like promises, return values, etc of the different manifest versions. Also now have to set an action var to account for different actions (const action = browser.action || browser.browserAction;).

  • Not sure if it’s really necessary to keep separate builds for firefox and firefox-android. I think I was able to handle this use case either way, but seems like the only difference is lack of commands and contextMenu permission. Would it matter to leave these in? Also I wonder if you would be able to use the shortcut keys on android if you plug in a keyboard.

  • Specific thing about the offscreen audio recorder: I changed it to use mp3WorkerFactory since I noticed it was creating a duplicate asset in the output, but haven’t been able to verify that it works since encodeAsMp3 always seems to be false. Haven’t dug into why that is yet though.

  • By default there’s no way to create an arbitrary output structure for unlisted scripts (https://wxt.dev/guide/essentials/entrypoints.html#unlisted-scripts). So all the scripts that were in the pages dir are at the moment just at the top level in the output. Based on what I can see though, it seems like these could just be standalone content scripts with the proper match URLs.

  • The biggest caveat that I’ll need some input on: by default you also can’t build arbitrary esm with wxt. So the first thing I tried for the module scripts was just building them as unlisted scripts, and it works, but doing this slows the initial build to like 30 sec since they are like content scripts with the way they are built. So I tried building the previous module script entry points for the UIs along side an actual html file, instead of injecting them into a template string like in anki-ui for instance. Problem is that, during dev, wxt needs to inject some localhost scripts into the html pages for vite’s hmr to work, and so manually injecting the html the way it is currently done causes bunch of cors issues with localhost that I don’t think are solvable without letting the iframe create it. So I came up with a few possible solutions:

    • The first was just using unlisted scripts as above. It works but the initial build is slow and no hmr in the content script UIs (like anki-ui etc). This is what’s currently in this branch: https://github.com/mwojick/asbplayer/tree/WXT
    • After that I tried an iframe approach using the utility built into wxt https://wxt.dev/guide/essentials/content-scripts.html#iframe, but has the issue with stuff like yomitan not working since the iframe gets loaded with a src. Not sure how important that is. But the good thing about this approach is you get hmr in your content script UIs which is really nice for dev work, and the initial build is faster (about 10 sec). This is in another branch that’s branched off of the wxt one: https://github.com/mwojick/asbplayer/tree/WXT-iframe-ui
    • The third approach I tried was to use this custom wxt module to build esm in content scripts: https://github.com/wxt-dev/examples/blob/main/examples/esm-content-script-ui/modules/esm-builder.ts. This seemed good in theory but I couldn’t get it to work, the build was just as slow, and seemed really fragile and hard to maintain anyway, which kind of defeats the purpose of a framework. I did hear that they are working on providing an option for esm in content scripts by default so maybe this could be an option in the future. But in case you are curious on the approach for this I have a branch here: https://github.com/mwojick/asbplayer/tree/WXT-module.
    • Another route would be to use a shadow root instead of an iframe (https://wxt.dev/guide/essentials/content-scripts.html#shadow-root). This could be a good long term solution since you also wouldn’t have to deal with iframe messaging but for now it’s probably not worth it especially considering how much work it would be. Also other than just styles I’m not sure what other types of isolation might be needed by the iframe. And in terms of performance, if it doesn’t ever support hmr then it would be worse than the iframe approach anyway.
    • Would be nice to have the best of both worlds by building the scripts as unlisted scripts (so that yomitan works) in production mode, and maybe in dev mode have it build with the iframe approach. Maybe that’s too fragile or inconsistent though.
  • In spite of the caveats there are some great dev ex improvements. Like developing in the context of UIs like the popup, settings, or sidebar where you have full hmr so you can see the change to the UI instantly. Even in the context of content scripts, you at least don’t have to wait as long nor manually rebuild/reload the extension. Usually just need to do a page reload if anything. Only exception is when changing a config file which requires a full rebuild. The fact that it also handles the differences between mv2 and mv3 and has an auto publish feature is a huge plus.

@mwojick mwojick marked this pull request as ready for review May 18, 2025 00:16

page_script_relative_path="pages/$page_basename.js"
jq ".web_accessible_resources" $MANIFESTPATH | grep "$page_script_relative_path" >/dev/null
# TODO: manifest is no longer manually created, so will need to check in a different way
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO is not necessary as the page scripts are automatically added to web_accessible_resources

Copy link
Copy Markdown
Owner

@killergerbah killergerbah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm 99% done developing a tutorial using your branch - the hotswap was a life saver.
Just want to test a bit more before merging.

@killergerbah killergerbah merged commit 95c1190 into killergerbah:main May 25, 2025
1 check passed
@killergerbah
Copy link
Copy Markdown
Owner

@mwojick Thank you so much for this invaluable change. You've saved future developers many many hours of iteration time on this project.

@killergerbah killergerbah added this to the Extension v1.11.0 milestone Jul 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants