rollup / rollup

Next-generation ES module bundler

Home Page:https://rollupjs.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Consider UMD / IIFE support for code splitting

guybedford opened this issue · comments

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.

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.

Strongly agree!

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.

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).

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.

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.

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

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.

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 💯

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.

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

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

commented

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

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.

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 :-/

@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.

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

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.

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.

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

@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.

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.

commented

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.

@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.

@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