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

Consider UMD / IIFE support for code splitting #2072

Open
guybedford opened this issue Mar 20, 2018 · 26 comments
Open

Consider UMD / IIFE support for code splitting #2072

guybedford opened this issue Mar 20, 2018 · 26 comments

Comments

@guybedford
Copy link
Contributor

To continue to discussion here, in cases where code splitting:

  1. Uses a small number of chunks (say just two layers at most)
  2. Does not use dynamic import at all

it could be beneficial to have UMD / IIFE support for code splitting.

Chunk global names can be based on the chunk name, and similarly for entry points. The tough part then is just ensuring the script order, but this information is available as the output of both generate and write.

@lukastaegert
Copy link
Member

Actually I think this could be a great improvement for the surprisingly common use-case of creating a vendor-bundle. Right now the overhead (i.e. adding a runtime) can be quite intimidating to just extract some dependencies from your code.

@tivac
Copy link
Contributor

tivac commented Jul 12, 2018

Strongly agree!

@theseanl
Copy link

theseanl commented Jul 13, 2018

As far as rendering is concerned, currently FinaliserOptions interface does not carry information of a name of a chunk that is being rendered, which is required by the OP's scheme, and we can extend the interface like { chunkName: this.name }.

If we'd like rollup to provide an ergonomic API for sorting modules, we may take a look at what Closure Compiler's code splitting does - instead of creating anonymous chunks, Closure Compiler requires consumers to provide a dependency graph (actually a tree) of output chunks by --module input flags.

@guybedford
Copy link
Contributor Author

The more I think of this the more it makes me uneasy actually.

We should not be encouraging users to manually inject script tags, or named module bundling that will not easily work, and require lots of manual intervention. The result will be feature requests for things that are unsuitable for code splitting in Rollup. Named bundles are a proven mistake (and speaking as the worst culprit here).

@lukastaegert
Copy link
Member

True, this could be opening a can of worms. Right now though, at least for legacy browsers, users have to manually inject an amd or systemjs runtime which is also some non-trivial overhead that also affects performance, albeit slightly. In a situation where there are no dynamic imports, it is hard to understand why this should be necessary.

Enabling IIFE in some way for non-dynamic code-splitting could provide a solution that is true to rollup's spirit of minimal overhead.

@lukastaegert
Copy link
Member

Going one-step further considering the question of naming, rollup could by default take care of the naming automatically i.e. rollup makes sure the chunks can access each other. In case a name is provided, we could throw an error if more than one input is given.

@lukastaegert
Copy link
Member

As a benefit for the user, the CLI could print out the correct execution order of the chunks in these situations.

@theseanl
Copy link

theseanl commented Jul 13, 2018

In case a name is provided, we could throw an error if more than one input is given.

At a glance, it seems to be possible to use name to expose chunks' export object to a global scope. For instance, if name was "a.b.c", we may render a chunk's iife form as

this.a.b.c['chunk'] = (function(exports, exportedFromChunk2) {
   ...
})({}, this.a.b.c['chunk2'].exportedFromChunk2);

then, if chunks are included via script tags in a correct order, they will be able to find each other's exports. 'chunk', 'chunk2' can be derived from ModuleDeclarationDependency.id which I suppose is already sanitized to be used as a JS identifier.

As for a sprit of unifying code path for single bundle output and code splitting, if there is no chunks we may restore the current behavior of name flag.

Named bundles are a proven mistake (and speaking as the worst culprit here).

@guybedford Would you mind providing some references?

IMO Closure Compiler supporting code splitting via named chunks demonstrates that it is a viable solution, and personally I'm happy with it. Although if rollup could provide a better alternative, it would be nice.

@tivac
Copy link
Contributor

tivac commented Jul 19, 2018

I agree that this could get bad, but I think overall it would be hugely valuable. I don't want to pay the runtime cost of any loading system given our perf needs, I can easily add a few <script> tags in the right order though!

As a benefit for the user, the CLI could print out the correct execution order of the chunks in these situations.

👍 x 💯

@tivac
Copy link
Contributor

tivac commented Jul 23, 2018

I needed this sooner rather than later, so worked around it missing by using amd and wrote a tiny AMD implementation that expects <script> tags that load anonymous AMD modules (since most implementations don't support them).

https://www.npmjs.com/package/amd-script

Not in production yet, but working for our simple use-case so far and way faster to get to "showing UI" than depending on a solution that injects <script> or uses XHR.

@guybedford
Copy link
Contributor Author

Awesome work @tivac. Look forward to the blog post :)

@guybedford
Copy link
Contributor Author

Also, if you wanted to include this approach in a section on the code splitting docs, feel free to PR that as well.

@guybedford
Copy link
Contributor Author

@tivac missed your chance on that blog post - https://twitter.com/DasSurma/status/1027165714648584192 :P

@eight04
Copy link

eight04 commented Aug 28, 2018

When building browser extensions, we have to specify script order inside the manifest or inject them manually with tabs.executeScript. I originally solved the problem by building each dependency individually and marking them as externals (#2374) but you have to make sure there is no variable name conflict since all scripts injected through tabs.executeScript share the same scope.

I ended up making a plugin to convert ES module output into IIFEs:
https://github.com/eight04/rollup-plugin-iife

@shellscape
Copy link
Contributor

Hey folks. This is a saved-form message, but rest assured we mean every word. The Rollup team is attempting to clean up the Issues backlog in the hopes that the active and still-needed, still-relevant issues bubble up to the surface. With that, we're closing issues that have been open for an eon or two, and have gone stale like pirate hard-tack without activity.

We really appreciate the folks have taken the time to open and comment on this issue. Please don't confuse this closure with us not caring or dismissing your issue, feature request, discussion, or report. The issue will still be here, just in a closed state. If the issue pertains to a bug, please re-test for the bug on the latest version of Rollup and if present, please tag @shellscape and request a re-open, and we'll be happy to oblige.

@sbrl
Copy link

sbrl commented Aug 9, 2019

Hey, @shellscape! This feature, or at least the ability to disable code-splitting, would be extremely helpful. As it stands, I'm having to work around it by maintaining multiple Rollup configurations and running Rollup multiple times.

Example use-case: I have a browser-based application that uses web workers. I need 1 entry point for the main application, and another for the web worker. Because browsers don't yet support new Worker("example.mjs", { type: "module" }); as far as I know, I can't use code-splitting as it breaks the web worker - hence the workaround I've described above.

Please don't close this issue :-/

@shellscape
Copy link
Contributor

@sbrl this issue hasn't gotten any love from the userbase in well over a year. I'm good with re-opening, but we need someone to lead the effort on that. If you're willing to lead the effort on getting this implemented (or if anyone would like to step up as well) then I'm happy to reopen.

@lukastaegert
Copy link
Member

Also for the issue you mention, @sbrl, I would rather work on making AMD or SystemJS work better for code-splitting builds. It is already possible today if you manually do some modifications to the wrapper code in renderChunk of the entry chunk. This test from the Rollup code-base details the process and is actually runnable if you have a server that serves all relevant files (or your tweak the paths a little): https://github.com/rollup/rollup/tree/master/test/chunking-form/samples/emit-file/emit-chunk-worker

@theseanl
Copy link

theseanl commented Aug 9, 2019

Since my comment above, I implemented the named module bundling for IIFE bundles which was discussed here - check out rollup-plugin-tscc. Basically, you provide bundle names to build, an entry file for each bundles, and specify dependency information among them. Then the plugin will do some magic to merge anonymous chunks to an appropriate bundle. I think it will be helpful for situations like chrome extensions, etc.

This was originally released as a part of tscc build tool, which bundles typescript codes with closure compiler, but the rollup-plugin-tscc plugin alone only provides options and merges output chunks, so it should work for js-only projects too.

@sbrl
Copy link

sbrl commented Aug 9, 2019

Ah, I hadn't even thought about looking into AMD / SystemJS, @lukastaegert. That sounds like a better option if they load chunks. Not sure how that would be implemented in a web worker as I've no experience there, but if it works then that would be awesome!

My main problem is that I need to split my code in the web worker over multiple files (and import a few npm modules), and I found that the existing web worker plugins either choked or just didn't work at all on it.

@theseanl that sounds interesting. I'll have to look into it.

@ChristianMurphy
Copy link

A workaround that may work for some babel+rollup users in the meantime, a babel plugin that can translate some dynamic imports to static imports to allow rollup to generate a UMD/IIFE build.
https://www.npmjs.com/package/babel-plugin-transform-dynamic-imports-to-static-imports

@derrickb
Copy link

derrickb commented Jul 9, 2021

@lukastaegert can this please be reopened and reconsidered? I just want to be able to output a vendor js file that I can add a script tag for. At least allow us to have the feature and use it that way, if it's down to a philosophical argument.

I want to import vendor modules, have the benefit of treeshaking and plugins work on those, and then split them out to their own bundled js file, ready to be used in the browser next to my app js file.

@lukastaegert
Copy link
Member

I can reopen this, but it would still need someone to work on this. This would be a bigger task, and there are things to figure out first:

  • as we are not adding a runtime loader, the chunks will need to be loaded in a fixed order by the user. How is this order configured, and what about auto-generated shared chunks? Alternatively, we could fail the build if the user does not specify an order between their entry chunks via something that controls the implicitlyLoadedBefore aspect of chunks (see https://rollupjs.org/guide/en/#thisemitfileemittedfile-emittedchunk--emittedasset--string; if there is an up-front order between chunks, there should be no additional auto-generated chunks as Rollup will put shared modules into the chunk that is loaded first)
  • also, preserveModules could be problematic and should probably not be used. Cycles between chunks need to fail the build.
  • as we do not have a loader, we cannot handle dynamic imports correctly and should probably fail the build if they are used?
  • how do we control global names for internal chunks? Should they be configured as well by the user? If not, do we want to scan for global variable conflicts with the user code?
  • if so, how are we working with preserveEntrySignatures? This relies on generating additional chunks so that there are no additional imports on entry chunks.
  • Ideally, we should do this for IIFE output first/as well

And there is probably still quite a bit of stuff missing.

@lukastaegert lukastaegert reopened this Jul 12, 2021
@eight04
Copy link

eight04 commented Jul 12, 2021

Just want to share what I do after generating IIFE modules using rollup-plugin-iife. I wrote a plugin that collects dependencies of each entries and inject them into different targets (html script tags, manifest.json, worker entry, etc). Therefore, multiple entries can share the same chunk, that will be loaded into multiple contexts.

Live examples:
Web worker (with rollup-plugin-comlink)
https://github.com/eight04/mgcm-skill-data/blob/84d272e418eef546d0f9ba6c61b005d90278a84b/rollup.config.js#L59-L74

Browser extension
https://github.com/eight04/image-picka/blob/577e415215e300c344389f00a76c77ab55b944b6/rollup.config.js#L55-L79

My app doesn't use dynamic imports. Tough I think it should be configurable if users are able to provide a loader function (from a module? or a global function?) userDefinedImport(importer, importee, importeeDependencies, globalVariableName) -> Object so rollup can rewrite import() to userDefinedImport().

Cycles should be fine like ESM, as long as there is no cycle on critical path.

@derrickb
Copy link

@lukastaegert thanks Lukas. I don't have all the answers to the questions you raised as I don't use dynamic chunks, modules, etc. in my bundled app... it all gets transpiled down to something that runs in very old WebKit, equivalent to Safari 4.

How is this order configured, and what about auto-generated shared chunks?

I assume the order is on me, the user, to figure out or configure. I really don't mind editing some html script tags to get things working, I don't expect everything to be 100% automatic. In my case the order is:

<script src="vendor.js"></script>
<script src="app.js"></script>

how do we control global names for internal chunks

I'm not sure what internal chunks are?

And there is probably still quite a bit of stuff missing.

I definitely respect the fact that there are likely a lot of scenarios and edge cases you're considering and I appreciate you giving it some thought. My hope is you'll allow an implementation that, like you said, works with IIFE mode and maybe has the required checks to result in build error for unsupported scenarios.

@rhengles
Copy link

rhengles commented May 29, 2023

@lukastaegert commented on Jul 12, 2021
as we are not adding a runtime loader

Could I write a plugin that did add a runtime loader, and made umd/iife code-splitting work ?

Edit: Nvm, it seems this approach described in emit-chunk-worker (#2072 (comment)) may be enough

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

No branches or pull requests

10 participants