critical-path-css-rails 0.4.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "critical-path-css-rails",
3
+ "version": "1.0.0",
4
+ "description": "NPM dependencies of critical-path-css-rails",
5
+ "private": true,
6
+ "directories": {
7
+ "lib": "lib"
8
+ },
9
+ "dependencies": {
10
+ "penthouse": "=0.11.5"
11
+ },
12
+ "license": "MIT"
13
+ }
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe 'CssFetcher' do
6
+ before :all do
7
+ StaticFileServer.start
8
+ end
9
+
10
+ after :all do
11
+ StaticFileServer.stop
12
+ end
13
+
14
+ it 'fetches css' do
15
+ config = CriticalPathCss::Configuration.new(
16
+ 'base_url' => StaticFileServer.url,
17
+ 'css_path' => 'spec/fixtures/static/test.css',
18
+ 'routes' => ['/test.html']
19
+ )
20
+ fetcher = CriticalPathCss::CssFetcher.new(config)
21
+ expect(fetcher.fetch).to(
22
+ eq('/test.html' => "p {\n color: red;\n}\n")
23
+ )
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ p {
2
+ color: red;
3
+ }
@@ -0,0 +1,7 @@
1
+ <!doctype html>
2
+ <head>
3
+ <link rel="stylesheet" href="test.css">
4
+ </head>
5
+ <body>
6
+ <p>Hello world</p>
7
+ </body>
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'critical-path-css-rails'
5
+
6
+ require 'support/static_file_server'
7
+
8
+ RSpec.configure do |config|
9
+ # Enable flags like --only-failures and --next-failure
10
+ config.example_status_persistence_file_path = '.rspec_status'
11
+
12
+ # Disable RSpec exposing methods globally on `Module` and `main`
13
+ config.disable_monkey_patching!
14
+
15
+ config.expect_with :rspec do |c|
16
+ c.syntax = :expect
17
+ end
18
+ end
@@ -0,0 +1,45 @@
1
+ require 'socket'
2
+
3
+ module StaticFileServer
4
+ class << self
5
+ def start # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
6
+ @port = get_free_port
7
+ rd, wt = IO.pipe
8
+ @pid = fork do
9
+ require 'webrick'
10
+ rd.close
11
+ server = WEBrick::HTTPServer.new(
12
+ DocumentRoot: File.expand_path('spec/fixtures/static'),
13
+ Port: @port,
14
+ BindAddress: '127.0.0.1',
15
+ StartCallback: lambda do
16
+ # write "1", signal a server start message
17
+ wt.write(1)
18
+ wt.close
19
+ end
20
+ )
21
+ trap('INT') { server.shutdown }
22
+ server.start
23
+ end
24
+ wt.close
25
+ # read a byte for the server start signal
26
+ rd.read(1)
27
+ rd.close
28
+ end
29
+
30
+ def stop
31
+ Process.kill('INT', @pid)
32
+ end
33
+
34
+ def url
35
+ "http://localhost:#{@port}"
36
+ end
37
+
38
+ def get_free_port
39
+ server = TCPServer.new('127.0.0.1', 0)
40
+ port = server.addr[1]
41
+ server.close
42
+ port
43
+ end
44
+ end
45
+ end
metadata CHANGED
@@ -1,33 +1,34 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: critical-path-css-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Misshore
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-04 00:00:00.000000000 Z
11
+ date: 2017-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: phantomjs
14
+ name: rspec
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.1.1
20
- type: :runtime
19
+ version: '3.6'
20
+ type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 2.1.1
26
+ version: '3.6'
27
27
  description: Only load the CSS you need for the initial viewport in Rails!
28
28
  email: mmisshore@gmail.com
29
29
  executables: []
30
- extensions: []
30
+ extensions:
31
+ - ext/npm/extconf.rb
31
32
  extra_rdoc_files: []
32
33
  files:
33
34
  - ".codeclimate.yml"
@@ -39,15 +40,26 @@ files:
39
40
  - LICENSE
40
41
  - README.md
41
42
  - critical-path-css-rails.gemspec
43
+ - ext/npm/extconf.rb
44
+ - ext/npm/install.rb
42
45
  - lib/config/critical_path_css.yml
43
46
  - lib/critical-path-css-rails.rb
44
47
  - lib/critical_path_css/configuration.rb
45
48
  - lib/critical_path_css/css_fetcher.rb
49
+ - lib/critical_path_css/rails/config_loader.rb
46
50
  - lib/critical_path_css/rails/engine.rb
47
51
  - lib/critical_path_css/rails/version.rb
52
+ - lib/fetch-css.js
48
53
  - lib/generators/critical_path_css/install_generator.rb
49
- - lib/penthouse/penthouse.js
54
+ - lib/npm_commands.rb
50
55
  - lib/tasks/critical_path_css.rake
56
+ - package-lock.json
57
+ - package.json
58
+ - spec/css_fetcher_spec.rb
59
+ - spec/fixtures/static/test.css
60
+ - spec/fixtures/static/test.html
61
+ - spec/spec_helper.rb
62
+ - spec/support/static_file_server.rb
51
63
  homepage:
52
64
  licenses:
53
65
  - MIT
@@ -68,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
80
  version: '0'
69
81
  requirements: []
70
82
  rubyforge_project:
71
- rubygems_version: 2.5.1
83
+ rubygems_version: 2.6.12
72
84
  signing_key:
73
85
  specification_version: 4
74
86
  summary: Critical Path CSS for Rails!
@@ -1,601 +0,0 @@
1
- /*
2
- Penthouse CSS Critical Path Generator
3
- https://github.com/pocketjoso/penthouse
4
- Author: Jonas Ohlsson
5
- License: MIT
6
- Version: 0.3.4
7
-
8
- USAGE:
9
- phantomjs penthouse.js [options] <URL to page> <CSS file>
10
- Options:
11
- --width <width> The viewport width in pixels. Defaults to 1300
12
- --height <height> The viewport height in pixels. Defaults to 900
13
-
14
- to run on HTTPS sites two flags must be passed in, directly after phantomjs in the call:
15
- --ignore-ssl-errors=true --ssl-protocol=tlsv1
16
-
17
- DEPENDENCIES
18
- + "phantomjs" : "~1.9.7"
19
-
20
- */
21
-
22
-
23
- (function() { "use strict";
24
- /*
25
- * parser for the script - can be used both for the standalone node binary and the phantomjs script
26
- */
27
-
28
- /*jshint unused:false*/
29
-
30
- var usageString = '[--width <width>] [--height <height>] <url> <main.css>';
31
-
32
- function buildError(msg, problemToken, args) {
33
- var error = new Error(msg + problemToken);
34
- error.token = problemToken;
35
- error.args = args;
36
- throw error;
37
- }
38
-
39
- // Parses the arguments passed in
40
- // @returns { width, height, url, css }
41
- // throws an error on wrong options or parsing error
42
- function parseOptions(argsOriginal) {
43
- var args = argsOriginal.slice(0),
44
- validOptions = ['--width', '--height'],
45
- parsed = {},
46
- val,
47
- len = args.length,
48
- optIndex,
49
- option;
50
-
51
- if (len < 2) buildError('Not enough arguments, ', args);
52
-
53
- while (args.length > 2 && args[0].match(/^(--width|--height)$/)) {
54
- optIndex = validOptions.indexOf(args[0]);
55
- if (optIndex === -1) buildError('Logic/Parsing error ', args[0], args);
56
-
57
- // lose the dashes
58
- option = validOptions[optIndex].slice(2);
59
- val = args[1];
60
-
61
- parsed[option] = parseInt(val, 10);
62
- if (isNaN(parsed[option])) buildError('Parsing error when parsing ', val, args);
63
-
64
- // remove the two parsed arguments from the list
65
- args = args.slice(2);
66
- }
67
- parsed.url = args[0];
68
- parsed.css = args[1];
69
-
70
- if (!parsed.url) {
71
- buildError('Missing url/path to html file', '', args);
72
- }
73
-
74
- if (!parsed.css) {
75
- buildError('Missing css file', '', args);
76
- }
77
-
78
-
79
- return parsed;
80
- }
81
-
82
- if (typeof module !== 'undefined') {
83
- module.exports = exports = {
84
- parse: parseOptions,
85
- usage: usageString
86
- };
87
- }
88
- /*
89
- module for removing unused fontface rules - can be used both for the standalone node binary and the phantomjs script
90
- */
91
- /*jshint unused:false*/
92
-
93
- function unusedFontfaceRemover (css){
94
- var toDeleteSections = [];
95
-
96
- //extract full @font-face rules
97
- var fontFaceRegex = /(@font-face[ \s\S]*?\{([\s\S]*?)\})/gm,
98
- ff;
99
-
100
- while ((ff = fontFaceRegex.exec(css)) !== null) {
101
-
102
- //grab the font name declared in the @font-face rule
103
- //(can still be in quotes, f.e. 'Lato Web'
104
- var t = /font-family[^:]*?:[ ]*([^;]*)/.exec(ff[1]);
105
- if (typeof t[1] === 'undefined')
106
- continue; //no font-family in @fontface rule!
107
-
108
- //rm quotes
109
- var fontName = t[1].replace(/['"]/gm, '');
110
-
111
- // does this fontname appear as a font-family or font (shorthand) value?
112
- var fontNameRegex = new RegExp('([^{}]*?){[^}]*?font(-family)?[^:]*?:[^;]*' + fontName + '[^,;]*[,;]', 'gmi');
113
-
114
-
115
- var fontFound = false,
116
- m;
117
-
118
- while ((m = fontNameRegex.exec(css)) !== null) {
119
- if (m[1].indexOf('@font-face') === -1) {
120
- //log('FOUND, keep rule');
121
- fontFound = true;
122
- break;
123
- }
124
- }
125
- if (!fontFound) {
126
- //NOT FOUND, rm!
127
-
128
- //can't remove rule here as it will screw up ongoing while (exec ...) loop.
129
- //instead: save indices and delete AFTER for loop
130
- var closeRuleIndex = css.indexOf('}', ff.index);
131
- //unshift - add to beginning of array - we need to remove rules in reverse order,
132
- //otherwise indeces will become incorrect again.
133
- toDeleteSections.unshift({
134
- start: ff.index,
135
- end: closeRuleIndex + 1
136
- });
137
- }
138
- }
139
- //now delete the @fontface rules we registed as having no matches in the css
140
- for (var i = 0; i < toDeleteSections.length; i++) {
141
- var start = toDeleteSections[i].start,
142
- end = toDeleteSections[i].end;
143
- css = css.substring(0, start) + css.substring(end);
144
- }
145
-
146
- return css;
147
- };
148
-
149
-
150
-
151
- if(typeof module !== 'undefined') {
152
- module.exports = unusedFontfaceRemover;
153
- }
154
- /*jshint unused:false*/
155
-
156
- /* === preFormatCSS ===
157
- * preformats the css to ensure we won't run into and problems in our parsing
158
- * removes comments (actually would be anough to remove/replace {} chars.. TODO
159
- * replaces } char inside content: '' properties.
160
- */
161
-
162
- function cssPreformatter (css){
163
- //remove comments from css (including multi-line coments)
164
- css = css.replace(/\/\*[\s\S]*?\*\//g, '');
165
-
166
- //replace Windows \r\n with \n,
167
- //otherwise final output might get converted into /r/r/n
168
- css = css.replace(/\r\n/gm, '\n');
169
-
170
- //we also need to replace eventual close curly bracket characters inside content: '' property declarations, replace them with their ASCI code equivalent
171
- //\7d (same as: '\' + '}'.charCodeAt(0).toString(16) );
172
-
173
- var m,
174
- regexP = /(content\s*:\s*['"][^'"]*)}([^'"]*['"])/gm,
175
- matchedData = [];
176
-
177
- //for each content: '' rule that contains at least one end bracket ('}')
178
- while ((m = regexP.exec(css)) !== null) {
179
- //we need to replace ALL end brackets in the rule
180
- //we can't do it in here, because it will mess up ongoing exec, store data and do after
181
-
182
- //unshift - add to beginning of array - we need to remove rules in reverse order,
183
- //otherwise indeces will become incorrect.
184
- matchedData.unshift({
185
- start: m.index,
186
- end: m.index + m[0].length,
187
- replaceStr: m[0].replace(/\}/gm, '\\7d')
188
- });
189
- }
190
-
191
- for (var i = 0; i < matchedData.length; i++) {
192
- var item = matchedData[0];
193
- css = css.substring(0, item.start) + item.replaceStr + css.substring(item.end);
194
- }
195
-
196
- return css;
197
- };
198
-
199
- if(typeof module !== 'undefined') {
200
- module.exports = cssPreformatter;
201
- }
202
- var standaloneMode = true;
203
- 'use strict';
204
- var standaloneMode = standaloneMode || false;
205
-
206
- var page = require('webpage').create(),
207
- fs = require('fs'),
208
- system = require('system'),
209
- DEBUG = false,
210
- stdout = system.stdout; // for using this as a file
211
-
212
- var combineArgsString = function(argsArr) {
213
- return [].join.call(argsArr, ' ') + '\n';
214
- };
215
-
216
- // monkey patch for directing errors to stderr
217
- // https://github.com/ariya/phantomjs/issues/10150#issuecomment-28707859
218
- var errorlog = function() {
219
- system.stderr.write(combineArgsString(arguments));
220
- };
221
-
222
- var debug = function() {
223
- if (DEBUG) errorlog('DEBUG: ' + combineArgsString(arguments));
224
- };
225
-
226
- // discard stdout from phantom exit;
227
- var phantomExit = function(code) {
228
- if (page) {
229
- page.close();
230
- }
231
- setTimeout(function() {
232
- phantom.exit(code);
233
- }, 0);
234
- };
235
-
236
- //don't confuse analytics more than necessary when visiting websites
237
- page.settings.userAgent = 'Penthouse Critical Path CSS Generator';
238
-
239
- /* prevent page JS errors from being output to final CSS */
240
- page.onError = function(msg, trace) {
241
- //do nothing
242
- };
243
-
244
- page.onResourceError = function(resourceError) {
245
- page.reason = resourceError.errorString;
246
- page.reason_url = resourceError.url;
247
- };
248
-
249
- var main = function(options) {
250
- debug('main(): ', JSON.stringify(options));
251
- //final cleanup
252
- //remove all empty rules, and remove leading/trailing whitespace
253
- try {
254
- var f = fs.open(options.css, 'r');
255
-
256
- //preformat css
257
- var cssPreformat;
258
- if (standaloneMode) {
259
- cssPreformat = cssPreformatter;
260
- } else {
261
- cssPreformat = require('./css-preformatter.js');
262
- }
263
- options.css = cssPreformat(f.read());
264
- } catch (e) {
265
- errorlog(e);
266
- phantomExit(1);
267
- }
268
-
269
- // start the critical path CSS generation
270
- getCriticalPathCss(options);
271
- };
272
-
273
- function cleanup(css) {
274
- //remove all animation rules, as keyframes have already been removed
275
- css = css.replace(/(-webkit-|-moz-|-ms-|-o-)?animation[ ]?:[^;{}]*;/gm, '');
276
- //remove all empty rules, and remove leading/trailing whitespace
277
- return css.replace(/[^{}]*\{\s*\}/gm, '').trim();
278
- }
279
-
280
- /* Final function
281
- * Get's called from getCriticalPathCss when CSS extraction from page is done*/
282
- page.onCallback = function(css) {
283
- debug('phantom.onCallback');
284
-
285
- try {
286
- if (css) {
287
- // we are done - clean up the final css
288
- var finalCss = cleanup(css);
289
-
290
- // remove unused @fontface rules
291
- var ffRemover;
292
- if (standaloneMode) {
293
- ffRemover = unusedFontfaceRemover;
294
- } else {
295
- ffRemover = require('./unused-fontface-remover.js');
296
- }
297
- finalCss = ffRemover(finalCss);
298
-
299
- if(finalCss.trim().length === 0){
300
- errorlog('Note: Generated critical css was empty for URL: ' + options.url);
301
- }
302
-
303
- // return the critical css!
304
- stdout.write(finalCss);
305
- phantomExit(0);
306
- } else {
307
- // No css. This is not an error on our part
308
- // but still safer to warn the end user, in case they made a mistake
309
- errorlog('Note: Generated critical css was empty for URL: ' + options.url);
310
- // for consisteny, still generate output (will be empty)
311
- stdout.write(css);
312
- phantomExit(0);
313
- }
314
-
315
- } catch (ex) {
316
- debug('phantom.onCallback -> error', ex);
317
- errorlog('error: ' + ex);
318
- phantomExit(1);
319
- }
320
- };
321
-
322
- /*
323
- * Tests each selector in css file at specified resolution,
324
- * to see if any such elements appears above the fold on the page
325
- * modifies CSS - removes selectors that don't appear, and empty rules
326
- *
327
- * @param options.url the url as a string
328
- * @param options.css the css as a string
329
- * @param options.width the width of viewport
330
- * @param options.height the height of viewport
331
- ---------------------------------------------------------*/
332
- function getCriticalPathCss(options) {
333
- debug('getCriticalPathCss():', JSON.stringify(options));
334
-
335
- page.viewportSize = {
336
- width: options.width,
337
- height: options.height
338
- };
339
-
340
- page.open(options.url, function(status) {
341
- if (status !== 'success') {
342
- errorlog('Error opening url \'' + page.reason_url + '\': ' + page.reason);
343
- phantomExit(1);
344
- } else {
345
-
346
- debug('Starting sandboxed evaluation of CSS\n', options.css);
347
- // sandboxed environments - no outside references
348
- // arguments and return value must be primitives
349
- // @see http://phantomjs.org/api/webpage/method/evaluate.html
350
- page.evaluate(function sandboxed(css) {
351
- var h = window.innerHeight,
352
- renderWaitTime = 100, //ms TODO: user specifiable through options object
353
- finished = false,
354
- currIndex = 0,
355
- forceRemoveNestedRule = false;
356
-
357
- //split CSS so we can value the (selector) rules separately.
358
- //but first, handle stylesheet initial non nested @-rules.
359
- //they don't come with any associated rules, and should all be kept,
360
- //so just keep them in critical css, but don't include them in split
361
- var splitCSS = css.replace(/@(import|charset|namespace)[^;]*;/g, '');
362
- var split = splitCSS.split(/[{}]/g);
363
-
364
- var getNewValidCssSelector = function(i) {
365
- var newSel = split[i];
366
- /* HANDLE Nested @-rules */
367
-
368
- /*Case 1: @-rule with CSS properties inside [REMAIN]
369
- Can't remove @font-face rules here, don't know if used or not.
370
- Another check at end for this purpose.
371
- */
372
- if (/@(font-face)/gi.test(newSel)) {
373
- //skip over this rule
374
- currIndex = css.indexOf('}', currIndex) + 1;
375
- return getNewValidCssSelector(i + 2);
376
- }
377
- /*Case 2: @-rule with CSS properties inside [REMOVE]
378
- @page
379
- This case doesn't need any special handling,
380
- as this "selector" won't match anything on the page,
381
- and will therefor be removed, together with it's css props
382
- */
383
-
384
- /*Case 4: @-rule with full CSS (rules) inside [REMOVE]
385
- @media print|speech|aural, @keyframes
386
- Delete this rule and all its contents - doesn't belong in critical path CSS
387
- */
388
- else if (/@(media (print|speech|aural)|(([a-z\-])*keyframes))/gi.test(newSel)) {
389
- //force delete on child css rules
390
- forceRemoveNestedRule = true;
391
- return getNewValidCssSelector(i + 1);
392
- }
393
-
394
- /*Case 3: @-rule with full CSS (rules) inside [REMAIN]
395
- This test is executed AFTER Case 4,
396
- since we here match every remaining @media,
397
- after @media print has been removed by Case 4 rule)
398
- - just skip this particular line (i.e. keep), and continue checking the CSS inside as normal
399
- */
400
- else if (/@(media|(-moz-)?document|supports)/gi.test(newSel)) {
401
- return getNewValidCssSelector(i + 1);
402
- }
403
- /*
404
- Resume normal execution after end of @-media rule with inside CSS rules (Case 3)
405
- Also identify abrupt file end.
406
- */
407
- else if (newSel.trim().length === 0) {
408
- //abrupt file end
409
- if (i + 1 >= split.length) {
410
- //end of file
411
- finished = true;
412
- return false;
413
- }
414
- //end of @-rule (Case 3)
415
- forceRemoveNestedRule = false;
416
- return getNewValidCssSelector(i + 1);
417
- }
418
- return i;
419
- };
420
-
421
- var removeSelector = function(sel, selectorsKept) {
422
- var selPos = css.indexOf(sel, currIndex);
423
-
424
- //check what comes next: { or ,
425
- var nextComma = css.indexOf(',', selPos);
426
- var nextOpenBracket = css.indexOf('{', selPos);
427
-
428
- if (selectorsKept > 0 || (nextComma > 0 && nextComma < nextOpenBracket)) {
429
- //we already kept selectors from this rule, so rule will stay
430
-
431
- //more selectors in selectorList, cut until (and including) next comma
432
- if (nextComma > 0 && nextComma < nextOpenBracket) {
433
- css = css.substring(0, selPos) + css.substring(nextComma + 1);
434
- }
435
- //final selector, cut until open bracket. Also remove previous comma, as the (new) last selector should not be followed by a comma.
436
- else {
437
- var prevComma = css.lastIndexOf(',', selPos);
438
- css = css.substring(0, prevComma) + css.substring(nextOpenBracket);
439
- }
440
- } else {
441
- //no part of selector (list) matched elements above fold on page - remove whole rule CSS rule
442
- var endRuleBracket = css.indexOf('}', nextOpenBracket);
443
-
444
- css = css.substring(0, selPos) + css.substring(endRuleBracket + 1);
445
- }
446
- };
447
-
448
-
449
- var processCssRules = function() {
450
- for (var i = 0; i < split.length; i = i + 2) {
451
- //step over non DOM CSS selectors (@-rules)
452
- i = getNewValidCssSelector(i);
453
-
454
- //reach end of CSS
455
- if (finished) {
456
- //call final function to exit outside of phantom evaluate scope
457
- window.callPhantom(css);
458
- }
459
-
460
- var fullSel = split[i];
461
- //fullSel can contain combined selectors
462
- //,f.e. body, html {}
463
- //split and check one such selector at the time.
464
- var selSplit = fullSel.split(',');
465
- //keep track - if we remove all selectors, we also want to remove the whole rule.
466
- var selectorsKept = 0;
467
- var aboveFold;
468
-
469
- for (var j = 0; j < selSplit.length; j++) {
470
- var sel = selSplit[j];
471
-
472
- //some selectors can't be matched on page.
473
- //In these cases we test a slightly modified selectors instead, temp.
474
- var temp = sel;
475
-
476
- if (sel.indexOf(':') > -1) {
477
- //handle special case selectors, the ones that contain a semi colon (:)
478
- //many of these selectors can't be matched to anything on page via JS,
479
- //but that still might affect the above the fold styling
480
-
481
- //these psuedo selectors depend on an element,
482
- //so test element instead (would do the same for f.e. :hover, :focus, :active IF we wanted to keep them for critical path css, but we don't)
483
- temp = temp.replace(/(:?:before|:?:after)*/g, '');
484
-
485
- //if selector is purely psuedo (f.e. ::-moz-placeholder), just keep as is.
486
- //we can't match it to anything on page, but it can impact above the fold styles
487
- if (temp.replace(/:[:]?([a-zA-Z0-9\-\_])*/g, '').trim().length === 0) {
488
- currIndex = css.indexOf(sel, currIndex) + sel.length;
489
- selectorsKept++;
490
- continue;
491
- }
492
-
493
- //handle browser specific psuedo selectors bound to elements,
494
- //Example, button::-moz-focus-inner, input[type=number]::-webkit-inner-spin-button
495
- //remove browser specific pseudo and test for element
496
- temp = temp.replace(/:?:-[a-z-]*/g, '');
497
- }
498
-
499
- if (!forceRemoveNestedRule) {
500
- //now we have a selector to test, first grab any matching elements
501
- var el;
502
- try {
503
- el = document.querySelectorAll(temp);
504
- } catch (e) {
505
- //not a valid selector, remove it.
506
- removeSelector(sel, 0);
507
- continue;
508
- }
509
-
510
- //check if selector matched element(s) on page..
511
- aboveFold = false;
512
-
513
- for (var k = 0; k < el.length; k++) {
514
- var testEl = el[k];
515
- //temporarily force clear none in order to catch elements that clear previous content themselves and who w/o their styles could show up unstyled in above the fold content (if they rely on f.e. 'clear:both;' to clear some main content)
516
- testEl.style.clear = 'none';
517
-
518
- //check to see if any matched element is above the fold on current page
519
- //(in current viewport size)
520
- if (testEl.getBoundingClientRect().top < h) {
521
- //then we will save this selector
522
- aboveFold = true;
523
- selectorsKept++;
524
-
525
- //update currIndex so we only search from this point from here on.
526
- currIndex = css.indexOf(sel, currIndex);
527
-
528
- //set clear style back to what it was
529
- testEl.style.clear = '';
530
- //break, because matching 1 element is enough
531
- break;
532
- }
533
- //set clear style back to what it was
534
- testEl.style.clear = '';
535
- }
536
- } else {
537
- aboveFold = false;
538
- } //force removal of selector
539
-
540
- //if selector didn't match any elements above fold - delete selector from CSS
541
- if (aboveFold === false) {
542
- //update currIndex so we only search from this point from here on.
543
- currIndex = css.indexOf(sel, currIndex);
544
- //remove seletor (also removes rule, if nnothing left)
545
- removeSelector(sel, selectorsKept);
546
- }
547
- }
548
- //if rule stayed, move our cursor forward for matching new selectors
549
- if (selectorsKept > 0) {
550
- currIndex = css.indexOf('}', currIndex) + 1;
551
- }
552
- }
553
-
554
- //we're done - call final function to exit outside of phantom evaluate scope
555
- window.callPhantom(css);
556
- };
557
-
558
- //give some time (renderWaitTime) for sites like facebook that build their page dynamically,
559
- //otherwise we can miss some selectors (and therefor rules)
560
- //--tradeoff here: if site is too slow with dynamic content,
561
- // it doesn't deserve to be in critical path.
562
- setTimeout(processCssRules, renderWaitTime);
563
-
564
- }, options.css);
565
- }
566
- });
567
- }
568
-
569
- var parser, parse, usage, options;
570
-
571
- // test to see if we are running as a standalone script
572
- // or as part of the node module
573
- if (standaloneMode) {
574
- parse = parseOptions;
575
- usage = usageString;
576
- } else {
577
- parser = require('../options-parser');
578
- parse = parser.parse;
579
- usage = parser.usage;
580
- }
581
-
582
- try {
583
- options = parse(system.args.slice(1));
584
- } catch (ex) {
585
-
586
- errorlog('Caught error parsing arguments: ' + ex.message);
587
-
588
- // the usage string does not make sense to show if running via Node
589
- if(standaloneMode) {
590
- errorlog('\nUsage: phantomjs penthouse.js ' + usage);
591
- }
592
-
593
- phantomExit(1);
594
- }
595
-
596
- // set defaults
597
- if (!options.width) options.width = 1300;
598
- if (!options.height) options.height = 900;
599
-
600
- main(options);
601
- })();