New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Content Script ESM Support #357
Comments
This feature is really nice, I am exploring how to implement HMR in content script like CRXJS, it is really a magic. |
I've switch my proposal to use @yunsii I'd like to structure the outputs like this:
At a minimum, the loader would look something like this: import(
/* @vite-ignore */
browser.runtime.getURL("/content-scripts/<name>.js")
); I'm also not sure where we need to call the const { default } = await import(
/* @vite-ignore */
browser.runtime.getURL("/content-scripts/<name>.js")
);
default.main(...); If I remember correctly, dynamically imported modules don't have access to the |
@aklinker1 Both examples seems ok, here is my demo https://github.com/yunsii/chrome-extension-raw-demo/blob/master/src/js/isolated_content_script.js |
Cool, thanks for researching this! If both work, we'll go with whatever option makes the most sense during the implementation. |
What's the meaning? I do not understand exactly. |
@yunsii Between the two options (running |
Also, for future reference, here are the minimum requirements to get ESM content scripts working:
Here's a minimal example with an ESM service worker and content script sharing the ES module utility. |
So it means that |
You can use all three |
Yup, exactly! |
I made a quick Vite project to spike out what's required to build an ESM chrome extension. Here it is: minimal-vite-esm-extension.zip Really all you have to do is add the loader to the bundle in a custom plugin during the // vite.config.ts
import { Plugin } from "vite";
import { defineConfig } from "vite";
const esmContentScriptLoader = (): Plugin => ({
name: "esm-content-script-loader",
generateBundle(_options, bundle, _isWrite) {
// Add the loader to the bundle before the bundle is written to the disk
bundle["content-script-loader.js"] = {
type: "asset",
fileName: "content-script-loader.js",
name: "content-script-loader",
needsCodeReference: false,
source: `(async () => {
console.log("Importing 'content-script'...")
await import(
/* vite-ignore */
chrome.runtime.getURL('/content-script.js')
)
console.log("Imported 'content-script'!")
})()
`,
};
},
});
export default defineConfig({
build: {
rollupOptions: {
input: {
popup: "src/popup.html",
"content-script": "src/content-script.ts",
background: "src/background.ts",
},
output: {
format: "esm",
entryFileNames: "[name].js",
},
},
// Not necessary, just for clearity when looking at output files
minify: false,
},
plugins: [esmContentScriptLoader()],
}); Otherwise vite pretty much builds everything else correctly. This doesn't include a working dev mode, just the build. There's a lot of complex pre-rendering that needs to happen for dev mode to work, and that's all setup in WXT, so it makes sense to implement it in WXT, then test dev mode. |
I've prioritized #57 over this issue, so I still haven't done any additional work on this yet. |
I haven't had any more time to spend on this the last 3 weeks, I've been tackling the smaller bugs people have reported recently. But don't worry, this is at the top of my priorities when I have a free weekend to focus on it. |
Update I tried setting up dev mode with the dynamic import loaders, but ran into a problem: CSS from the page is always applied to the page, and there's no way to change that. So basically, Other than that, I'm gonna keep going forward, and maybe I'll find a workaround, but just wanted to leave an update here. I haven't attempted to add HMR yet, but have a good idea about how I'd go about it. |
What's the meaning? Shadow dom CSS from |
The way Vite deals with CSS in dev mode, it reads and transforms a file, then either adds or removes a WXT, on the other hand, does a full build for each content script so we have a CSS file that can be loaded into the extension. However, moving to esm, there is no CSS file exists and we have to rely on vite's method of adding style blocks to the page as modules change. Shadow roots, however, need the style injected inside the shadow root. Vite doesn't provide a way to change where the esm |
https://github.com/vitejs/vite/blob/main/packages%2Fvite%2Fsrc%2Fclient%2Fclient.ts#L418 Here's where Vite appends the style to |
Is there any approach to override the function? |
Feature Request
As discussed in #335, it is possible to load ESM content scripts using a dynamic import. The downside is that since it's async, the standard
run_at
option has basically no effect.I propose adding a new option to
defineContentScript
:type: "module"
. Similar to the background'stype: "module"
option.When WXT sees an async content script, it will load the script asynchronously using a dynamic import.
Questions:
Does therun_at
in the manifest make a difference in loading speed when using a dynamic importCan async content scripts be bundled in one step alongside HTML entrypoints, or do they have to be separated into their own step? My concern here is mixing chunks with side-effects that only work in HTML pagesShouldtype: "module"
be the default value? It works well with the defaultrun_at: "document_idle"
, and will likely provide a much better dev experience.Is your feature request related to a bug?
#335
What are the alternatives?
No real alternatives to the feature as a whole. Instead of adding a new field, we could use
runAt: "async"
, but that wouldn't provide a way to set the actualrun_at
in the manifest. That said, therun_at
doesn't really matter, it can cause the browser to import the script earlier, but the code will never run before theDOMContentLoaded
event.Additional context
CC @yunsii
This will fix: #270
The text was updated successfully, but these errors were encountered: