Problem
I was talking to Paul Irish today regarding some tweaks I was doing to html5boilerplate‘s code when I hit the usual kludge you see when dealing with JavaScript in .htaccess files or <VirtualHost> directives. Something to the effect of
# compress JavaScript
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
# [ ... snip ... ]
FilterProvider COMPRESS DEFLATE resp=Content-Type
/(text/|(application/(x-)?)javascript)/
# [ ... snip ... ]
# add expires to JavaScript
ExpiresByType application/javascript "access plus 2 months"
ExpiresByType application/x-javascript "access plus 2 months"
ExpiresByType text/javascript "access plus 2 months"
And I wondered why we, as developers, stand for using JavaScript with up to 3 mime/content-types when it was clearly specified 5 years ago in RFC 4329 that we’d at least like to use only one?!
From that spec:
Use of the "text" top-level type for this kind of content is
known to be problematic. This document thus defines
text/javascript and text/ecmascript but marks them as
"obsolete". Use of experimental and unregistered media
types, as listed in part above, is discouraged. The media
types,
* application/javascript
* application/ecmascript
which are also defined in this document, are intended for
common use and should be used instead.
I know that this is an informational RFC, not a standards track one, but I wanted to see how close modern (and not so modern) browsers were doing at accomplishing this goal of allowing external script files with the Content-Type: application/javascript.
Test
So, I threw together a test page to check how server-side Content-Type HTTP headers and <script type="attributes"> affected their execution. Here’s how I set up the types with Apache on my web server:
AddType application/javascript .aj
AddType application/x-javascript .axj
AddType text/javascript .tj
AddType zomg/totally-fake .ztf
And then I created a file with each of those extensions with a unique message that would document.write itself to my test page if they were execute correctly. Here’s the basic gist of the test page:
<h1>With type="XXX/YYY":</h1>
<script type="XXX/YYY" src="write.aj"></script>
<script type="XXX/YYY" src="write.axj"></script>
<script type="XXX/YYY" src="write.tj"></script>
<script type="XXX/YYY" src="write.ztf"></script>
And I also added a test with <script> tags with no type attribute at all, as per past advice I remember hearing from Doug Crockford’s site.
Results
Here were my results in a nifty table format (X = it worked!):
| <script type> | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| application /javascript |
application /x-javascript |
text /javascript |
zomg /totally-fake |
(none) | ||||||||||||||||
| Browser | .aj | .axj | .tj | .ztf | .aj | .axj | .tj | .ztf | .aj | .axj | .tj | .ztf | .aj | .axj | .tj | .ztf | .aj | .axj | .tj | .ztf |
| IE 6 (XP) | X | X | X | X | X | X | X | X | ||||||||||||
| IE 7 (XP) | X | X | X | X | X | X | X | X | ||||||||||||
| IE 8 (XP) | X | X | X | X | X | X | X | X | ||||||||||||
| IE 9 (W7) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| Safari 3 (XP) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| Safari 4 (XP) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| Safari 5 (XP) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| Opera 8 (XP) | X | X | X | X | X | X | X | X | X | X | X | X | ||||||||
| Opera 9 (XP) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| Opera 10 (XP) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| Opera 11 (W7) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| Opera 11 (Ubuntu) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| FF 2 (XP) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| FF 3 (XP) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| FF 3.6 (XP) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| FF 3.6 (Ubuntu) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| FF 4 (W7) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| FF 4 (Linux) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| Chrome 11 (XP) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| Chrome 11 (W7) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
| Chromium 11 (Ubuntu) | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | ||||
Conclusion
So, given this data, I came to one very clear conclusion:
never use <script type=”zomg/totally-fake”>
.
In addition to this epiphany, I plan only serving my JavaScript with one Content-Type – application/javascript as it had absolutely no impact on anything and is the right thing to do TM for the open web.
I’m also going to simply omit type="XXX/YYY" from my markup when I’m not validating my page as XHTML 1.0, as it saves bytes and works the same way (yup, even in HTML5). I do have a few sites that are valid XHTML 1.0, so it does matter in those cases, so in those cases I’ll use type="text/javascript" so I can continue passing and working in every browser possible.
Will you do the same?
Update:
I tested quite a few more combinations and wrote a follow-up article about a couple more things I found out here.
Aaah, this is the blog post I’ve been meaning to write for months now, but I never got around to it. Thanks!
I had created a similar test case here: http://mathiasbynens.be/demo/javascript-mime-type and found that the MIME type doesn’t seem to matter. You could even serve
.jsfiles with an emptyContent-Typeheader if you wanted to – they would still work.Yeah, figured that out today. Pretty whacky. I also tried a different type in data URIs for the src of the script as a joke for vapor.js, and it seemed to work (even with text/plain, lol, my pull request on vapor.js)
Oh, just saw you did this on your test page as well, very cool! Like the UI, and the easter eggs,
(
<noscript>Obviously, since JavaScript is disabled, all tests will fail.</noscript>)!Thanks for doing this test.
“I plan only serving my JavaScript with one Content-Type – application/javascript as it had absolutely no impact on anything.” => Well, maybe I’m not understanding your table, but it seems that IE < 9 and Opera 8 will not execute your JavaScript file if you do this
I wouldn't say "it has no impact".
I’m going to send
Content-Type: application/javascriptin my HTTP response headers, but omit thetypeattribute on my<script>tags.I totally agree that both of them having similar values and ending with “type” is a bit confusing, for sure, lol.
Also note that the type attribute in script tags and the Content-Type header (MIME Type) sent along with the web server response are two different beasts entirely.
Yup, that’s pretty much what I found out (and guessed before the test as well).
IE has content sniffing, so it just makes up it’s own mind what kind of file something is as (or after) it’s downloading. This can cause for some gnarly security bugs, though (see: utf-7 vs utf-8 hacks in IE).
Nice work! Definitely using “application/javascript” and no “text/javascript” when not xhtml 1. Do u know if it’s of some use to do “script.src=’text/javascript’ after a “var script=document.createElement(‘script’)” ?
I’ll add items to test this behavior (as well as data URIs) soon. I already started to on test page, but got busy.
Sorry script.type=”text/javascript”, not script.src
Pingback: links for 2011-05-06 « sonofbluerobot
Did you check out encodings? In my experience, text/JavaScript takes encoding into consideration, while application/JavaScript does not
Pingback: 2295 script tags later | danbeam.org :: blog
Thank you for a great post.
Pingback: Hauk IT - Web Consulting