Skip to content
This repository has been archived by the owner on Dec 31, 2022. It is now read-only.

AMD? #39

Closed
FagnerMartinsBrack opened this issue Nov 21, 2014 · 19 comments
Closed

AMD? #39

FagnerMartinsBrack opened this issue Nov 21, 2014 · 19 comments

Comments

@FagnerMartinsBrack
Copy link

Hi, when using this polyfill with requireJS in PhantomJS environment I am getting:

Error: Mismatched anonymous define() module: function (){return b[a]}
http://requirejs.org/docs/errors.html#mismatch

The expected here is that Promise is available globally, just like the native behavior and no error occurs.

Any reason for using AMD in a project that is suppose to act as a polyfill of standards?

@getify
Copy link
Owner

getify commented Nov 21, 2014

Let's ping @jrburke to see if he might have any insight. I don't personally deal much with AMD, but I do distribute my libs with the UMD header which I believed was supposed to be require-compatible.

@FagnerMartinsBrack
Copy link
Author

The thing is: this project is a polyfill intended to strictly follow the spec and is not expected to be AMD compatible.

That is the reason why I tried to find one that is interpreted as "this is spec only, ever, and nothing else", like "copy it to your environment, it doesn't matter where (just before the scripts you are using Promise), and it will just work without dependencies".

Weird enough, every polyfill I found does the same with module loaders, so I may be missing something.

Note: Moving the polyfill script tag to before the requireJS scripts is a way to workaround this problem, but doesn't actually solve it.

It is not anyting related to AMD, AMD is working properly, THAT is the problem.

@getify
Copy link
Owner

getify commented Nov 21, 2014

AMD in the browser and CommonJS in node both have this annoying property that they sorta hijack and assume everything is loaded in that same way. So, if you're using an AMD loader in your app, you should be loading NPO with it, and if you're in node, you should be loading NPO as a standard module with require(..).

I personally think both systems should have accommodations (and thus are not actually "behaving properly" though they're "behaving as designed") for people loading up polyfills that are intended to be standalone and patching the global env, but they don't, so my UMD is a compromise that makes it so you can still use NPO, but that's only if you go with the flow.

@FagnerMartinsBrack
Copy link
Author

I understand your idea behind using AMD in the browser and assuming ppl will load through it, but think this way:

  1. I use native Promise object inside a require call, both are available globally.
  2. I want a polyfill to just make my code work without any further changes, just put my code before the code I am referencing the Promise global object.

Take a look in my specific problem here. I had polyfills.js with just 2 polyfills, then I changed the build process here to split the files and concatenate them.

I expected the code to work, but the build failed. Then I realized the polyfill was not actually a polyfill that mimics the native behavior, it was changing its behavior according to the presence, or not, of AMD.

I hope you understand why I disagree with:

... is a compromise that makes it so you can still use NPO, but that's only if you go with the flow.

With the argument that this compromise is irrelevant when one is expected to override native behavior.

The best backwards compatible solution in this case would be to not take out Promise from global scope and do as jquery does, keeping Promise globally and return it for AMD loaders.

I am not saying jQuery is correct in their side, I am saying that in this specific case that solution is the correct one.

@FagnerMartinsBrack
Copy link
Author

By the way, here you express the intent of making it globally available even though AMD is in place, but as I mentioned it doesn't work.

Maybe a named module is the proper way to go here, since this polyfill is intended to be concatenated with other scripts that are not AMD compatible (other polyfills).

@getify
Copy link
Owner

getify commented Nov 21, 2014

We are well outside of my expertise. I don't know or understand why AMD expects the things it expects. I use UMD because I don't want to care how you use the script. If I had no UMD, I'd get people (in fact, I did early on) complain that they want to load it with AMD. UMD is intended to be a don't-think-about-it wrapper. To the extent that AMD loaders are incompatible with that, I think that's their fault, not UMD's.

I am not going to remove UMD, because there's too many people who rely on its behavior. But if there's some sort of specific tweak that doesn't break their use cases and enables yours, I'm happy to consider it. But I can't have a reasonable conversation about proper AMD design/behavior, because I've deliberately avoided that world. So an AMD expert should weigh in.

@smikes
Copy link
Collaborator

smikes commented Nov 21, 2014

If you're in a requireJS environment, can't you get what you need by doing a require on the NPO module?

When you say

I use a native Promise object inside a require call

Can you point me at where you're doing that and tell me about what you're trying to do?

(BTW I am not the AMD expert we are looking for.)

@jrburke
Copy link

jrburke commented Nov 21, 2014

@FagnerMartinsBrack the error in the original message sounds like the file is loaded by a hand-authored script tag in an HTML document instead of being loaded by the loader. If that is the case, when using an AMD loader, all AMD modules should be loaded by the loader. That, or load this shim in a script src tag before the tag for require.js. In that case, then you do not use npo as a dependency in a module, you can just use Promise as a global.

Or, if doing a build, and the built file is loaded by the loader and this error occurs, the build tool does not know how to find the define() call in the npo.js file to give it a module name during a build. I just tried with the latest r.js, 2.1.15, and it does seem to find the define() call, so more info on what build tool was used and its version would probably help to pinpoint the problem.

@FagnerMartinsBrack
Copy link
Author

If you're in a requireJS environment, can't you get what you need by doing a require on the NPO module?

Yes, I can. But that is not how Promises natively works. I expect globals without changing my code.

Can you point me at where you're doing that and tell me about what you're trying to do?

Native environment (it works):

<script src="require.main.js"></script>
<script src="require-2.1.15.js"></script>
<script src="polyfills.js"></script>
require( [ "moduleA" ], function( moduleA ) {
    Promise.resolve( moduleA() );
});

Environment with requireJS AND the native polyfill concatenated inside polyfills.js (it doesn't work)

<script src="require.main.js"></script>
<script src="require-2.1.15.js"></script>
<script src="polyfills.js"></script>
require( [ "moduleA" ], function( moduleA ) {
    Promise.resolve( moduleA() );
});
Error: Mismatched anonymous define() module: function (){return b[a]}
http://requirejs.org/docs/errors.html#mismatch

Same code, but a polyfill with AMD support was concatenated inside polyfills.js.

Why was it concatenated and not used within an specific custom fancy build process? Because when I am handling polyfills I don't need to care about if I am using AMD or not. The native behavior doesn't care, so should the polyfill, otherwise it may work in some cases and it may not work in other cases.

This is a huge abstraction leak between a polyfill and an AMD environment.

@getify

If I had no UMD, I'd get people (in fact, I did early on) complain that they want to load it with AMD

As I said several times, this is a polyfill, and the native implementation doesn't care if your environment is using AMD in the browser or not. So the reasoning for not including AMD would be that the native implementation doesn't use it either.

The polyfill usage is usually to put a single script tag above the code that is using some feature, and then it should start using that feature.
In this case put it above the first require call in your application and it should work.
Now, if you have an AMD environment, there is an unnecessary new layer of complexity, you need to put the script tag above the first require call in your application and also above the script tags used to load requireJS.

But if there's some sort of specific tweak that doesn't break their use cases and enables yours, I'm happy to consider it. But I can't have a reasonable conversation about proper AMD design/behavior, because I've deliberately avoided that world. So an AMD expert should weigh in.

In this case only @jrburke can tell if there is a way to enable AMD and also provide global access to the Promise polyfill by enabling the user to put the script below or above the requireJS script tags and avoid this abstraction leak. Otherwise this code is not a "native-promise-only" as the repository name suggests, sorry.


This is not a matter if AMD is working or not, is a matter of abstraction leaks, semantics and expectations of a polyfill. Maybe when ES6 module pattern lands it can be expected inside the polyfills code, but until then you are adding custom code to something that should just replace standard stuff.

Hope I made the point clear.

@FagnerMartinsBrack
Copy link
Author

@jrburke

... the error in the original message sounds like the file is loaded by a hand-authored script tag in an HTML document instead of being loaded by the loader. If that is the case, when using an AMD loader, all AMD modules should be loaded by the loader. That, or load this shim in a script src tag before the tag for require.js. In that case, then you do not use npo as a dependency in a module, you can just use Promise as a global.

Yes I am aware of that, but in this specific case it doesn't make sense (see above).

@getify
Copy link
Owner

getify commented Nov 21, 2014

There are lots of people who use AMD and who expect there to be no such thing as a "real polyfill" in the sense of patching the global environment. They expect to be able to import a "Promise" module for any module that requires promises, and to call it whatever they want (not necessarily using the name Promise). I have seen a couple of projects where people call it NPO in fact, to keep it clear.

I think the goal of keeping a polyfill entirely separate from the module system in use is admirable, but quite frankly it's unrealistic based on the expectations of the community. I use UMD because it's the easiest compromise for those who want to load NPO in AMD.

Moreover, most people that I've run across in the AMD world do in fact load everything with AMD. So whether I like that or not, supporting that has paved the cowpath and made it easier for people.

It's unfortunate that in your one circumstance and perspective, it's complicated it. But you're the first person to push back, and there are hundreds who've been happily using it in AMD and CommonJS environments now, for several months.

@getify
Copy link
Owner

getify commented Nov 21, 2014

Furthermore, stating personal opinion here, I think pretty much all the module systems have abjectly failed to recognize the validity of pure use-cases like the polyfill and the plugin. They variously just hand waive and/or push such handling complication details back on the user. I don't see this being fixed _at all_ in ES6 modules, unfortunately.

@FagnerMartinsBrack
Copy link
Author

But you're the first person to push back, and there are hundreds who've been happily using it in AMD and CommonJS environments now, for several months.

Well, then I can't argue about that. The only thing I can hope is that people don't start creating every polyfill to be loaded using AMD, since the original feature may just be available as if you were not aware if your environment uses AMD or not.

I don't see this being fixed at all in ES6 modules, unfortunately.

I don't understand what you mean.
Polyfills inside a standard modules environment has nothing to do with polyfills in a custom module environment. If you need to create a polyfill considering some standard browser module pattern, then there is nothing to fix.

@FagnerMartinsBrack
Copy link
Author

By the way I don't know details of how ES6 modules is supposed to work, so I may be stating some wrong assumptions here.

@getify
Copy link
Owner

getify commented Nov 21, 2014

The only thing I can hope is that...

I go back to an earlier sentiment that I've echoed a few times. I think the shortcoming is that there is not, to my knowledge, a simple way for me to express a piece of code which will act either as a direct native-global-patching polyfill, OR as an AMD-loaded module. I believe this to be a shortcoming in the module mechanisms (AMD and CommonJS, namely) that they don't give me a better way of serving both the concerns you have (which I would naturally tend to agree with!) and the concerns of those who want "no globals at all" kind of programming, and want to import everything.

If someone comes up with a compellingly-better UMD'ish type pattern which I can wrap around my code that simultaneously achieves both goals, I'm more than happy to adjust.

I think the ball is in the court of the AMD and/or CommonJS experts to suggest if there's something I've missed, or if there's something they need to do better to accommodate these concerns. :)

@jrburke
Copy link

jrburke commented Nov 21, 2014

If the desire is to have a UMD wrapper that only chooses one registration path, AMD, CJS or global, this type of UMD works:

https://github.com/umdjs/umd/blob/master/returnExportsGlobal.js

It includes support for a dependency, but that could be removed in this case.

The story is messier in ES6: if you actually want to export a value as a module, that requires new syntax (and a browser that supports that syntax), and you cannot use a plain script src tag to load it.

In general, polyfills are in a tough spot in any module system -- they are trying to provide "global", implicitly assumed capabilities. That is in conflict with a module system which is about explicit dependencies with local handles.

You can certainly design a workflow that loads all polyfills up front, and go with a polyfill style where the polyfills do not register with a module system and do not have dependencies amongst themselves. Then, polyfills are not referenced as explicit module dependencies, because the idea is that they just exist, and then a module loader can load them all up first before calling the main modular code. For an module loader like requirejs:

require(['polyfills'], function() { require(['app']); });

Where 'polyfills' is a defined module that just has dependencies on all the polyfills.

Trouble is, as @getify points out, users of a module system generally like to use the module loader to load all the code, and generally like dependencies to be all explicit in module definitions. It can be a benefit for the polyfill too, or at least the portability of the modules that use them. If the modules depend on the specific use of a particular polyfill (because the standard moves to a different place or has behavior that was not specified), those modules can still be used later even once all browser have the target of the polyfill.

The trouble for a polyfill provider is trying to bridge those multiple at once, and tradeoffs have to be made. From what I can tell, the tradeoffs for this project seem reasonable.

@getify
Copy link
Owner

getify commented Nov 21, 2014

If the desire is to have a UMD wrapper that only chooses one registration path...

Just to clarify, I use a modified UMD for polyfills which intentionally patches the global context AND optionally will expose itself to AMD or CJS.

@jrburke I would love to understand why the UMD (with require(..) in it) can't be included as-is (and not loaded with AMD) and why it needs to throw the error?

@jrburke
Copy link

jrburke commented Nov 21, 2014

Not sure I followed that last question, if you have some code you are thinking about that would help me follow along better.

@FagnerMartinsBrack
Copy link
Author

He is probably refering to this example: #39 (comment)

And this code

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

No branches or pull requests

4 participants