Skip to content

Commit

Permalink
Merge pull request #7 from hummal/develop
Browse files Browse the repository at this point in the history
Wildcards FTW
  • Loading branch information
philipp-winterle committed Jul 31, 2018
2 parents 5f8f9df + 44416fb commit ae173e7
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 29 deletions.
28 changes: 25 additions & 3 deletions README.md
Expand Up @@ -165,8 +165,8 @@ The CLI usage is not implemented yet :scream:. At the moment there is no need of
| puppeteer | Object | Optional. Configuration object of puppeteer options like an already existing browser instance or a path to a Chrome instead of the used Chromium. See documentation for [puppeteer object](#puppeteer-options). |
| printBrowserConsole | Boolean | Optional. If set to true prints console output of urls to the stdoutput. Defaults: false |
| dropKeyframes | Boolean | Optional. If set to false keeps keyframes as critical css content. Otherwise they are removed. Default: false |
| keepSelectors | Array | Optional. Every CSS Selector in this array will be kept as part of the critical css even if they are not part of it. Default: [] |
| removeSelectors: | Array | Optional. Every CSS Selector in this array will be removed of the critical css even if they are part of it. Default: [] |
| keepSelectors | Array | Optional. Every CSS Selector in this array will be kept as part of the critical css even if they are not part of it. You can use wildcards (%) to capture more rules with one entry. [Read more](#wildcards). Default: [] |
| removeSelectors: | Array | Optional. Every CSS Selector in this array will be removed of the critical css even if they are part of it. You can use wildcards (%) to capture more rules with one entry. [Read more](#wildcards). Default: [] |
| blockRequests | Array | Optional. Some of the requests made by pages are an |

### Browser options
Expand Down Expand Up @@ -197,6 +197,28 @@ The CLI usage is not implemented yet :scream:. At the moment there is no need of
| chromePath | String | Optional. Path to other Chrome or Chromium executable/bin file to use. . Default: Default Chromium shipped with puppeteer |
| headless: | Boolean | Optional. If set to false the browser will launch with GUI to be visible while processing. Default: true |

## Wildcards
You are already able to define the selectors to force keep or remove. With wildcards you can define a range of selectors to match against one entry in force selectors. The wildcard symbol is the `%` character. It can put at the beginning or end of a string. Take care of whitespaces between the selector string and the `%` as it will count as a character. Let's have a quick example:

```javascript
const {critical, rest} = await Crittr({
urls: urls,
css: css,
keepSelectors: [
".test %"
]
});
```

This keepSelectors options will match every selector that begins with `.test` and has no further selectors attached. Means `.test.test2`wouldn't match because there is a whitespace in there. But it will match `.test .test2 .test3`. Also this example wouldn't match selectors like this:

```css
.pre .test .test2 {} /* no match */
.pre.test .test2 {} /* no match */
.test .test2 {} /* match */
.test.test2 {} /* no match */
.test .test2:before {} /* match */
```

## FAQ :confused:
- Why do I need to put my css file in when I only want to extract the critical css?
Expand All @@ -207,7 +229,7 @@ The CLI usage is not implemented yet :scream:. At the moment there is no need of
## Upcoming :trumpet:

- [ ] :star: cookie includes
- [ ] :star: wildcards
- [x] :star: wildcards
- [ ] :grey_question: positioning of critical css rules
- [x] :+1: compress output option
- [x] :fire: return of the remaining css aswell
Expand Down
12 changes: 10 additions & 2 deletions index.js
Expand Up @@ -19,10 +19,18 @@ const Crittr = require(path.join(__dirname, pathToCrittr, 'classes', 'Crit
module.exports = (options) => {
return new Promise(async (resolve, reject) => {
log.time("Crittr Run");
const crttr = new Crittr(options);

let crittr;
let resultObj = {critical: null, rest: null};

try {
crittr = new Crittr(options);
} catch (err) {
reject(err);
}

try {
(resultObj = await crttr.run());
(resultObj = await crittr.run());
} catch (err) {
reject(err);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/classes/Crittr.class.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/classes/CssTransformator.class.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/evaluation/extract_critical_with_css.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
@@ -1,6 +1,6 @@
{
"name": "crittr",
"version": "1.1.1",
"version": "1.2.0",
"description": "Crittr is a high performance critical css extraction library with a multiple url support.",
"author": "Philipp Winterle",
"license": "GPL-3.0",
Expand Down Expand Up @@ -38,7 +38,7 @@
"fs-extra": "^7.0.0",
"lodash": "^4.17.10",
"object-hash": "^1.3.0",
"puppeteer": "1.6.*",
"puppeteer": "^1.6.1",
"run-queue": "^1.0.3",
"signale": "^1.2.1",
"sort-css-media-queries": "^1.3.4"
Expand All @@ -62,7 +62,7 @@
],
"husky": {
"hooks": {
"pre-commit": "npm run build && git add lib/* && npm test"
"pre-commit": "npm run build && npm test && git status && git add lib/*"
}
}
}
33 changes: 21 additions & 12 deletions src/classes/Crittr.class.js
Expand Up @@ -115,7 +115,8 @@ class Crittr {
log.error(message);
});
// Exit process when options are invalid
process.exit(1);
throw new Error("crittr stopped working. See errors above.")

}
}

Expand All @@ -129,10 +130,16 @@ class Crittr {
// Check url
if (!Array.isArray(this.options.urls)) {
errors.push({
message: "Url not valid"
message: "Urls not an Array"
});
}

if (Array.isArray(this.options.urls) && this.options.urls.length === 0) {
console.log("FEHLER")
errors.push(new Error("NO URLs to check. Insert at least one url in the urls option!"));
}


if (typeof this.options.css !== "string") {
errors.push({
message: "css not valid. Expected string got " + typeof this.options.css
Expand Down Expand Up @@ -364,7 +371,6 @@ class Crittr {
// Create the Rule Maps for further iteration
debug("getCriticalCssFromUrls - Merging multiple atf ast objects. Size: " + criticalAstSets.size);
let atfRuleMap = new Map();
let criticalcss = "";
for (let astObj of criticalAstSets) {
try {
// Merge all extracted ASTs into a final one
Expand Down Expand Up @@ -424,27 +430,27 @@ class Crittr {
ieFilters: false, // controls keeping IE `filter` / `-ms-filter`
iePrefixHack: false, // controls keeping IE prefix hack
ieSuffixHack: false, // controls keeping IE suffix hack
merging: false, // controls property merging based on understandability
merging: true, // controls property merging based on understandability
shorterLengthUnits: false, // controls shortening pixel units into `pc`, `pt`, or `in` units
spaceAfterClosingBrace: false, // controls keeping space after closing brace - `url() no-repeat` into `url()no-repeat`
urlQuotes: false, // controls keeping quoting inside `url()`
spaceAfterClosingBrace: true, // controls keeping space after closing brace - `url() no-repeat` into `url()no-repeat`
urlQuotes: true, // controls keeping quoting inside `url()`
zeroUnits: false // controls removal of units `0` value
},
selectors: {
adjacentSpace: false, // controls extra space before `nav` element
ie7Hack: false, // controls removal of IE7 selector hacks, e.g. `*+html...`
mergeLimit: 8191, // controls maximum number of selectors in a single rule (since 4.1.0)
ie7Hack: true, // controls removal of IE7 selector hacks, e.g. `*+html...`
mergeLimit: 1000, // controls maximum number of selectors in a single rule (since 4.1.0)
multiplePseudoMerging: false // controls merging of rules with multiple pseudo classes / elements (since 4.1.0)
},
level: {
1: {
all: false,
cleanupCharsets: true, // controls `@charset` moving to the front of a stylesheet; defaults to `true`
removeWhitespace: true // controls removing unused whitespace; defaults to `true`
removeWhitespace: false // controls removing unused whitespace; defaults to `true`
},
2: {
mergeAdjacentRules: true, // controls adjacent rules merging; defaults to true
mergeIntoShorthands: true, // controls merging properties into shorthands; defaults to true
mergeIntoShorthands: false, // controls merging properties into shorthands; defaults to true
mergeMedia: true, // controls `@media` merging; defaults to true
mergeNonAdjacentRules: true, // controls non-adjacent rule merging; defaults to true
mergeSemantically: false, // controls semantic merging; defaults to false
Expand Down Expand Up @@ -526,8 +532,6 @@ class Crittr {
let criticalAstObj = null;
let restAstObj = null;

// TODO: handle goto errors with retry

const getPage = async () => {
return new Promise((resolve, reject) => {
try {
Expand Down Expand Up @@ -614,6 +618,10 @@ class Crittr {
// startedRequests.splice(startedRequests.indexOf(request.url()), 1);
// });

page.on("error", err => {
hasError = err;
});

await page.emulate({
viewport: {
width: deviceOptions.width,
Expand All @@ -635,6 +643,7 @@ class Crittr {

// Go to destination page
if (hasError === false) {
// TODO: handle goto errors with retry
try {
debug("evaluateUrl - Navigating page to " + url);
await page.goto(url, {
Expand Down
2 changes: 1 addition & 1 deletion src/classes/CssTransformator.class.js
Expand Up @@ -151,7 +151,7 @@ class CssTransformator {
if (rule.rules) {
rule.rules = rule.rules.map(internalRule => {
const ruleKey = Rule.generateRuleKey(internalRule, media);
if (criticalSelectorsMap.has(ruleKey)) {
if (ruleKey !== false && criticalSelectorsMap.has(ruleKey)) {
const criticalSelectorsOfRule = criticalSelectorsMap.get(ruleKey);
internalRule.selectors = internalRule.selectors.filter(selector => !criticalSelectorsOfRule.includes(selector));
}
Expand Down
21 changes: 19 additions & 2 deletions src/evaluation/extract_critical_with_css.js
Expand Up @@ -114,12 +114,29 @@ module.exports = ({sourceAst, loadTimeout, keepSelectors, removeSelectors}) => {
*/
const isPurePseudo = selector => selector.startsWith(":") && selector.match(PSEUDO_EXCLUDED_REGEX) === null;

/**
* Creates a regex out of a wildcard selector. Returns the normal regex for a non wildcard selector
*
* @param {string} selector
* @returns {RegExp} {RegExp}
*/
const getRegexOfSelector = selector => {
selector = "^" + selector.replace(/\./g, "\\.").replace(/%/g, ".*") + "$";
return new RegExp(selector, "gm");
};

const isSelectorForceIncluded = selector => {
return keepSelectors.includes(selector);
return keepSelectors.includes(selector) || keepSelectors.some( tmpSel => {
const selectorWcRegex = getRegexOfSelector(tmpSel); // transform wildcards into regex
return selectorWcRegex.test(selector);
});
};

const isSelectorForceExcluded = selector => {
return removeSelectors.includes(selector);
return removeSelectors.includes(selector) || removeSelectors.some( tmpSel => {
const selectorWcRegex = getRegexOfSelector(tmpSel); // transform wildcards into regex
return selectorWcRegex.test(selector);
});
};

const isElementAboveTheFold = (element) => {
Expand Down

0 comments on commit ae173e7

Please sign in to comment.