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

UMD is not compatible with JavaScript modules #124

Closed
cdata opened this issue Sep 5, 2017 · 12 comments · Fixed by #125
Closed

UMD is not compatible with JavaScript modules #124

cdata opened this issue Sep 5, 2017 · 12 comments · Fixed by #125

Comments

@cdata
Copy link
Contributor

cdata commented Sep 5, 2017

Description

When using UMD in a script that is evaluated as a JavaScript module (tested in Chrome Canary and Safari stable), the reference to the global object (presumably window) is incorrectly detected.

Test case

Please refer to this test case for a demonstration of the problem: http://jsbin.com/yuvulec/1/edit?html

Analysis

JavaScript modules are always evaluated as JavaScript strict mode code [1]. In strict mode, an undefined or null this is no longer coerced to the global object [2].

What I expected

The following output in the console:

Everything is great!

What actually happened

Uncaught TypeError: Cannot read property 'b' of undefined
@cdata cdata changed the title UMD is not compatible with ES modules UMD is not compatible with JavaScript modules Sep 5, 2017
@ryanflorence
Copy link

Hmm, help me understand. Why would you define with UMD when you're using an ES module? Only other ES modules can consume it, so what's the point?

@cdata
Copy link
Contributor Author

cdata commented Sep 5, 2017

The intention is to be able to import a UMD module from a JavaScript module and have it export to the global object. For example, in a browser script module context:

import './umd-foo.js'; // exports Foo via UMD to window

Currently it is not possible to attempt importing UMD-wrapped scripts as they are guaranteed to error when detecting the global context. Many of these scripts would be importable as JavaScript modules with the correct detection.

@addyosmani
Copy link
Member

addyosmani commented Sep 5, 2017

I have similar questions around why. It's a little unclear when UMD would be useful or needed here if you're primarily using ESMs. Edit: appears our comments just crossed each other :) Will review.

@cdata
Copy link
Contributor Author

cdata commented Sep 5, 2017

Please see my response above.

Mainly this helps with using "legacy" script modules that leverage UMD in conjunction with JavaScript modules. For example, I recently attempted to import the Firebase JS SDK (which is distributed as UMD modules) from a JavaScript module. It would have worked and correctly populated the global object with the necessary properties, except it performed incorrect global detection.

It is important to import dependencies where you need them (instead of globally) to support correct analysis of the dependency graph in one's app. It is also useful to be able to import "legacy" UMD modules from JavaScript modules to facilitate a general ecosystem transition towards JavaScript modules.

@jrburke
Copy link
Member

jrburke commented Sep 5, 2017

Something like the loader spec needs to be finished to allow this sort of interop.

My hope with the loader spec is that it would allow you to then create a wrapping script over it that would allow you to configure it to not load these scripts in a script type="module" and then the wrapping script would implement enough of either AMD or CJS so that when the UMD script calls one of those APIs, it registers the module with the ES module registry. Or just rely on the globals since the loader did not load the script in a type="module" environment.

But who knows how it will work. The loader spec seems to be stalled.

In any case, I do not think it is advisable to load UMD scripts as type="module" scripts because of the particular environmental constraints around type="module" scripts. UMD scripts are not type="module" scripts, they are type="text/javascript" scripts.

@cdata
Copy link
Contributor Author

cdata commented Sep 5, 2017

Thanks for the feedback @jrburke.

UMD's export to global has historically served as an affordance for web browsers, for scenarios where no supported module system has been detected but export to globals is considered a reasonable fallback by the library author.

In practice, many libraries distribute a build artifact wrapped in UMD with global fallback in support of loading in a browser context. I referenced the Firebase SDK above, but I have also encountered UMD wrappers around the distributable form of other libraries such as jQuery and Redux (and certainly many other examples exist). Notably, in the jQuery case the wrapper explicitly performs a test for the availability of a global reference (window), and this deviation enables it to do the expected thing (and avoid an error) when imported. Redux, on the other hand, throws an exception because global is undefined.

Given that the change required to make UMD "just work" as script modules is potentially quite small, it seems like a compelling trade-off of correctness for utility in support of transitioning our ecosystem towards JavaScript modules.

Something like the loader spec needs to be finished to allow this sort of interop.

I would not characterize this issue as advocating for interop. Instead, it should be read as requesting reasonable fallback for the browser JavaScript module case - very similar to the existing fallback that exports to global - but that supports the case where the wrapper is evaluated in strict mode.

That having been stated, I don't otherwise understand why the loader spec is necessary to address the described issue.

In any case, I do not think it is advisable to load UMD scripts as type="module" scripts because of the particular environmental constraints around type="module" scripts. UMD scripts are not type="module" scripts, they are type="text/javascript" scripts.

Philosophically speaking, UMD is an adaptable and/or progressive wrapper that leverages the available module system (or falls back where possible). It seems fitting that the "universal" in UMD extend to being imported as JavaScript modules where it is feasible.

@cdata
Copy link
Contributor Author

cdata commented Sep 5, 2017

I would like to emphasize one detail because I think the problem statement in the issue may have been ambiguous (my apologies if this is the case):

The issue does ask that UMD gracefully fallback when loaded in a browser and evaluated as strict mode code (such as when imported as a JavaScript module).

The issue does not ask UMD to export a JavaScript module (or otherwise interop with the native module system) when loaded as a JavaScript module.

@addyosmani
Copy link
Member

But who knows how it will work. The loader spec seems to be stalled.

This is my interpretation of the current state of the loader spec as well. It appears parked.

The issue does ask that UMD gracefully fallback when loaded in a browser and evaluated as strict mode code (such as when imported as a JavaScript module).

Given that the change required to make UMD "just work" as script modules is potentially quite small, it seems like a compelling trade-off of correctness for utility in support of transitioning our ecosystem towards JavaScript modules.

Thanks for the clarification. One thing that might be useful for the discussion here is a patch demonstrating the proposed change you would like made. The OP noted that the issue when evaluating UMD in script is the global gets incorrectly detected - is the change just correct detection?

@cdata
Copy link
Contributor Author

cdata commented Sep 5, 2017

One thing that might be useful for the discussion here is a patch demonstrating the proposed change you would like made.

Yes, great point. I am happy to create one.

is the change just correct detection?

Yes, the proposed change would be correct detection of the global object in a strict mode context.

@jrburke
Copy link
Member

jrburke commented Sep 5, 2017

Loading a UMD script as type="module" means effectively adding a "use strict" to the top of the module. However, the module may not have been authored to strict mode rules.

So while you may come up for a better detection for the global in this case, it does not mean that all UMD modules will run as expected. I definitely ran into this with an AMD module optimizer: combining a bunch of modules and naively prepending "use strict" at the top of the bundled script would break some of the concatenated modules in some cases.

Unless the UMD module was explicitly authored with "use strict" at the top of the file, before the UMD wrapper, the forcing of strict mode is a change in expectations, a breaking of the contract the UMD script was assuming.

I will leave it to the active maintainers in this repo to sort out any possible changes for this repo, and I will try not to post on this issue any more. Maybe a "UMD for strict mode" example makes sense. But just be forewarned that loading existing non-strict mode UMD modules as type="module" with "use strict" behavior can lead to errors, regardless of the globals detection change.

@cdata
Copy link
Contributor Author

cdata commented Sep 5, 2017

But just be forewarned that loading existing non-strict mode UMD modules as type="module" with "use strict" behavior can lead to errors, regardless of the globals detection change.

Thanks @jrburke , this is a very important point. I completely agree that one cannot reasonably expect a module crafted without the deliberate intention of running in strict mode to support running in strict mode.

On the other hand, many modules today will work out of the box (indeed many are already authored with explicit support for strict mode).

@justinfagnani
Copy link

Even though UMD wasn't designed to be loaded from modules, it seems to be awful close to being useful in that context, even if it's still just writing to globals. The main benefit is that, like Chris said, modules can load their UMD dependencies directly and not have to do something like document that the app include them in some side bundle, and then of course you get the module loader's deduping.

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

Successfully merging a pull request may close this issue.

5 participants