├── .gitignore ├── Gruntfile.js ├── README.md ├── critical-css-widget.js ├── critical-css-widget.min.js ├── critical-css-widget.png ├── externs.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | node_modules/* 3 | closure-compiler 4 | closure-compiler/* 5 | sftp-config.json 6 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* global module:false */ 2 | module.exports = function(grunt) { 3 | 4 | // Project configuration 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | meta: {}, 8 | 9 | 'closure-compiler': { 10 | "build": { 11 | closurePath: '../closure-compiler/', 12 | js: 'critical-css-widget.js', 13 | jsOutputFile: './critical-css-widget.min.js', 14 | noreport: true, 15 | maxBuffer: 500, 16 | options: { 17 | compilation_level: 'ADVANCED_OPTIMIZATIONS', 18 | language_in: 'ECMASCRIPT5_STRICT', 19 | externs: ['externs.js'] 20 | } 21 | } 22 | } 23 | }); 24 | 25 | // Load Dependencies 26 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 27 | 28 | grunt.registerTask('build', ['closure-compiler']); 29 | 30 | grunt.registerTask('default', ['build']); 31 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Critical CSS Widget [DEPRECATED] 2 | 3 | **This widget is deprecated and may not work in newer browsers.** 4 | 5 | Use our new widget: [https://github.com/pagespeed-pro/widget](https://github.com/pagespeed-pro/widget). 6 | 7 | --- 8 | 9 | ## About 10 | 11 | A simple browser widget to extract Critical CSS and Full CSS from a page that can be used via the browser console. 12 | 13 | The widget is based on a concept by Paul Kinlan, head of Chrome webdeveloper relations team. 14 | 15 | https://gist.github.com/PaulKinlan/6284142 16 | 17 | The snippet uses a Chrome innovation called `getMatchedCSSRules` which is deprecated and will be removed in Chrome 63. The Critical CSS Widget is made cross browser using a [polyfill](https://github.com/ovaldi/getMatchedCSSRules) for `getMatchedCSSRules`. 18 | 19 | ## Usage 20 | 21 | Copy & paste the widget javascript code directly in the browser console (F12) and use the following methods with an optional callback to extract critical CSS. 22 | 23 | ### Extract Critical CSS 24 | 25 | ```javascript 26 | // file download 27 | critical.extract(); 28 | 29 | // callback 30 | critical.extract('critical',function(css) { 31 | console.log('Extracted critical CSS:',css); 32 | }); 33 | ``` 34 | 35 | ### Extract Full CSS 36 | 37 | ```javascript 38 | // file download 39 | critical.extract('full'); 40 | 41 | // callback 42 | critical.extract('full',function(css) { 43 | console.log('Extracted Full CSS:',css); 44 | }); 45 | ``` 46 | 47 | ### Copy & Paste Instant Extract 48 | 49 | The following code will instantly start a Critical CSS download after pasting the code into the browser console. 50 | 51 | ```javascript 52 | (function(d,c,s) {s=d.createElement('script');s.async=true;s.onload=c;s.src='https://raw.githack.com/style-tools/critical-css-widget/master/critical-css-widget.min.js';d.head.appendChild(s);})(document,function() { 53 | // critical css file download 54 | critical.extract(); 55 | }); 56 | ``` 57 | 58 | The snippet uses [raw.githack.com](https://raw.githack.com/). You can also directly copy the code from Github and insert it in the browser console: 59 | 60 | https://github.com/style-tools/critical-css-widget/blob/master/critical-css-widget.min.js -------------------------------------------------------------------------------- /critical-css-widget.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Critical CSS Widget 3 | * @link https://github.com/style-tools/critical-css-widget 4 | */ 5 | (function(window) { 6 | 7 | /** 8 | * Critical CSS extraction 9 | * 10 | * Based on a concept by PaulKinlan 11 | * @link https://gist.github.com/PaulKinlan/6284142 12 | * 13 | * Made cross browser using 14 | * @link https://github.com/ovaldi/getMatchedCSSRules 15 | */ 16 | var CSSCriticalPath = function(w, d, opts) { 17 | var opt = opts || {}; 18 | var css = {}; 19 | var inlineCount = 0; 20 | var pushCSS = function(r) { 21 | 22 | var stylesheetFile = r.parentStyleSheet.href; 23 | if (!stylesheetFile) { 24 | inlineCount++; 25 | stylesheetFile = 'inline'; 26 | } else { 27 | stylesheetFile = stylesheetFile; 28 | } 29 | if (!!css[stylesheetFile] === false) { 30 | css[stylesheetFile] = { 31 | media: r.parentStyleSheet.media, 32 | css: {} 33 | }; 34 | } 35 | 36 | if (!!css[stylesheetFile].css[r.selectorText] === false) { 37 | css[stylesheetFile].css[r.selectorText] = {}; 38 | } 39 | 40 | var styles = r.style.cssText.split(/;(?![A-Za-z0-9])/); 41 | for (var i = 0; i < styles.length; i++) { 42 | if (!!styles[i] === false) continue; 43 | var pair = styles[i].split(": "); 44 | pair[0] = pair[0].trim(); 45 | pair[1] = pair[1].trim(); 46 | css[stylesheetFile].css[r.selectorText][pair[0]] = pair[1]; 47 | } 48 | }; 49 | 50 | var parseTree = function() { 51 | // Get a list of all the elements in the view. 52 | var height = w.innerHeight; 53 | var walker = d.createTreeWalker(d, NodeFilter.SHOW_ELEMENT, function(node) { 54 | return NodeFilter.FILTER_ACCEPT; 55 | }, true); 56 | 57 | while (walker.nextNode()) { 58 | var node = walker.currentNode; 59 | var rect = node.getBoundingClientRect(); 60 | if (rect.top < height || opt.scanFullPage) { 61 | var rules = w.getMatchedCSSRules(node); 62 | if (!!rules) { 63 | for (var r = 0; r < rules.length; r++) { 64 | pushCSS(rules[r]); 65 | } 66 | } 67 | } 68 | } 69 | }; 70 | 71 | this.generateCSS = function() { 72 | var finalCSS = ""; 73 | 74 | var printConsole = (console && console.groupCollapsed); 75 | var consoleCSS; 76 | var cssRule; 77 | var title; 78 | 79 | var fileReferences = []; 80 | 81 | if (console.clear) { 82 | console.clear(); 83 | } 84 | 85 | if (printConsole) { 86 | console.log("%cSimple Critical CSS Extraction", "font-size:24px;font-weight:bold"); 87 | console.log("For professional Critical CSS generators, see https://github.com/addyosmani/critical-path-css-tools"); 88 | } 89 | 90 | for (var file in css) { 91 | 92 | if (printConsole) { 93 | title = (file === 'inline') ? 'Inline' : 'File: ' + file; 94 | console.groupCollapsed(title); 95 | consoleCSS = ''; 96 | } 97 | 98 | // line number 99 | var lineNo = finalCSS.split(/\r\n|\r|\n/).length; 100 | fileReferences.push([file, lineNo]); 101 | 102 | finalCSS += "/**\n * @file " + file; 103 | if (css[file].media && (css[file].media.length > 1 || css[file].media[0] !== 'all')) { 104 | var media = []; 105 | for (var i = 0; i < css[file].media.length; i++) { 106 | if (!css[file].media[i]) { 107 | continue; 108 | } 109 | media.push(css[file].media[i]); 110 | } 111 | if (media.length > 0) { 112 | media = media.join(' '); 113 | finalCSS += "\n * @media " + media; 114 | } 115 | } 116 | finalCSS += "\n */\n"; 117 | for (var k in css[file].css) { 118 | 119 | cssRule = k + " { "; 120 | for (var j in css[file].css[k]) { 121 | cssRule += j + ": " + css[file].css[k][j] + "; "; 122 | } 123 | cssRule += "}" + "\n"; 124 | 125 | finalCSS += cssRule; 126 | 127 | if (printConsole) { 128 | consoleCSS += cssRule; 129 | } 130 | } 131 | finalCSS += "\n"; 132 | 133 | if (printConsole) { 134 | console.log(consoleCSS); 135 | console.groupEnd(); 136 | } 137 | } 138 | 139 | if (printConsole) { 140 | console.groupCollapsed('All Extracted Critical CSS (' + humanFileSize(finalCSS.length) + ')'); 141 | } else { 142 | console.log('%cAll:', "font-weight:bold"); 143 | } 144 | console.log(finalCSS); 145 | 146 | if (printConsole) { 147 | console.groupEnd(); 148 | } 149 | 150 | return [finalCSS, fileReferences]; 151 | }; 152 | 153 | parseTree(); 154 | }; 155 | 156 | /** 157 | * Full CSS extraction 158 | * 159 | * Based on CSSSteal (chrome plugin) 160 | * @link https://github.com/krasimir/css-steal 161 | */ 162 | var CSSSteal = function() { 163 | var elements = [document.body], 164 | html = null, 165 | styles = [], 166 | indent = ' '; 167 | 168 | var getHTMLAsString = function() { 169 | return elements.outerHTML; 170 | }; 171 | var toArray = function(obj, ignoreFalsy) { 172 | var arr = [], 173 | i; 174 | 175 | for (i = 0; i < obj.length; i++) { 176 | if (!ignoreFalsy || obj[i]) { 177 | arr[i] = obj[i]; 178 | } 179 | } 180 | return arr; 181 | } 182 | var getRules = function(a) { 183 | var sheets = document.styleSheets, 184 | result = [], 185 | selectorText; 186 | 187 | a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector; 188 | for (var i in sheets) { 189 | try { 190 | var rules = sheets[i].rules || sheets[i].cssRules; 191 | } catch (e) { 192 | continue; 193 | } 194 | for (var r in rules) { 195 | selectorText = rules[r].selectorText ? rules[r].selectorText.split(' ').map(function(piece) { 196 | return piece ? piece.split(/(:|::)/)[0] : false; 197 | }).join(' ') : false; 198 | try { 199 | if (a.matches(selectorText)) { 200 | result.push(rules[r]); 201 | } 202 | } catch (e) { 203 | // can not run matches on this selector 204 | } 205 | } 206 | } 207 | return result; 208 | } 209 | var readStyles = function(els) { 210 | return els.reduce(function(s, el) { 211 | s.push(getRules(el)); 212 | s = s.concat(readStyles(toArray(el.children))); 213 | return s; 214 | }, []); 215 | }; 216 | var flattenRules = function(s) { 217 | var filterBySelector = function(selector, result) { 218 | return result.filter(function(item) { 219 | return item.selector === selector; 220 | }); 221 | } 222 | var getItem = function(selector, result) { 223 | var arr = filterBySelector(selector, result); 224 | return arr.length > 0 ? arr[0] : { 225 | selector: selector, 226 | styles: {} 227 | }; 228 | } 229 | var pushItem = function(item, result) { 230 | var arr = filterBySelector(item.selector, result); 231 | if (arr.length === 0) result.push(item); 232 | } 233 | var all = []; 234 | s.forEach(function(rules) { 235 | rules.forEach(function(rule) { 236 | var item = getItem(rule.selectorText, all); 237 | for (var i = 0; i < rule.style.length; i++) { 238 | var property = rule.style[i]; 239 | item.styles[property] = rule.style.getPropertyValue(property); 240 | } 241 | pushItem(item, all); 242 | }); 243 | }); 244 | return all; 245 | }; 246 | 247 | html = getHTMLAsString(); 248 | styles = flattenRules(readStyles(elements)); 249 | 250 | return styles.reduce(function(text, item) { 251 | text += item.selector + ' {\n'; 252 | text += Object.keys(item.styles).reduce(function(lines, prop) { 253 | lines.push(indent + prop + ': ' + item.styles[prop] + ';'); 254 | return lines; 255 | }, []).join('\n'); 256 | text += '\n}\n'; 257 | return text; 258 | }, ''); 259 | 260 | }; 261 | 262 | /** 263 | * Cross Browser getMatchedCSSRules 264 | * @link https://github.com/ovaldi/getMatchedCSSRules 265 | */ 266 | (function(factory, global) { 267 | global.getMatchedCSSRules = factory(); 268 | })((function(win) { 269 | 270 | function matchMedia(mediaRule) { 271 | return window.matchMedia(mediaRule.media.mediaText).matches; 272 | } 273 | 274 | function matchesSelector(el, selector) { 275 | var matchesSelector = el.matchesSelector || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector; 276 | 277 | if (matchesSelector) { 278 | try { 279 | return matchesSelector.call(el, selector); 280 | } catch (e) { 281 | return false; 282 | } 283 | } else { 284 | var matches = el.ownerDocument.querySelectorAll(selector), 285 | len = matches.length; 286 | 287 | while (len && len--) { 288 | if (matches[len] === el) { 289 | return true; 290 | } 291 | } 292 | } 293 | return false; 294 | } 295 | 296 | function getMatchedCSSRules(el) { 297 | var matchedRules = [], 298 | sheets = el.ownerDocument.styleSheets, 299 | slen = sheets.length, 300 | rlen, rules, mrules, mrlen, rule, mediaMatched; 301 | 302 | if (el.nodeType === 1) { 303 | while (slen && slen--) { 304 | try { 305 | rules = sheets[slen].cssRules || sheets[slen].rules; 306 | } catch (e) { 307 | continue; 308 | } 309 | rlen = rules.length; 310 | 311 | while (rlen && rlen--) { 312 | rule = rules[rlen]; 313 | if (rule instanceof CSSStyleRule && matchesSelector(el, rule.selectorText)) { 314 | matchedRules.push(rule); 315 | } else if (rule instanceof CSSMediaRule) { 316 | if (matchMedia(rule)) { 317 | try { 318 | mrules = rule.cssRules || rule.rules; 319 | } catch (e) { 320 | continue; 321 | } 322 | mrlen = mrules.length; 323 | while (mrlen && mrlen--) { 324 | rule = mrules[mrlen]; 325 | if (rule instanceof CSSStyleRule && matchesSelector(el, rule.selectorText)) { 326 | matchedRules.push(rule); 327 | } 328 | } 329 | } 330 | } 331 | } 332 | } 333 | } 334 | 335 | return matchedRules; 336 | } 337 | 338 | return function() { 339 | return window.getMatchedCSSRules ? window.getMatchedCSSRules : getMatchedCSSRules; 340 | }; 341 | })(window), this); 342 | 343 | /* FileSaver.js 344 | * A saveAs() FileSaver implementation. 345 | * 1.3.4 346 | * 2018-01-12 13:14:0 347 | * 348 | * By Eli Grey, http://eligrey.com 349 | * License: MIT 350 | * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md 351 | */ 352 | 353 | /*global self */ 354 | /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ 355 | 356 | /* @link http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ 357 | 358 | var saveAs = saveAs || (function(view) { 359 | "use strict"; 360 | // IE <10 is explicitly unsupported 361 | if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { 362 | return; 363 | } 364 | var 365 | doc = view.document 366 | // only get URL when necessary in case Blob.js hasn't overridden it yet 367 | , 368 | get_URL = function() { 369 | return view.URL || view.webkitURL || view; 370 | }, 371 | save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a"), 372 | can_use_save_link = "download" in save_link, 373 | click = function(node) { 374 | var event = new MouseEvent("click"); 375 | node.dispatchEvent(event); 376 | }, 377 | is_safari = /constructor/i.test(view.HTMLElement) || view.safari, 378 | is_chrome_ios = /CriOS\/[\d]+/.test(navigator.userAgent), 379 | throw_outside = function(ex) { 380 | (view.setImmediate || view.setTimeout)(function() { 381 | throw ex; 382 | }, 0); 383 | }, 384 | force_saveable_type = "application/octet-stream" 385 | // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to 386 | , 387 | arbitrary_revoke_timeout = 1000 * 40 // in ms 388 | , 389 | revoke = function(file) { 390 | var revoker = function() { 391 | if (typeof file === "string") { // file is an object URL 392 | get_URL().revokeObjectURL(file); 393 | } else { // file is a File 394 | file.remove(); 395 | } 396 | }; 397 | setTimeout(revoker, arbitrary_revoke_timeout); 398 | }, 399 | dispatch = function(filesaver, event_types, event) { 400 | event_types = [].concat(event_types); 401 | var i = event_types.length; 402 | while (i--) { 403 | var listener = filesaver["on" + event_types[i]]; 404 | if (typeof listener === "function") { 405 | try { 406 | listener.call(filesaver, event || filesaver); 407 | } catch (ex) { 408 | throw_outside(ex); 409 | } 410 | } 411 | } 412 | }, 413 | auto_bom = function(blob) { 414 | // prepend BOM for UTF-8 XML and text/* types (including HTML) 415 | // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF 416 | if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { 417 | return new Blob([String.fromCharCode(0xFEFF), blob], { 418 | type: blob.type 419 | }); 420 | } 421 | return blob; 422 | }, 423 | FileSaver = function(blob, name, no_auto_bom) { 424 | if (!no_auto_bom) { 425 | blob = auto_bom(blob); 426 | } 427 | // First try a.download, then web filesystem, then object URLs 428 | var 429 | filesaver = this, 430 | type = blob.type, 431 | force = type === force_saveable_type, 432 | object_url, dispatch_all = function() { 433 | dispatch(filesaver, "writestart progress write writeend".split(" ")); 434 | } 435 | // on any filesys errors revert to saving with object URLs 436 | , 437 | fs_error = function() { 438 | if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { 439 | // Safari doesn't allow downloading of blob urls 440 | var reader = new FileReader(); 441 | reader.onloadend = function() { 442 | var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); 443 | var popup = view.open(url, '_blank'); 444 | if (!popup) view.location.href = url; 445 | url = undefined; // release reference before dispatching 446 | filesaver.readyState = filesaver.DONE; 447 | dispatch_all(); 448 | }; 449 | reader.readAsDataURL(blob); 450 | filesaver.readyState = filesaver.INIT; 451 | return; 452 | } 453 | // don't create more object URLs than needed 454 | if (!object_url) { 455 | object_url = get_URL().createObjectURL(blob); 456 | } 457 | if (force) { 458 | view.location.href = object_url; 459 | } else { 460 | var opened = view.open(object_url, "_blank"); 461 | if (!opened) { 462 | // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html 463 | view.location.href = object_url; 464 | } 465 | } 466 | filesaver.readyState = filesaver.DONE; 467 | dispatch_all(); 468 | revoke(object_url); 469 | }; 470 | filesaver.readyState = filesaver.INIT; 471 | 472 | if (can_use_save_link) { 473 | object_url = get_URL().createObjectURL(blob); 474 | setTimeout(function() { 475 | save_link.href = object_url; 476 | save_link.download = name; 477 | click(save_link); 478 | dispatch_all(); 479 | revoke(object_url); 480 | filesaver.readyState = filesaver.DONE; 481 | }); 482 | return; 483 | } 484 | 485 | fs_error(); 486 | }, 487 | FS_proto = FileSaver.prototype, 488 | saveAs = function(blob, name, no_auto_bom) { 489 | return new FileSaver(blob, name || blob.name || "download", no_auto_bom); 490 | }; 491 | // IE 10+ (native saveAs) 492 | if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { 493 | return function(blob, name, no_auto_bom) { 494 | name = name || blob.name || "download"; 495 | 496 | if (!no_auto_bom) { 497 | blob = auto_bom(blob); 498 | } 499 | return navigator.msSaveOrOpenBlob(blob, name); 500 | }; 501 | } 502 | 503 | FS_proto.abort = function() {}; 504 | FS_proto.readyState = FS_proto.INIT = 0; 505 | FS_proto.WRITING = 1; 506 | FS_proto.DONE = 2; 507 | 508 | FS_proto.error = 509 | FS_proto.onwritestart = 510 | FS_proto.onprogress = 511 | FS_proto.onwrite = 512 | FS_proto.onabort = 513 | FS_proto.onerror = 514 | FS_proto.onwriteend = 515 | null; 516 | 517 | return saveAs; 518 | }( 519 | window 520 | )); 521 | 522 | // public extract Critical CSS method 523 | function extractCriticalCSS(callback) { 524 | 525 | var cp = new CSSCriticalPath(window, document); 526 | var result = cp.generateCSS(); 527 | var css = result[0]; 528 | var files = result[1]; 529 | 530 | try { 531 | var isFileSaverSupported = (callback) ? true : !!new Blob; 532 | } catch (e) {} 533 | 534 | if (!isFileSaverSupported) { 535 | alert('Your browser does not support javascript based file download. The critical CSS is printed in the console.') 536 | } else { 537 | 538 | var criticalCSS = "/**\n * Simple Critical CSS\n *\n * @url " + document.location.href + "\n * @title " + document.title + "\n * @viewport " + window.innerWidth + "x" + window.innerHeight + "\n * @size " + humanFileSize(css.length) + 539 | "\n *\n * Extracted using Critical CSS Widget.\n * @link https://github.com/critical-x/critical-css-widget\n *\n * For professional Critical CSS generators see https://github.com/addyosmani/critical-path-css-tools\n *\n * @sources"; 540 | 541 | var hlines = criticalCSS.split(/\r\n|\r|\n/).length; 542 | hlines += files.length + 3; 543 | for (var i = 0; i < files.length; i++) { 544 | criticalCSS += "\n * @line " + (files[i][1] + hlines) + "\t @file " + files[i][0]; 545 | } 546 | criticalCSS += "\n */\n\n"; 547 | criticalCSS += css; 548 | 549 | if (callback) { 550 | return callback(criticalCSS); 551 | } 552 | 553 | var blob = new Blob([criticalCSS], { 554 | type: "text/css;charset=utf-8" 555 | }); 556 | var path = window.location.pathname; 557 | if (path && path !== '/' && path.indexOf('/') !== -1) { 558 | path = '-' + path.replace(/\/$/, '').split('/').pop(); 559 | } else { 560 | path = '-front-page'; 561 | } 562 | var filename = 'critical-css' + path + '.css'; 563 | saveAs(blob, filename); 564 | } 565 | }; 566 | 567 | // public extract Full CSS method 568 | function extractFullCSS(callback) { 569 | var css = CSSSteal(); 570 | 571 | try { 572 | var isFileSaverSupported = (callback) ? true : !!new Blob; 573 | } catch (e) {} 574 | 575 | if (console.clear) { 576 | console.clear(); 577 | } 578 | 579 | console.log("%cFull CSS Extraction", "font-size:24px;font-weight:bold"); 580 | 581 | if (console.groupCollapsed) { 582 | console.groupCollapsed('Extracted Full CSS (' + humanFileSize(css.length) + ')'); 583 | } 584 | console.log(css); 585 | if (console.groupCollapsed) { 586 | console.groupEnd(); 587 | } 588 | 589 | if (!isFileSaverSupported) { 590 | alert('Your browser does not support javascript based file download. The full CSS is printed in the console.') 591 | } else { 592 | 593 | var fullcss = "/**\n * Full CSS\n *\n * @url " + document.location.href + "\n * @title " + document.title + "\n * @size " + humanFileSize(css.length) + "\n *\n * Extracted using Critical CSS Widget.\n * @link https://github.com/critical-x/critical-css-widget\n */\n\n" + 594 | css; 595 | 596 | if (callback) { 597 | return callback(fullcss); 598 | } 599 | 600 | var blob = new Blob([fullcss], { 601 | type: "text/css;charset=utf-8" 602 | }); 603 | var path = window.location.pathname; 604 | if (path && path !== '/' && path.indexOf('/') !== -1) { 605 | path = '-' + path.replace(/\/$/, '').split('/').pop(); 606 | } else { 607 | path = '-front-page'; 608 | } 609 | var filename = 'full-css' + path + '.css'; 610 | saveAs(blob, filename); 611 | } 612 | }; 613 | 614 | function humanFileSize(size) { 615 | var i = Math.floor(Math.log(size) / Math.log(1024)); 616 | return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB'][i]; 617 | }; 618 | 619 | // public controller 620 | function critical() {}; 621 | 622 | // use existing client 623 | if (window.critical) { 624 | // extend existing public controller 625 | var criticalproto = window.critical.constructor.prototype; 626 | } else { 627 | window.critical = new critical; 628 | var criticalproto = critical.prototype; 629 | } 630 | 631 | // public extract method 632 | // window.critical.extract(type,callback) 633 | criticalproto.extract = function(type, callback) { 634 | if (type === 'full') { 635 | extractFullCSS(callback); 636 | } else { 637 | extractCriticalCSS(callback); 638 | } 639 | return 'Starting extract...'; 640 | } 641 | })(window); -------------------------------------------------------------------------------- /critical-css-widget.min.js: -------------------------------------------------------------------------------- 1 | (function(k){function x(){function a(c){return c.reduce(function(c,e){c.push(b(e));e=e.children;var m=[],h;for(h=0;h