Skip to content
This repository has been archived by the owner on Oct 21, 2022. It is now read-only.

Commit

Permalink
Merge pull request #50 from filamentgroup/original-definitions
Browse files Browse the repository at this point in the history
Original definitions
  • Loading branch information
scottjehl authored Sep 6, 2016
2 parents c33cc26 + b61da9a commit 83704a2
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 0 deletions.
106 changes: 106 additions & 0 deletions critical.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
var fs = require( "fs" );
var os = require( "os" );
var postcss = require( "postcss" );
var css = require('css');
var _ = require("lodash");

var DEFAULT_BUFFER_SIZE = 800*1024; //had this as the set val before, don't want to break things

Expand Down Expand Up @@ -165,4 +167,108 @@

};

// create a function that can be passed to `map` for a collection of critical
// css rules. The function will match original rules against the selector of
// the critical css declarations, concatenate them together, and then keep
// only the unique ones
function replaceDecls(originalRules, check){
return function(criticalRule){
// restrict the declaration mapping to a certain type of rule, e.g. 'rule'
// or 'media'
if(check && !check(criticalRule.type)){
return criticalRule;
}

// find all the rules in the original CSS that have the same selectors and
// then create an array of all the associated declarations. Note that this
// works with mutiple duplicate selectors on the original CSS
var originalDecls = _.flatten(
originalRules
.filter(function(rule){
return _.isEqual(rule.selectors, criticalRule.selectors);
})
.map(function(rule){
return rule.declarations;
})
);

// take all the declarations that were found from the original CSS and use
// them here, make sure that we de-dup any declarations from the original CSS
criticalRule.declarations =
_.uniqBy(criticalRule.declarations.concat(originalDecls), function(decl){
return decl.property + ":" + decl.value;
});

return criticalRule;
};
}

var nested = ["media", "supports", "document"];

exports.restoreOriginalDefs = function(originalCSS, criticalCSS, stringifyOpts){
// parse both the original CSS and the critical CSS so we can deal with the
// ASTs directly
var originalAST = css.parse(originalCSS);
var criticalAST = css.parse(criticalCSS);

var newRules;

// run two maps over the rules in the critical CSS
//
// 1. map the top level rules to rules where the declarations are replaced
// by the declarations from the same selectors in the original CSS
// 2. map the media query rules to rules where the declarations are replaced
// by the declarations from the same selectors in the same media queries
// in the original CSS
newRules = criticalAST
.stylesheet
.rules
// for all the top level rules that are in the criticalCSS AST replace them
// with the original rule definitions from the original AST
.map(replaceDecls(originalAST.stylesheet.rules, function(ruleType){
// only deal with top level rules here
return ruleType === "rule";
}))
// for all the rules that are in media queries match the same media
// queries in the original and use the rules from there
.map(function(criticalNested){
// handle media rules only here
if( nested.indexOf(criticalNested.type) == -1){
return criticalNested;
}

var type = criticalNested.type;

// find all the rules that apply for the current media query
var originalMediaRules;

// get all of the rules inside media queries that match the critical
// media query media string
originalMediaRules = _.flatten(
originalAST
.stylesheet
.rules
.filter(function(rule){
return rule[type] == criticalNested[type];
})
.map(function(media){
return media.rules;
})
);

// replace the declarations in each of the rules for this media query
// with the declarations in the original css for the same media query
criticalNested.rules = criticalNested
.rules
.map(replaceDecls(originalMediaRules));

return criticalNested;
});

criticalAST.stylesheet.rules = newRules;

// return the CSS as a string
return css.stringify(criticalAST, stringifyOpts);
};

}(typeof exports === "object" && exports || this));
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
"test": "node node_modules/.bin/grunt test"
},
"dependencies": {
"css": "^2.2.1",
"lodash": "^4.15.0",
"phantomjs-prebuilt": "^2.1.3",
"postcss": "^5.0.19",
"postcss-initial": "^1.5.1",
Expand Down
8 changes: 8 additions & 0 deletions test/files/media-critical.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@media (min-width:50em){
.bio{font-size:1em;}
.bio img{float:left;}
}

.baz {}

.foo {}
14 changes: 14 additions & 0 deletions test/files/media-expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@media (min-width:50em){
.bio{
font-size:1em;
color: red;
}
.bio img {
float:left;
-moz-box-sizing:content-box;
}
}

.baz{font-size:1em;}

.foo{font-size:1em;color:blue;}
21 changes: 21 additions & 0 deletions test/files/media.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.foo{font-size:1em;}
.foo{color:blue;}
.bar{font-size:1em;}

@media (min-width:50em){
.bio{font-size:1em;}
.bio img {
float:left;
-moz-box-sizing:content-box;
}
}

@media (min-width:1000em){
.bio{font-size:3em;}
}

.baz{font-size:1em;}

@media (min-width:50em){
.bio{color: red;}
}
11 changes: 11 additions & 0 deletions test/files/nested-critical.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@media (min-width:50em){
.bio{}
}

@supports (box-shadow:50em){
.bio{}
}

@document url(http://example.com) {
.bio{background-color: blue;}
}
11 changes: 11 additions & 0 deletions test/files/nested-expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@media (min-width:50em){
.bio{color: red;}
}

@supports (box-shadow:50em){
.bio{width: 100%;}
}

@document url(http://example.com) {
.bio{background-color: blue;border-size: 1px;}
}
11 changes: 11 additions & 0 deletions test/files/nested.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@media (min-width:50em){
.bio{color: red;}
}

@supports (box-shadow:50em){
.bio{width: 100%;}
}

@document url(http://example.com) {
.bio{border-size: 1px;}
}
75 changes: 75 additions & 0 deletions test/unit/original_definitions_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*global require:true*/
(function( exports ){
"use strict";

var path = require("path");
var critical = require(path.join( "..", "..", "critical.js") );
var fs = require("fs");

function readTestCSSFile(name){
return fs
.readFileSync(path.join(__dirname, "..", "files", name + ".css"))
.toString();
}

function testDefs(test, opts) {
test.expect(1);

var result = critical
.restoreOriginalDefs(opts.original, opts.critical, { compress: true })
.replace(/\s/g, "");

test.equal(result, opts.expected.replace(/\s/g, ""));
test.done();
}

exports.restoreOriginalDefs = {
"adds stripped definitions": function(test) {
testDefs(test, {
original: "body { color: red; }",
critical: "body {}",
expected: "body { color:red; }"
});
},

"adds muliple stripped definitions": function(test) {
testDefs(test, {
original: "body { color: red; font-size: 20px; }",
critical: "body {}",
expected: "body { color:red; font-size:20px; }"
});
},

"does not include removed selectors": function(test) {
testDefs(test, {
original: "body { color: red; } div.removed {}",
critical: "body {}",
expected: "body { color:red; }"
});
},

"includes media queries": function(test) {
testDefs(test, {
original: "@media (max-width: 600px) { body { color: red; } } @media (max-width: 400px) { body { color: red; } }",
critical: "@media (max-width: 600px) { body {} }",
expected: "@media (max-width: 600px) { body { color:red; } }"
});
},

"includes complex media queries": function(test) {
testDefs(test, {
original: readTestCSSFile("media"),
critical: readTestCSSFile("media-critical"),
expected: readTestCSSFile("media-expected")
});
},

"includes nested rules": function(test) {
testDefs(test, {
original: readTestCSSFile("nested"),
critical: readTestCSSFile("nested-critical"),
expected: readTestCSSFile("nested-expected")
});
}
};
}(typeof exports === "object" && exports || this));

0 comments on commit 83704a2

Please sign in to comment.