diff --git a/README.md b/README.md index f25ab63..74a6e10 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ However, more packages may need to be installed depending on your OS distributio After reviewing the dependency requirements, add `critical-path-css-rails` to your Gemfile: ``` -gem 'critical-path-css-rails', '~> 2.10.0' +gem 'critical-path-css-rails', '~> 3.0.0' ``` Download and install by running: diff --git a/docker/ruby/Dockerfile b/docker/ruby/Dockerfile index 4376437..3404a71 100644 --- a/docker/ruby/Dockerfile +++ b/docker/ruby/Dockerfile @@ -7,10 +7,11 @@ RUN apt-get update && apt-get install -y build-essential libpq-dev nodejs npm # Install Penthouse JS Dependencies RUN apt-get install -y libpangocairo-1.0-0 libx11-xcb1 libxcomposite1 libxcursor1 libxdamage1 libxi6 libxtst6 libnss3 libcups2 libxss1 libxrandr2 libgconf2-4 libasound2 libatk1.0-0 libgtk-3-0 +# Configure Node/NPM RUN npm cache clean -f RUN npm install -g n -RUN n 8.9.3 -RUN ln -sf /usr/local/n/versions/node/8.9.3/bin/node /usr/bin/nodejs +RUN n 10.15.1 +RUN ln -sf /usr/local/n/versions/node/10.15.1/bin/node /usr/bin/nodejs ENV BUNDLE_PATH /gems diff --git a/lib/critical-path-css-rails.rb b/lib/critical-path-css-rails.rb index 7586934..e361d3a 100644 --- a/lib/critical-path-css-rails.rb +++ b/lib/critical-path-css-rails.rb @@ -6,16 +6,11 @@ module CriticalPathCss CACHE_NAMESPACE = 'critical-path-css'.freeze def self.generate(route) - ::Rails.cache.write( - route, - CssFetcher.new(config).fetch_route(route), - namespace: CACHE_NAMESPACE, - expires_in: nil - ) + ::Rails.cache.write(route, fetcher.fetch_route(route), namespace: CACHE_NAMESPACE, expires_in: nil) end def self.generate_all - CssFetcher.new(config).fetch.each do |route, css| + fetcher.fetch.each do |route, css| ::Rails.cache.write(route, css, namespace: CACHE_NAMESPACE, expires_in: nil) end end @@ -32,7 +27,11 @@ def self.fetch(route) ::Rails.cache.read(route, namespace: CACHE_NAMESPACE) || '' end - def self.config - @config ||= Configuration.new(CriticalPathCss::Rails::ConfigLoader.new.load) + def self.fetcher + @fetcher ||= CssFetcher.new(Configuration.new(config_loader.config)) + end + + def self.config_loader + @config_loader ||= CriticalPathCss::Rails::ConfigLoader.new end end diff --git a/lib/critical_path_css/css_fetcher.rb b/lib/critical_path_css/css_fetcher.rb index 3ead1a2..54a9880 100644 --- a/lib/critical_path_css/css_fetcher.rb +++ b/lib/critical_path_css/css_fetcher.rb @@ -10,33 +10,21 @@ def initialize(config) end def fetch - @config.routes.map.with_index { |route, index| - css_path = @config.css_paths[index].present? ? @config.css_paths[index] : @config.css_path - [route, css_for_route(route, css_path)] - }.to_h + @config.routes.map { |route| [route, fetch_route(route)] }.to_h end def fetch_route(route) - css_for_route route - end - - protected - - def css_for_route(route, css_path) options = { 'url' => @config.base_url + route, - 'css' => css_path, - ## optional params - # viewport dimensions + 'css' => fetch_css_path_for_route(route), 'width' => 1300, 'height' => 900, + 'timeout' => 30_000, # CSS selectors to always include, e.g.: 'forceInclude' => [ # '.keepMeEvenIfNotSeenInDom', # '^\.regexWorksToo' ], - # ms; abort critical CSS generation after this timeout - 'timeout' => 30_000, # set to true to throw on CSS errors (will run faster if no errors) 'strict' => false, # characters; strip out inline base64 encoded resources larger than this @@ -63,5 +51,17 @@ def css_for_route(route, css_path) end out end + + private + + def fetch_css_path_for_route(route) + index_for_route = @config.routes.index(route) + + if index_for_route && @config.css_paths[index_for_route] + @config.css_paths[index_for_route] + else + @config.css_path + end + end end end diff --git a/lib/critical_path_css/rails/config_loader.rb b/lib/critical_path_css/rails/config_loader.rb index ee4d00d..27f1d8a 100644 --- a/lib/critical_path_css/rails/config_loader.rb +++ b/lib/critical_path_css/rails/config_loader.rb @@ -3,22 +3,13 @@ module Rails class ConfigLoader CONFIGURATION_FILENAME = 'critical_path_css.yml'.freeze - def load - config = YAML.safe_load(ERB.new(File.read(configuration_file_path)).result, [], [], true)[::Rails.env] - validate_css_path config - if config['css_path'] - config['css_path'] = "#{::Rails.root}/public" + ( - config['css_path'] || - ActionController::Base.helpers.stylesheet_path( - config['manifest_name'], host: '' - ) - ) - config['css_paths'] = [] - else - config['css_path'] = '' - config['css_paths'] = config['css_paths'].collect { |path| "#{::Rails.root}/public#{path}" } - end - config + def initialize + validate_css_paths + format_css_paths + end + + def config + @config ||= YAML.safe_load(ERB.new(File.read(configuration_file_path)).result, [], [], true)[::Rails.env] end private @@ -27,7 +18,21 @@ def configuration_file_path @configuration_file_path ||= ::Rails.root.join('config', CONFIGURATION_FILENAME) end - def validate_css_path(config) + def format_css_paths + if config['css_path'] + config['css_path'] = format_path(config['css_path']) + config['css_paths'] = [] + else + config['css_path'] = '' + config['css_paths'] = config['css_paths'].collect { |path| format_path(path) } + end + end + + def format_path(path) + "#{::Rails.root}/public#{path}" + end + + def validate_css_paths if config['css_path'] && config['css_paths'] raise LoadError, 'Cannot specify both css_path and css_paths' elsif config['css_paths'] && config['css_paths'].length != config['routes'].length diff --git a/package-lock.json b/package-lock.json index a3e7120..bc55893 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", "requires": { - "es6-promisify": "5.0.0" + "es6-promisify": "^5.0.0" } }, "async-limiter": { @@ -27,7 +27,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -46,10 +46,10 @@ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "requires": { - "buffer-from": "1.1.1", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "typedarray": "0.0.6" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" } }, "core-util-is": { @@ -67,8 +67,8 @@ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.28.tgz", "integrity": "sha512-joNNW1gCp3qFFzj4St6zk+Wh/NBv0vM5YbEreZk0SD4S23S+1xBKb6cLDg2uj4P4k/GUMlIm6cKIDqIG+vdt0w==", "requires": { - "mdn-data": "1.1.4", - "source-map": "0.5.7" + "mdn-data": "~1.1.0", + "source-map": "^0.5.3" } }, "debug": { @@ -76,7 +76,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", "requires": { - "ms": "2.1.1" + "ms": "^2.1.1" } }, "es6-promise": { @@ -86,10 +86,10 @@ }, "es6-promisify": { "version": "5.0.0", - "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "requires": { - "es6-promise": "4.2.5" + "es6-promise": "^4.0.3" } }, "extract-zip": { @@ -123,7 +123,7 @@ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", "requires": { - "pend": "1.2.0" + "pend": "~1.2.0" } }, "fs.realpath": { @@ -136,12 +136,12 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "https-proxy-agent": { @@ -149,8 +149,8 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", "requires": { - "agent-base": "4.2.1", - "debug": "3.2.6" + "agent-base": "^4.1.0", + "debug": "^3.1.0" }, "dependencies": { "debug": { @@ -158,7 +158,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { - "ms": "2.1.1" + "ms": "^2.1.1" } } } @@ -168,8 +168,8 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -202,17 +202,17 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -228,7 +228,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "path-is-absolute": { @@ -246,10 +246,10 @@ "resolved": "https://registry.npmjs.org/penthouse/-/penthouse-1.10.1.tgz", "integrity": "sha512-D0fUazt6EvtoJvbKJ4u6yVIwfrWoSW1+2clr5JZaKag5pzgwLJKYcN8NgmAoBmWOwsMk+3ZAkV5Cv09LzQtqAw==", "requires": { - "css-mediaquery": "0.1.2", + "css-mediaquery": "^0.1.2", "css-tree": "1.0.0-alpha.28", - "debug": "4.1.0", - "jsesc": "2.5.1", + "debug": "^4.1.0", + "jsesc": "^2.5.1", "puppeteer": "1.9.0" } }, @@ -273,14 +273,14 @@ "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.9.0.tgz", "integrity": "sha512-GH4PmhJf9wBRAPvtJkEJLAvdNNOofZortmBZSj8cGWYni98GUFqsf66blOEfJbo5B8l0KG5HR2d/W2MejnUrzg==", "requires": { - "debug": "3.2.6", - "extract-zip": "1.6.7", - "https-proxy-agent": "2.2.1", - "mime": "2.3.1", - "progress": "2.0.1", - "proxy-from-env": "1.0.0", - "rimraf": "2.6.2", - "ws": "5.2.2" + "debug": "^3.1.0", + "extract-zip": "^1.6.6", + "https-proxy-agent": "^2.2.1", + "mime": "^2.0.3", + "progress": "^2.0.0", + "proxy-from-env": "^1.0.0", + "rimraf": "^2.6.1", + "ws": "^5.1.1" }, "dependencies": { "debug": { @@ -288,23 +288,23 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { - "ms": "2.1.1" + "ms": "^2.1.1" } } } }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "rimraf": { @@ -312,7 +312,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "requires": { - "glob": "7.1.3" + "glob": "^7.0.5" } }, "safe-buffer": { @@ -330,7 +330,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } }, "typedarray": { @@ -353,7 +353,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", "requires": { - "async-limiter": "1.0.0" + "async-limiter": "~1.0.0" } }, "yauzl": { @@ -361,7 +361,7 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", "requires": { - "fd-slicer": "1.0.1" + "fd-slicer": "~1.0.1" } } } diff --git a/spec/lib/critical_path_css/css_fetcher_spec.rb b/spec/lib/critical_path_css/css_fetcher_spec.rb index 86e4d89..32b0cd9 100644 --- a/spec/lib/critical_path_css/css_fetcher_spec.rb +++ b/spec/lib/critical_path_css/css_fetcher_spec.rb @@ -1,22 +1,36 @@ require 'spec_helper' RSpec.describe 'CssFetcher' do - describe '#fetch' do - let(:subject) { CriticalPathCss::CssFetcher.new(config) } - let(:response) { - ['foo','', OpenStruct.new(exitstatus: 0)] - } - let(:routes) { ['/', '/new_route'] } - let(:config) do - OpenStruct.new( - base_url: 'http://0.0.0.0:9292', - css_path: css_path, - css_paths: css_paths, - penthouse_options: {}, - routes: routes - ) + let(:subject) { CriticalPathCss::CssFetcher.new(config) } + let(:response) { ['foo','', OpenStruct.new(exitstatus: 0)] } + let(:routes) { ['/', '/new_route'] } + let(:config) do + OpenStruct.new( + base_url: 'http://0.0.0.0:9292', + css_path: css_path, + css_paths: css_paths, + penthouse_options: {}, + routes: routes + ) + end + + describe '#fetch_route' do + context 'when a single css_path is configured' do + let(:css_path) { '/test.css' } + let(:css_paths) { [] } + + it 'generates css for the single route' do + expect(Open3).to receive(:capture3) do |arg1, arg2, arg3| + options = JSON.parse(arg3) + expect(options['css']).to eq '/test.css' + end.once.and_return(response) + + subject.fetch_route(routes.first) + end end + end + describe '#fetch' do context 'when a single css_path is configured' do let(:css_path) { '/test.css' } let(:css_paths) { [] } diff --git a/spec/lib/critical_path_css/rails/config_loader_spec.rb b/spec/lib/critical_path_css/rails/config_loader_spec.rb index 232b7ab..d6fbfae 100644 --- a/spec/lib/critical_path_css/rails/config_loader_spec.rb +++ b/spec/lib/critical_path_css/rails/config_loader_spec.rb @@ -2,6 +2,7 @@ RSpec.describe 'ConfigLoader' do let(:subject) { CriticalPathCss::Rails::ConfigLoader.new } + describe '#load' do before do allow(File).to receive(:read).and_return(config_file) @@ -25,15 +26,11 @@ } it 'sets css_path with the path' do - config = subject.load - - expect(config['css_path']).to eq '/app/spec/internal/public/test.css' + expect(subject.config['css_path']).to eq '/app/spec/internal/public/test.css' end it 'leaves css_paths empty' do - config = subject.load - - expect(config['css_paths']).to eq [] + expect(subject.config['css_paths']).to eq [] end end @@ -58,15 +55,11 @@ } it 'sets css_path to empty string' do - config = subject.load - - expect(config['css_path']).to eq '' + expect(subject.config['css_path']).to eq '' end it 'leaves css_paths to an array of paths' do - config = subject.load - - expect(config['css_paths']).to eq ['/app/spec/internal/public/test.css','/app/spec/internal/public/test2.css'] + expect(subject.config['css_paths']).to eq ['/app/spec/internal/public/test.css','/app/spec/internal/public/test2.css'] end end @@ -92,7 +85,7 @@ } it 'raises an error' do - expect { subject.load }.to raise_error LoadError, 'Cannot specify both css_path and css_paths' + expect { subject }.to raise_error LoadError, 'Cannot specify both css_path and css_paths' end end @@ -118,7 +111,7 @@ } it 'raises an error' do - expect { subject.load }.to raise_error LoadError, 'Must specify css_paths for each route' + expect { subject }.to raise_error LoadError, 'Must specify css_paths for each route' end end end