From 782f81298949e8e8c20410d7eb79431688e36e23 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Tue, 3 Feb 2015 18:48:36 +0200 Subject: [PATCH] Add grunt-critical. --- .gitignore | 1 + Gruntfile.js | 44 +++++++++- package.json | 2 + source/_includes/header.html | 8 +- source/assets/js/vendor/loadCSS.js | 124 +++++++++++++++++++++++++++++ 5 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 source/assets/js/vendor/loadCSS.js 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 ));