mdn / js-examples

Code examples that accompany the MDN JavaScript/ECMAScript documentation
https://developer.mozilla.org/en-US/docs/Web/JavaScript
Creative Commons Zero v1.0 Universal
1.12k stars 699 forks source link

.mjs modules #4

Closed qtov closed 4 years ago

qtov commented 4 years ago

Hey,

I took the freedom to change the page about js modules. Mostly because when putting the .js extension gives me the following errors:

Failed to load module script: The server responded with a non-JavaScript MIME type of "text/plain". Strict MIME type checking is enforced for module scripts per HTML spec. - Chromium Loading module from “http://127.0.0.1:8001/utils.js” was blocked because of a disallowed MIME type (“text/plain”). Loading failed for the module with source “http://127.0.0.1:8001/utils.js”. - Firefox

Renaming them to .mjs fixes it.

The reason as to why I changed it was that the docs were the first page I looked at. I tried the examples on the page to no avail just to be met by errors and had to search a while to make it work.

Would you be so kind to rename the modules from name.js to name.mjs to make the changes consistent? (When somebody decides to clone the examples) Also, if my change wasn't a correct one, please let me know.

Best regards!

chrisdavidmills commented 4 years ago

Hi @kvazhir ,

Thanks for your input in trying to figure out this problem.

I've reverted your changes for now, as I'm not convinced they are right, as would like to have more of a discussion before we decide what to do about this. Your work is not lost — we can always revert them back if needed.

Basically, I know about the .mjs extension for module files, and when I originally wrote this article, I was going to use it. However, when I tested my examples I found that only Chrome currently seems to support that extension. Firefox breaks with a disallowed MIME type error, similar to the one you cite above.

So therefore I ended up going with the .js extension in my examples. I've just tried testing the examples again, in Firefox, Chrome, and Chrome Canary. For me, they still seem to work on all browsers with .js, but only Chrome with .mjs.

Question — are you trying to run the examples directly from files (e.g. via a file:// URL), or are you running them through a web server? I found that they need to be run through a web server to work properly.

I may well be missing something here, so let me know if you think I am.

Thanks again for the input, I really appreciate it.

qtov commented 4 years ago

Hey,

Thanks for replying. I'm running it through a web server. Locally they don't want to work. For this example I was using the simple http server python 3.7.4 provides. python3 -m http.server

When testing with more browsers, .js extensions do not seem to work in any of my browsers except Edge.

Browsers tested: Vivaldi - 2.7.1628.33 (Stable channel) (64-bit) Chrome - Version 77.0.3865.90 (Official Build) (64-bit) Firefox - 69.0.1 (64-bit)

All of the above did not work. And Edge, which surprisingly worked. Edge - 42.17134.1.0

Could it be a problem of python on how it serves the files? Taking them as text/plain instead of javascript/whatever. Or have you disabled strict MIME type checking? Vivaldi is my main driver, and the rest are kept with the default settings.

Thanks for taking your time to answer.

qtov commented 4 years ago

To add a bit of information, this article looks to explain why .mjs is used. https://medium.com/passpill-project/files-with-mjs-extension-for-javascript-modules-ced195d7c84a

chrisdavidmills commented 4 years ago

Thanks for the further details, and sorry for taking so long to get back to you.

So, I'm at a loss of what to do here. When I try it with .mjs, it doesn't seem to work, and when I try it with .js, it does seem to work.

I am using Python simple server too, and I've not modified it in any way. So I wonder what the default setting is?

chrisdavidmills commented 4 years ago

And this is not just for when I run then in python simple server, BTW.

When I run them from GitHub, I still get the same results. If you look at https://github.com/mdn/js-examples/tree/master/modules, you'll find links to all the live examples.

qtov commented 4 years ago

I tried it again, but I get the same results. My Python 3.7.4 http.server apparently serves .js files as text/plain. It works fine with .mjs files that are served as application/javascript.

In [1]: import http.server
In [2]: http.server.SimpleHTTPRequestHandler.extensions_map['.js']
Out[2]: 'text/plain'
In [3]: http.server.SimpleHTTPRequestHandler.extensions_map['.mjs']
Out[3]: 'application/javascript'

I tried the SimpleHTTPServer of Python 2.7.15, no extension worked.

In [1]: import SimpleHTTPServer
In [2]: SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map['.js']
Out[2]: 'text/plain'
In [3]: SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map['.mjs']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-3-f4f2344be2e0> in <module>()
----> 1 SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map['.mjs']

KeyError: '.mjs'

I tried making it work with CherryPy (using Python 2.7.15 and Python 3.7.4).

Python 2 didn't want to work with either extension (.js served as text/plain and .mjs served as text/html (which is default if no key was found)). For this I had to explicitly tell the server how to serve static content.

mimetypes.add_type('application/javascript', '.js')
mimetypes.add_type('application/javascript', '.mjs')

This way it works with both .mjs and .js .

Python 3 worked by default with the .mjs extension (.js served as text/plain and .mjs served as application/javascript)

So in the end, it's the server's fault how it serves static content. Python 3's simple server and CherryPy works only with .mjs, Python 2's simple server and CherryPy works with neither of them.

I don't know why Python 3 works with .mjs but not with .js. Probably it's more than just an extension.

The exact version of Python I'm using is: Python 3.7.4 (tags/v3.7.4:e09359112e, Jul 8 2019, 19:29:22) [MSC v.1916 32 bit (Intel)] and Python 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:22:17) [MSC v.1500 32 bit (Intel)]

qtov commented 4 years ago

I've seen the links to the live examples, I have no idea why it works there and not on my part. All I'm using are files (.html, .js) a command python3 -m http.server and a browser.

I've also tried the examples you linked to no avail, they're throwing the same error.

I think that the best approach would be a note in the docs, no need for changing the extensions. A note saying that it might be possible to see this error and to try using .mjs instead or specifying the mimetype for a given extension.

chrisdavidmills commented 4 years ago

This morning I've done a bunch more work on trying to figure out the problem here.

I finally realised the problem was actually that macOS was silently adding on .js to the end of my .mjs files and hiding the file extension. So all of my files were actually coming out as x.mjs.js. Bloody operating systems! Once I stopped it from doing that, it worked fine. All the examples in the repo now work, with .mjs files.

So I feel slightly silly now, but I've worked it out. I've just reverted back to your edits on the JS Module guide on MDN, and now I'll give it a final clean up.

Thank you so much for your help on this.

GeoffreyBooth commented 4 years ago

Hi @chrisdavidmills I’m on the modules team for Node.js. I’ve done some research about file extensions and various servers. I think I can explain what you and @kvazhir were seeing.

Basically, browsers don’t care about file extensions. They don’t “support” .js or .mjs per se. What browsers care about, but only for ES modules, is MIME type: the JavaScript file meant to be interpreted as a module needs to be served with a Content-Type header set to one of the JavaScript MIME types such as text/javascript or application/javascript. Per the ECMAScript spec, if the file is served without that MIME type, the browser will reject it.

So therefore what matters isn’t which browser you use to load the ES module URL, but rather what server you use to serve that file (assuming you’re using a modern browser that supports ES module syntax). And the servers use file extensions to decide what MIME type to use to put in Content-Type, assuming they set that header at all. So for ES modules to work properly, the JavaScript file needs to be hosted on a properly configured server.

I created a repo with automated tests to see which of the most popular servers serve .js and .mjs files with the correct MIME types by default: https://github.com/GeoffreyBooth/js-mjs-mime-type-test. The TLDR is that all servers serve .js files properly by default, but as of last month only Python and Node.js served .mjs with the correct header by default. The Python server was probably a recent change, which would explain your experiences.

Depending on which authority you trust, Apache httpd and Nginx serve something like 70 to 74% of all websites—and they don’t support .mjs by default. Both are easily configured, but it’s an open question what percentage of users on shared hosting have permission to set custom MIME types. Therefore it’s a reasonable assumption that something like half of all the websites in the world are served by servers that aren’t serving .mjs properly, either because the server default configuration doesn’t support it or the user can’t configure it (or doesn’t know to, or know how to). We can’t rely on software updates to save us, either; apparently 22% of websites are still running Apache httpd 2.2, which reached end of life more than two years ago on 2018-01-01.

Because of this, the only users who can use .mjs safely for their final output JavaScript files are those users who know where their files will eventually be served, and that that server supports .mjs. Anyone else, especially authors of public open source JavaScript, should use .js for their final published files. Authors can use whatever extension they want in development, where they control the environment; it would be no different than using TypeScript during development, but saving out to JavaScript for the files that run in the browser.

I think that any recommendation that users use .mjs needs to include this important caveat, or else users will be frustrated—as you both were!—that their files aren’t running in browsers as they expected. I don’t know specifically what servers you were running or how they were configured, but in general every web server will serve ES modules correctly as .js files, while few will serve .mjs files correctly.

chrisdavidmills commented 4 years ago

@GeoffreyBooth thanks for the explanation; this is really helpful.

So it looks like were explaining this wrongly in our MDN article; I've updated the note at the bottom of this section to hopefully explain it correctly. Can you let me know if my wording needs an update?

Thanks!

GeoffreyBooth commented 4 years ago

@chrisdavidmills Thanks for taking the time to update it. Looking at the current text, I have a few thoughts:

Here’s how I would phrase it:

Note: The .mjs extension is useful during development for clarity, to make clear that a file is a module and not just regular JavaScript; and for additional validation when used with tools that support .mjs. In production, however, be aware that many web servers do not support .mjs; you should save your final published JavaScript files using .js extensions unless you know that the files will be hosted on a server that serves .mjs files correctly. Browsers require module JavaScript to be served with a Content-Type header containing a JavaScript MIME type such as text/javascript; virtually all servers do this already for .js files, but many do not for .mjs files. If module JavaScript is served without the required header, browsers will refuse to load it.

In case you’re curious, the error thrown by Firefox is:

Loading module from “http://localhost/file.mjs” was blocked because of a disallowed MIME type (“application/octet-stream”). Loading failed for the module with source “http://localhost/file.mjs”.

And Chrome returns:

Failed to load module script: The server responded with a non-JavaScript MIME type of "application/octet-stream". Strict MIME type checking is enforced for module scripts per HTML spec.

As a broader point, who is your target audience? The .mjs extension is only useful during development, assuming that the user is using tools that support it. In production, however, there’s no benefit to .mjs—and a huge potential downside in that proper server configuration is make-or-break as to whether your site works. Are you writing these docs more for intermediate-to-advanced users who are likely using build tools that generate separate files for production? Or are they more often beginners who are editing the same files that they’ll be copying to a server for hosting? If the latter, we should probably change the examples back to using .js and have the note be more about how there’s also this .mjs extension that they could use under certain circumstances.

chrisdavidmills commented 4 years ago

Thanks @GeoffreyBooth ! It as been a while, but I've returned to this now. I consulted a few people, and it looks like consensus is definitely to use .js over .mjs.

So, @ExE-Boss has helpfully updated the examples repo to change them back to .js.

And I've just updated the article to make this change too, including explaningthe file extenions thing at

https://wiki.developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#Aside_%E2%80%94_.mjs_versus_.js

What do you think?

GeoffreyBooth commented 4 years ago

Thanks @chrisdavidmills and @ExE-Boss, the current text is much better. I just realized I could edit it, so rather than give you yet more notes I made some very minor edits. Please feel free to re-revise; basically I wanted to make it clear that you need the correct MIME type not just for .mjs files but for all JavaScript modules. The previous text seemed to imply that the MIME type was only required for .mjs, whereas it’s only an issue for .mjs files as most servers already serve .js files correctly. I also wanted to clarify that the note is from the V8 docs; it’s not an official position of Google.

I don’t know if there’s a consensus; .mjs has some passionate proponents, including the author of that V8 article. So you might get further complaints from that camp now 😄. This has been something the modules team at Node has been dealing with for years now, and I’ve been trying to stay neutral and keep all recommendations purely fact-based and including inconvenient caveats. My priority is that users actually start using import and export syntax, and I don’t want adoption hindered by users feeling like they need to use .mjs if their workflow or hosting environment make that difficult or impossible.

chrisdavidmills commented 4 years ago

Thanks @chrisdavidmills and @ExE-Boss, the current text is much better. I just realized I could edit it, so rather than give you yet more notes I made some very minor edits. Please feel free to re-revise; basically I wanted to make it clear that you need the correct MIME type not just for .mjs files but for all JavaScript modules. The previous text seemed to imply that the MIME type was only required for .mjs, whereas it’s only an issue for .mjs files as most servers already serve .js files correctly. I also wanted to clarify that the note is from the V8 docs; it’s not an official position of Google.

This is great, thanks so much for your further help!

I don’t know if there’s a consensus; .mjs has some passionate proponents, including the author of that V8 article. So you might get further complaints from that camp now 😄. This has been something the modules team at Node has been dealing with for years now, and I’ve been trying to stay neutral and keep all recommendations purely fact-based and including inconvenient caveats. My priority is that users actually start using import and export syntax, and I don’t want adoption hindered by users feeling like they need to use .mjs if their workflow or hosting environment make that difficult or impossible.

Whatever the feeling about this in other camps, I agree with your position; I am a pragmatist, and just want to make learning as easy as possible.