diff --git a/.gitignore b/.gitignore
index 56b53a3f..6de932f0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
/node_modules
/npm-debug.log
/source/.jekyll-metadata
+/source/assets/css/critical.css
diff --git a/Gruntfile.js b/Gruntfile.js
index c49f6106..f0143e98 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -138,6 +138,20 @@ module.exports = function(grunt) {
}
},
+ critical: {
+ dist: {
+ options: {
+ base: '<%= dirs.dest %>',
+ css: ['<%= concat.css.dest %>'],
+ extract: true,
+ width: 1300,
+ height: 900
+ },
+ src: '<%= dirs.dest %>/index.html',
+ dest: '<%= dirs.src %>/assets/css/critical.css'
+ }
+ },
+
cssmin: {
minify: {
options: {
@@ -204,9 +218,13 @@ module.exports = function(grunt) {
options: {
assetsDirs: [
'<%= dirs.dest %>/',
+ '<%= dirs.dest %>/assets/css/',
'<%= dirs.dest %>/assets/img/'
],
patterns: {
+ html: [
+ [/loadCSS\(['"]([^"']+)['"]\)/gm, 'Replacing reference to CSS within `loadCSS`']
+ ],
js: [
[/navigator\.serviceWorker\.register\(["'](\/sw\.min\.js)["']/gm, 'Replacing reference to sw.min.js']
]
@@ -225,6 +243,7 @@ module.exports = function(grunt) {
options: {
base: 'https://cdn.mpc-hc.org/',
html: {
+ 'link[rel="preload"]': 'href',
'meta[itemprop="image"]': 'content',
'meta[property="og:image:secure_url"]': 'content',
'input[type="image"]': 'src'
@@ -239,6 +258,20 @@ module.exports = function(grunt) {
}
},
+ staticinline: {
+ dist: {
+ options: {
+ basepath: '<%= dirs.src %>/'
+ },
+ files: [{
+ expand: true,
+ cwd: '<%= dirs.dest %>/',
+ src: '**/*.{html,php}',
+ dest: '<%= dirs.dest %>/'
+ }]
+ }
+ },
+
connect: {
options: {
hostname: 'localhost',
@@ -297,6 +330,11 @@ module.exports = function(grunt) {
},
htmllint: {
+ options: {
+ ignore: [
+ 'A "link" element with an "integrity" attribute must have a "rel" attribute that contains the value "stylesheet".'
+ ]
+ },
src: '<%= dirs.dest %>/**/*.html'
},
@@ -384,6 +422,8 @@ module.exports = function(grunt) {
'concat',
'autoprefixer',
'uncss',
+ 'critical',
+ 'staticinline',
'cssmin',
'uglify',
'filerev',
@@ -398,7 +438,7 @@ module.exports = function(grunt) {
var envCI = process.env.CI || process.env.ci;
if (envCI && envCI.toLowerCase() === 'true') {
- buildTasks.splice(12, 1);
+ buildTasks.splice(14, 1);
}
grunt.registerTask('build', buildTasks);
@@ -417,6 +457,8 @@ module.exports = function(grunt) {
'useminPrepare',
'concat',
'autoprefixer',
+ 'critical',
+ 'staticinline',
'filerev',
'usemin',
'generate-sri',
diff --git a/package.json b/package.json
index 05f4fcd6..efcb62f4 100644
--- a/package.json
+++ b/package.json
@@ -30,11 +30,13 @@
"grunt-contrib-htmlmin": "^2.0.0",
"grunt-contrib-uglify": "^2.0.0",
"grunt-contrib-watch": "^1.0.0",
+ "grunt-critical": "^0.2.2",
"grunt-eslint": "^19.0.0",
"grunt-filerev": "^2.3.1",
"grunt-html": "^8.4.0",
"grunt-include-replace": "^5.0.0",
"grunt-jekyll": "^0.4.2",
+ "grunt-static-inline": "^0.1.9",
"grunt-uncss": "^0.6.0",
"grunt-usemin": "^3.1.1",
"load-grunt-tasks": "^3.4.0",
diff --git a/source/_includes/header.html b/source/_includes/header.html
index 6e52d0df..16e9775e 100644
--- a/source/_includes/header.html
+++ b/source/_includes/header.html
@@ -13,7 +13,12 @@
-
+
+
+
+
@@ -64,6 +69,7 @@
{% feed_meta %}
+
{% capture body_class %}{{ page.layout }}{% if page.body_class %} page-{{ page.body_class }}{% elsif page.slug %} page-{{ page.slug | slugify }}{% endif %}{% endcapture %}
diff --git a/source/assets/js/vendor/loadCSS.js b/source/assets/js/vendor/loadCSS.js
new file mode 100644
index 00000000..ca0fe362
--- /dev/null
+++ b/source/assets/js/vendor/loadCSS.js
@@ -0,0 +1,124 @@
+/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */
+(function(w){
+ "use strict";
+ /* exported loadCSS */
+ var loadCSS = function( href, before, media ){
+ // Arguments explained:
+ // `href` [REQUIRED] is the URL for your CSS file.
+ // `before` [OPTIONAL] is the element the script should use as a reference for injecting our stylesheet before
+ // By default, loadCSS attempts to inject the link after the last stylesheet or script in the DOM. However, you might desire a more specific location in your document.
+ // `media` [OPTIONAL] is the media type or query of the stylesheet. By default it will be 'all'
+ var doc = w.document;
+ var ss = doc.createElement( "link" );
+ var ref;
+ if( before ){
+ ref = before;
+ }
+ else {
+ var refs = ( doc.body || doc.getElementsByTagName( "head" )[ 0 ] ).childNodes;
+ ref = refs[ refs.length - 1];
+ }
+
+ var sheets = doc.styleSheets;
+ ss.rel = "stylesheet";
+ ss.href = href;
+ // temporarily set media to something inapplicable to ensure it'll fetch without blocking render
+ ss.media = "only x";
+
+ // wait until body is defined before injecting link. This ensures a non-blocking load in IE11.
+ function ready( cb ){
+ if( doc.body ){
+ return cb();
+ }
+ setTimeout(function(){
+ ready( cb );
+ });
+ }
+ // Inject link
+ // Note: the ternary preserves the existing behavior of "before" argument, but we could choose to change the argument to "after" in a later release and standardize on ref.nextSibling for all refs
+ // Note: `insertBefore` is used instead of `appendChild`, for safety re: http://www.paulirish.com/2011/surefire-dom-element-insertion/
+ ready( function(){
+ ref.parentNode.insertBefore( ss, ( before ? ref : ref.nextSibling ) );
+ });
+ // A method (exposed on return object for external use) that mimics onload by polling document.styleSheets until it includes the new sheet.
+ var onloadcssdefined = function( cb ){
+ var resolvedHref = ss.href;
+ var i = sheets.length;
+ while( i-- ){
+ if( sheets[ i ].href === resolvedHref ){
+ return cb();
+ }
+ }
+ setTimeout(function() {
+ onloadcssdefined( cb );
+ });
+ };
+
+ function loadCB(){
+ if( ss.addEventListener ){
+ ss.removeEventListener( "load", loadCB );
+ }
+ ss.media = media || "all";
+ }
+
+ // once loaded, set link's media back to `all` so that the stylesheet applies once it loads
+ if( ss.addEventListener ){
+ ss.addEventListener( "load", loadCB);
+ }
+ ss.onloadcssdefined = onloadcssdefined;
+ onloadcssdefined( loadCB );
+ return ss;
+ };
+ // commonjs
+ if( typeof exports !== "undefined" ){
+ exports.loadCSS = loadCSS;
+ }
+ else {
+ w.loadCSS = loadCSS;
+ }
+}( typeof global !== "undefined" ? global : this ));
+
+/*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */
+(function( w ){
+ // rel=preload support test
+ if( !w.loadCSS ){
+ return;
+ }
+ var rp = loadCSS.relpreload = {};
+ rp.support = function(){
+ try {
+ return w.document.createElement( "link" ).relList.supports( "preload" );
+ } catch (e) {
+ return false;
+ }
+ };
+
+ // loop preload links and fetch using loadCSS
+ rp.poly = function(){
+ var links = w.document.getElementsByTagName( "link" );
+ for( var i = 0; i < links.length; i++ ){
+ var link = links[ i ];
+ if( link.rel === "preload" && link.getAttribute( "as" ) === "style" ){
+ w.loadCSS( link.href, link, link.getAttribute( "media" ) );
+ link.rel = null;
+ }
+ }
+ };
+
+ // if link[rel=preload] is not supported, we must fetch the CSS manually using loadCSS
+ if( !rp.support() ){
+ rp.poly();
+ var run = w.setInterval( rp.poly, 300 );
+ if( w.addEventListener ){
+ w.addEventListener( "load", function(){
+ rp.poly();
+ w.clearInterval( run );
+ } );
+ }
+ if( w.attachEvent ){
+ w.attachEvent( "onload", function(){
+ w.clearInterval( run );
+ } )
+ }
+ }
+}( this ));