Skip to content

Instantly share code, notes, and snippets.

@branneman
Last active September 27, 2016 11:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save branneman/37865bcfc5d102c2be6c to your computer and use it in GitHub Desktop.
Save branneman/37865bcfc5d102c2be6c to your computer and use it in GitHub Desktop.
Conditioner.js bootstrap with JavaScript Mustard Cut & Polyfill loading
<!doctype html>
<html>
<head>
...
<script>
(function(d){
if (!('querySelector' in d && 'addEventListener' in window)) return;
d.documentElement.className += ' has-js';
d.addEventListener('DOMContentLoaded', function() {
var s = d.createElement('script');
s.setAttribute('src', '/static/js/vendor/requirejs/require.js');
s.setAttribute('data-main', '/static/js/main');
d.body.appendChild(s);
});
}(document));
</script>
</head>
<body>
...
</body>
</html>
(function(){
require.config({
map: {
'*': {
classList: 'polyfills/classList',
conditioner: 'vendor/conditionerjs/conditioner'
}
}
});
var polyfills = [];
if (!('classList' in document.documentElement)) {
polyfills.push('classList');
}
// ... more feature detections ...
require(['conditioner'].concat(polyfills), function(conditioner) {
conditioner.init();
});
}());
@rikschennink
Copy link

I'd run the mustard cut in the head so you can remove the no-js earlier and use it in your CSS. Use it later on for the mustard cut statement?

Replace document.querySelector('#js-mustard-cut') with document.getElementById('js-mustard-cut') it's a lot faster. I'm also wondering if it's possible to just select the existing <script> element and add the src and async attributes to it. I also think the async doesn't matter if it's the last script being loaded but I'm not sure.

You could do the following to get rid of the global deps variable:

require(['conditioner'].concat(polyfills), function(conditioner) {
    document.documentElement.classList.remove('no-js');
    conditioner.init();
});

I'm a little worried about servering all the polyfills as separate files, concatting and minifying them would be faster I guess.

@branneman
Copy link
Author

I'm going with a has-js class instead of a no-js class, because Progressive Enhancement.

I'd run the mustard cut in the head so you can remove the no-js earlier

This is possible, however since I'm loading the polyfills in my main.js, this means I can't use classList anymore, a simple x.className += ' has-js' will have to do then.

Also, right now I can call insertBefore on the current script at the end of the <body>. What do you propose for inserting the <script> tag at the end of the body?

I also think the async doesn't matter if it's the last script being loaded

I copied it from the Google Analytics code, but I'm unsure why they add it as well.

Replace querySelector() with getElementById()

Sure, why not ;)

get rid of the global deps variable

You're right. I've removed the deps variable, and underscored the polyfills variable to indicate it's private.

I'm a little worried about servering all the polyfills as separate files

I don't see any other way, I want them to be conditionally loaded, every browser gets different polyfills.

@davidhund
Copy link

Sorry to go OT a bit, but just to chip in re: your last point: have you seen https://polyfill.io ?

It's "Polyfill As A Service" and I am lovin' the idea: you simply link to 1 script endpoint and the UA automagically receives appropriate polyfills. You can—of course—also specify features explicitly:https://cdn.polyfill.io/v1/docs/features/

Anyway, off-topic for your use-case but my feeling is that this could be an elegant way to deal with polyfills…

@rikschennink
Copy link

I'm going with a has-js class instead of a no-js class, because Progressive Enhancement.

Interesting, I'm going to think on this some more.

a simple x.className += ' has-js' will have to do then.

It might be simple, but it's probably fast as well.

Also, right now I can call insertBefore on the current script at the end of the body. What do you propose for inserting the script tag at the end of the body?

I'd say document.body.appendChild()

I copied it from the Google Analytics code, but I'm unsure why they add it as well.

They have added it because Google Analytics is embedded in the head and they use a 'queue' object for all the tracking stuff (it works before the script is loaded).

I don't see any other way, I want them to be conditionally loaded, every browser gets different polyfills.

I would find a common set of functionality and merge those. Or, you could only polyfill once a module loads that requires certain polyfills. Certainly for conditional modules that would be preferable.

@branneman
Copy link
Author

Updated! Thanks for the response so far :)

@rikschennink
Copy link

Looks nice, I've taken a stab at shortening the mustard cut a bit. And have implemented the bouncer pattern.

(function(){

    var d = document;
    if (!('querySelector' in d && 'addEventListener' in window)) {return;}

    d.documentElement.className += ' has-js';
    d.addEventListener('DOMContentLoaded', function() {
        var s = d.createElement('script');
        s.setAttribute('src', '/static/js/vendor/requirejs/require.js');
        s.setAttribute('data-main', '/static/js/main');
        d.body.appendChild(s);
    });

}());

Also the new module enabled property in Conditioner might be interesting in relation to the polyfills. It's a micro mustard cut.

To prevent polluting the global scope with the _polyfills variable you could wrap the entire main.js in a self executing function or maybe even make a separate polyfills module.

@branneman
Copy link
Author

Manual minification, I love it. We can do even better:

(function(d){
    // ...
}(document));

Also, would it be possible to shorten both the setAttribute calls? I know s.src = will work, but will this work for the data attribute as well?

var s = d.createElement('script');
s.src = '/static/js/vendor/requirejs/require.js';
s['data-main'] = '/static/js/main';

Or we could go crazy and save more bytes:

(function(d, f){
    s[f]('src', '/static/js/vendor/requirejs/require.js');
    s[f]('data-main', '/static/js/main');
}(document, 'setAttribute'));

On second thought, let's stop here ;) We should leave minification to a minifier.

Anyway, I've updated the gist with the sane changes.

@rikschennink
Copy link

Haha, brilliant! :-) Excellent plan to leave minifying to the pros ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment