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

[Web Extension Transformer] doesn't support code splitting #5859

Closed
fregante opened this issue Feb 17, 2021 · 14 comments
Closed

[Web Extension Transformer] doesn't support code splitting #5859

fregante opened this issue Feb 17, 2021 · 14 comments

Comments

@fregante
Copy link
Contributor

fregante commented Feb 17, 2021

🙋 feature request

Code splitting via import() creates a script tag and injects it (Parcel source) but this doesn't work for "content scripts" in extensions because such script tags are not run in the extension’s isolated world. Injecting a script tag is actually a way to explicitly escape the isolated world

😯 Current Behavior

The code-splitted file doesn't have access to the chrome.* APIs like the rest of the entry.js file does because it's injected via script tag directly in the page rather than being loaded as a content script.

💁 Possible Solution

I'm not aware of a way to load script from a content script in the same world (only non-content scripts pages can use the chrome.tabs.executeScript API to do that) so I think the only solution would be to:

  1. Automatically add the secondary file to dist/manifest.json, before the entry, e.g.
      "content_scripts": [{
      	"scripts": [
    + 		"vendor.d186d19.js",
      		"entry.js"
      	]
      }]
  2. Have parcel’s loader execute its code when import() is called.

🔦 Context

I'm considering content-splitting to avoid bundling the same dependency over and over. In this PR the files are all 30KB+ because they all bundle their own version of a library:

distribution/background.js                                                     33.61 KB    199ms
distribution/options.a7e143c4.js                                               34.95 KB    689ms

but with import() I'd get this:

distribution/background.js                                                      8.57 KB    189ms
distribution/webext-options-sync.a0fe799f.js                                   35.79 KB    812ms
distribution/options.312654a3.js                                                9.92 KB    811ms
@fregante
Copy link
Contributor Author

Because of the sync nature of the suggested solution, I wish there was a way to require code-splitted files synchronously (a-la-require), but I suppose that's not going to happen.

@mischnic
Copy link
Member

mischnic commented Feb 17, 2021

Well there are two ways to achieve codesplitting:

  • import() to create async child bundles
  • shared bundles

The problem with shared bundles is #4227 (for the case of simply running parcel build index.js), but in this case there is already a manifest, so it might be possible to implement this.

@fregante
Copy link
Contributor Author

import() to create async child bundles

Yeah that’d be ideal, but:

shared bundles

Yes 🙌, I think that’s what I suggested above.

@mischnic
Copy link
Member

The main thing the prevents shared bundles is isEntry: true. Without that, there should be shared bundles. A webextension packager would be needed to then prepend the shared bundles into the content_scripts array (similar to how the HTML packager inserts <script> tags for shared bundles).

But I think there was some problem if it's set to false.

@101arrowz
Copy link
Member

The problem was the removal of the prelude from all descendants of the manifest when another descendant had received the prelude. Normally this is fine because they all share the same global environment, but in this case that doesn't happen. If that can be circumvented, isEntry: true is not necessary.

@mischnic
Copy link
Member

A webextension packager would be needed to then prepend the shared bundles into the content_scripts array (similar to how the HTML packager inserts <script> tags for shared bundles).

(That would also be needed for #5865)

@fregante

This comment has been minimized.

@fregante
Copy link
Contributor Author

I have news that would make this much easier to implement: almost-raw import() calls are now supported in Firefox 89 and they work in every browser including Safari, in content scripts and in background pages 🎉

https://bugzilla.mozilla.org/show_bug.cgi?id=1536094

import(chrome.runtime.getURL('file.js')).then(mod => {console.log(mod.default)})
// manifest.json
{
  "web_accessible_resources": ["file.js"]
}

This works in Parcel today because the dynamic import call is ignored while web_accessible_resources pushes file.js into Parcel’s graph.


What's next for full code-spltting support:

  • when encountering import('left-pad') calls, code-split as usual BUT preserve the actual import() call instead of using its own loading method; this is useful to every Parcel user (is there already an issue for this?)
  • automatically wrap import’s path in chrome.runtime.getURL() + add the resulting bundle to the manifest

Example input

// entry-a.js
import('left-pad').then(alert)
// entry-b.js
import('left-pad').then(console.log)

Desired output

// entry-a.js
import(chrome.runtime.getURL('left-pad.8f8a6d.js')).then(alert)
// entry-b.js
import(chrome.runtime.getURL('left-pad.8f8a6d.js')).then(console.log)
// left-pad.8f8a6d.js
export default (str, n) => str.padStart(n)
// manifest.json
{
  "web_accessible_resources": ["left-pad.8f8a6d.js"]
}

@101arrowz
Copy link
Member

I think #7050 unintentionally fixed this but someone should verify.

@101arrowz
Copy link
Member

Yep this should be fixed in the latest versions but if anyone uses dynamic import more extensively, please test it out and report back here.

@SpaceK33z
Copy link
Contributor

SpaceK33z commented Apr 3, 2023

import(chrome.runtime.getURL('file.js')).then(mod => {console.log(mod.default)})

I'm trying to do what @fregante is doing, but it's failing because Parcel always seems to rewrite import() to require(). Is there any way to tell Parcel to not rewrite import to require?

@fregante
Copy link
Contributor Author

fregante commented Apr 3, 2023

If that's the case, it means this was not fixed, as my last comment specifically suggested that import() should not be converted to require(). I haven't worked on Parcel extensions much since this PR was closed so I did not check.

Can we reopen this issue? @101arrowz

@Curve
Copy link

Curve commented Jul 25, 2023

import(chrome.runtime.getURL('file.js')).then(mod => {console.log(mod.default)})

I'm trying to do what @fregante is doing, but it's failing because Parcel always seems to rewrite import() to require(). Is there any way to tell Parcel to not rewrite import to require?

I'm experiencing the exact same issue, is there some way to make parcel not rewrite the import? Something like webpacks /*webpackIgnore: true*/ would be useful here.

@fregante
Copy link
Contributor Author

It's probably best to create a new issue with updated repro.

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

No branches or pull requests

5 participants