Skip to content
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

Support TRUE dynamic import? #335

Closed
yunsii opened this issue Jan 9, 2024 · 9 comments
Closed

Support TRUE dynamic import? #335

yunsii opened this issue Jan 9, 2024 · 9 comments
Labels

Comments

@yunsii
Copy link
Contributor

yunsii commented Jan 9, 2024

Feature Request

I noticed that wxt bundle each single js file for each entrypoint currently, and if I dynamic import a module, for instance const module = await import("axios"), it still be bundled in the single js and just like I do not dynamic import it.

Is your feature request related to a bug?

It maybe encounter error if the imported module has iife with some specific logic.

What are the alternatives?

  • Fork third package, make a change and release it
  • Or patch package
@yunsii yunsii added the feature label Jan 9, 2024
@aklinker1
Copy link
Collaborator

It might be possible to support true dynamic imports. Any chunks that are generated during the build could be added to web_accessible_resouces, but the import URL would have to be transformed to a full URL instead of a relative one, and that URL would have to know the extension's ID ahead of time.

Instead, right now, I think you can inline dynamic imports. This puts the imported module's code inline, instead of hoisting it to the top of the bundled file, accomplishing the same thing, the module not loading until where it was originally imported.

export default defineConfig({
  vite: () => ({
    rollupOptions: {
      output: {
        inlineDynamicImports: true,
      },
    },
  }),
})

I haven't tested this before, but it probably works?

@yunsii
Copy link
Contributor Author

yunsii commented Jan 10, 2024

that URL would have to know the extension's ID ahead of time.

Not exactly, we migrate from @crxjs/vite-plugin to wxt, and this is a content script entry js in @crxjs/vite-plugin:

(function () {
  'use strict';

  const injectTime = performance.now();
  (async () => {
    const { onExecute } = await import(
      /* @vite-ignore */
      chrome.runtime.getURL("assets/chunk-add03e4e.js")
    );
    onExecute?.({ perf: { injectTime, loadTime: performance.now() - injectTime } });
  })().catch(console.error);

})();

So dynamic import can use this approach? Furthermore, @crxjs/vite-plugin almost bundle modules with dynamic import, it can be maximize reuse code, and generate package size 22,598 KB after zipped. After migrate to wxt, generate package size 29,121 KB after zipped. Is the package size can be optimized?

@yunsii
Copy link
Contributor Author

yunsii commented Jan 10, 2024

Unfortunately, this way does not work as expected.

export default defineConfig({
  vite: () => ({
    build: {
      rollupOptions: {
        output: {
          inlineDynamicImports: true,
        },
      },
    }
  }),
})

@aklinker1
Copy link
Collaborator

@yunsii Can you provide a small @crxjs/vite-plugin that includes chunks like this so I can look into it more?

In the past, using dynamic imports to load chunks like this for content scripts has been a challenge technically and comes with several caveats.

#191 (comment)

But if it's possible to do it with none of the downsides, I would like to do it, and a sample project would be very helpful.

@yunsii
Copy link
Contributor Author

yunsii commented Jan 11, 2024

@aklinker1
Copy link
Collaborator

aklinker1 commented Jan 11, 2024

@yunsii Thanks for sharing! For future reference for anyone else who reads this (and myself lol), here's the relevant files:

// ...
setTimeout(async () => {
    const axios = await import("axios");
    console.log({ axios });
}, 1000);

and the relevant output:

// dist/assets/index.tsx-loader.ddfff801.js
// ...
(function () {
  'use strict';

  const injectTime = performance.now();
  (async () => {
    const { onExecute } = await import(
      /* @vite-ignore */
      chrome.runtime.getURL("assets/index.tsx.2d68af1a.js")
    );
    onExecute?.({ perf: { injectTime, loadTime: performance.now() - injectTime } });
  })().catch(console.error);

})();
// dist/assets/index.tsx.2d68af1a.js
// ...
setTimeout(async () => {
  const e = await y(() => import("./index.1f5d4f90.js"), []);
  console.log({ axios: e });
}, 1e3);

And Chrome was able to resolve import("./index.1f5d4f90.js") using a relative path correctly. Since this was run on google.com/search, I would have expected the browser to look at google.com/search/index.1f5d4f90.js, but Chrome recognized that it was inside and extension and resolved it from chrome-extension://{id}/assets/index.1f5d4f90.js. Cool!

Screenshot 2024-01-10 at 8 59 19 PM

I also tested to see if modules could access the extension APIs, and they can, even if loaded by a dynamic import.

Screenshot 2024-01-10 at 9 03 24 PM

That said, there are one things holding me back from implementing this:

CRXJS's "loader" setup ignores the run_at of a content script because the dynamic import function is async. So if you set run_at: "document_start", your code won't actually run until sometime in the future after the DOM has loaded, which is not when "document_start" is supposed to run at.

So we can't use the same approach as CRXJS with a loader file if we want to support run_at. Instead, WXT would have to bundle the content script like normal, but keep dynamic imports, and wrap all their URLs in browser.runtime.getURL, similar to the loader file above.

AFAIK, this is not possible with Vite. So I'm not going support chunks in content scripts with Vite. If someone can produce this type of output with a simple Vite build and provide an example of they did it, I would gladly implement this! But right now, I have no idea how to do that, so it's not gonna happen.

Unfortunately, this way does not work as expected.

Interesting. I guess there's a limitation to Rollup's inlineDynamicImports that causes them to not actually be inlined: rollup/rollup#4735

That sucks :/


TLDR: no, WXT will not be supporting dynamic imports in JS entrypoints :(

And unfortunately, I don't have a solution for you without more details about your use-case/setup.

@aklinker1 aklinker1 closed this as not planned Won't fix, can't repro, duplicate, stale Jan 11, 2024
@yunsii
Copy link
Contributor Author

yunsii commented Jan 11, 2024

By the way, I found WXT bundle style caused enableMapSet() not work as expected, immer throw error that I should enableMapSet() before in my real world extension recently. But the minimal reproduction is okey.

I have to refactor my code from Map to plain Object state alternatively.

But I can not find out the reason, maybe complex dependency tree in my real world extension caused the error? I will follow up again if I have a chance.

@yunsii
Copy link
Contributor Author

yunsii commented Jan 16, 2024

@aklinker1 I find that if a entrypoint is too big, rebuild is too slow in my recent experience.

Refer to #191 (comment) How about to make a not run_at document_start entrypoint dynamic import modules, or at least trial in DEV env? If so, is it possible to only rebuild specific changed file and reload page as soon as possible?

@aklinker1
Copy link
Collaborator

aklinker1 commented Jan 18, 2024

I could get behind this. Something like adding a 4th runAt option, maybe "async", that uses chunks and a dynamic import like the examples from above. All the content scripts that have runAt: "async" could then be bundled together, sharing chunks and decreasing the overall bundle size.

I could use this for one of my extensions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants