Skip to content

Use penthouse from npm #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
languages:
Ruby: true
exclude_paths:
- lib/penthouse/penthouse.js
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ test/version_tmp
tmp

.DS_Store
/node_modules/
.rspec_status
/npm-debug.log
19 changes: 16 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
# A sample Gemfile
source "https://rubygems.org"
source 'https://rubygems.org'

gem 'rubocop', require: false
gemspec

group :development, :test do
gem 'byebug', platform: [:ruby], require: false
gem 'rubocop', require: false
end

# HACK: npm install on bundle
unless $npm_commands_hook_installed # rubocop:disable Style/GlobalVars
Gem.pre_install do |installer|
next true unless installer.spec.name == 'critical-path-css-rails'
require_relative './ext/npm/install'
end
$npm_commands_hook_installed = true # rubocop:disable Style/GlobalVars
end
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This gem give you the ability to load only the CSS you *need* on an initial page

This gem assumes that you'll load the rest of the CSS asyncronously. At the moment, the suggested way is to use the [loadcss-rails](https://github.com/michael-misshore/loadcss-rails) gem.

This gem uses [PhantomJS](https://github.com/colszowka/phantomjs-gem) and [Penthouse](https://github.com/pocketjoso/penthouse) to generate the critical CSS.
This gem uses [Penthouse](https://github.com/pocketjoso/penthouse) to generate the critical CSS.

## Update

Expand All @@ -17,7 +17,7 @@ Versions below 0.3.0 are not compatible with this version. Please read the Upgr
Add `critical-path-css-rails` to your Gemfile:

```
gem 'critical-path-css-rails', '~> 0.4.0'
gem 'critical-path-css-rails', '~> 1.0.0'
```

Download and install by running:
Expand Down Expand Up @@ -128,9 +128,9 @@ Answer 'Y' when prompted to overwrite `critical_path_css.rake`. However, overwr
The critical-path-css-rails gem follows these version guidelines:

```
patch version bump = updates to critical-path-css-rails and patch-level updates to Penthouse and PhantomJS
minor version bump = minor-level updates to critical-path-css-rails, Penthouse, and PhantomJS
major version bump = major-level updates to critical-path-css-rails, Penthouse, PhantomJS, and updates to Rails which may be backwards-incompatible
patch version bump = updates to critical-path-css-rails and patch-level updates to Penthouse
minor version bump = minor-level updates to critical-path-css-rails and Penthouse
major version bump = major-level updates to critical-path-css-rails, Penthouse, and updates to Rails which may be backwards-incompatible
```

## Contributing
Expand Down
6 changes: 4 additions & 2 deletions critical-path-css-rails.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ Gem::Specification.new do |s|
s.description = 'Only load the CSS you need for the initial viewport in Rails!'
s.license = 'MIT'

s.add_runtime_dependency 'phantomjs', ['~> 2.1']

s.files = `git ls-files`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
s.require_path = 'lib'

s.add_development_dependency 'rspec', '~> 3.6'

s.extensions = ['ext/npm/extconf.rb']
end
3 changes: 3 additions & 0 deletions ext/npm/extconf.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
File.write 'Makefile',
"make:\n\t\ninstall:\n\truby install.rb\nclean:\n\t\n"

4 changes: 4 additions & 0 deletions ext/npm/install.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
require_relative '../../lib/npm_commands'

NpmCommands.new.install('--production', '.') ||
raise('Error while installing npm dependencies')
24 changes: 15 additions & 9 deletions lib/critical-path-css-rails.rb
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
module CriticalPathCss
require 'critical_path_css/css_fetcher'
require 'critical_path_css/configuration'
require 'critical_path_css/css_fetcher'
require 'critical_path_css/rails/config_loader'

module CriticalPathCss
CACHE_NAMESPACE = 'critical-path-css'

def self.generate(route)
Rails.cache.write(
::Rails.cache.write(
route,
CssFetcher.new.fetch_route(route),
CssFetcher.new(config).fetch_route(route),
namespace: CACHE_NAMESPACE,
expires_in: nil
)
end

def self.generate_all
CssFetcher.new.fetch.each do |route, css|
Rails.cache.write(route, css, namespace: CACHE_NAMESPACE, expires_in: nil)
CssFetcher.new(config).fetch.each do |route, css|
::Rails.cache.write(route, css, namespace: CACHE_NAMESPACE, expires_in: nil)
end
end

def self.clear(route)
Rails.cache.delete(route, namespace: CACHE_NAMESPACE)
::Rails.cache.delete(route, namespace: CACHE_NAMESPACE)
end

def self.clear_matched(routes)
Rails.cache.delete_matched(routes, namespace: CACHE_NAMESPACE)
::Rails.cache.delete_matched(routes, namespace: CACHE_NAMESPACE)
end

def self.fetch(route)
Rails.cache.read(route, namespace: CACHE_NAMESPACE) || ''
::Rails.cache.read(route, namespace: CACHE_NAMESPACE) || ''
end

def self.config
@config ||= Configuration.new(CriticalPathCss::Rails::ConfigLoader.new.load)
end
end
26 changes: 8 additions & 18 deletions lib/critical_path_css/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,39 +1,29 @@
require 'erb'
module CriticalPathCss
class Configuration
CONFIGURATION_FILENAME = 'critical_path_css.yml'

def initialize
@configurations = YAML.load(ERB.new(File.read(configuration_file_path)).result)[Rails.env]
def initialize(config)
@config = config
end

def base_url
@configurations['base_url']
@config['base_url']
end

def css_path
@css_path ||= begin
relative_path = @configurations['css_path'] || manifest_path
"#{Rails.root}/public#{relative_path}"
end
@config['css_path']
end

def manifest_name
@configurations['manifest_name']
@config['manifest_name']
end

def routes
@configurations['routes']
@config['routes']
end

private

def configuration_file_path
@configuration_file_path ||= Rails.root.join('config', CONFIGURATION_FILENAME)
end

def manifest_path
@manifest_path ||= ActionController::Base.helpers.stylesheet_path(manifest_name, host: '')
def penthouse_options
@config['penthouse_options'] || {}
end
end
end
65 changes: 50 additions & 15 deletions lib/critical_path_css/css_fetcher.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
require 'json'
require 'open3'

module CriticalPathCss
class CssFetcher
require 'phantomjs'
require 'critical_path_css/configuration'

PENTHOUSE_PATH = "#{File.dirname(__FILE__)}/../penthouse/penthouse.js"
GEM_ROOT = File.expand_path(File.join('..', '..'), File.dirname(__FILE__))

def initialize
@config = Configuration.new
def initialize(config)
@config = config
end

def fetch
Expand All @@ -20,15 +20,50 @@ def fetch_route(route)
protected

def css_for_route(route)
url = @config.base_url + route

Phantomjs.run(
'--ignore-ssl-errors=true',
'--ssl-protocol=tlsv1',
PENTHOUSE_PATH,
url,
@config.css_path
)
options = {
'url' => @config.base_url + route,
'css' => @config.css_path,
## optional params
# viewport dimensions
'width' => 1300,
'height' => 900,
# CSS selectors to always include, e.g.:
'forceInclude' => [
# '.keepMeEvenIfNotSeenInDom',
# '^\.regexWorksToo'
],
# ms; abort critical CSS generation after this timeout
'timeout' => 30000,
# 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
'maxEmbeddedBase64Length' => 1000,
# specify which user agent string when loading the page
'userAgent' => 'Penthouse Critical Path CSS Generator',
# ms; render wait timeout before CSS processing starts (default: 100)
'renderWaitTime' => 100,
# set to false to load (external) JS (default: true)
'blockJSRequests' => true,
# see `phantomjs --help` for the list of all available options
'phantomJsOptions' => {
'ignore-ssl-errors' => true,
'ssl-protocol' => 'tlsv1'
},
'customPageHeaders' => {
# use if getting compression errors like 'Data corrupted':
'Accept-Encoding' => 'identity'
}
}.merge(@config.penthouse_options)
out, err, st = Dir.chdir(GEM_ROOT) do
Open3.capture3('node', 'lib/fetch-css.js', JSON.dump(options))
end
if !st.exitstatus.zero? || out.empty? && !err.empty?
STDOUT.puts out
STDERR.puts err
raise "Failed to get CSS for route #{route}\n" \
" with options=#{options.inspect}"
end
out
end
end
end
24 changes: 24 additions & 0 deletions lib/critical_path_css/rails/config_loader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module CriticalPathCss
module Rails
class ConfigLoader
CONFIGURATION_FILENAME = 'critical_path_css.yml'

def load
config = YAML.load(ERB.new(File.read(configuration_file_path)).result)[::Rails.env]
config['css_path'] = "#{::Rails.root}/public" + (
config['css_path'] ||
ActionController::Base.helpers.stylesheet_path(
config['manifest_name'], host: ''
)
)
config
end

private

def configuration_file_path
@configuration_file_path ||= ::Rails.root.join('config', CONFIGURATION_FILENAME)
end
end
end
end
3 changes: 1 addition & 2 deletions lib/critical_path_css/rails/version.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
module CriticalPathCSS
module Rails
VERSION = '0.4.0'
PENTHOUSE_VERSION = '0.3.4'
VERSION = '1.0.0'
end
end
14 changes: 14 additions & 0 deletions lib/fetch-css.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const penthouse = require('penthouse');
const fs = require('fs');

const penthouseOptions = JSON.parse(process.argv[2]);

const STDOUT_FD = 1;
const STDERR_FD = 2;

penthouse(penthouseOptions).then(function(criticalCss) {
fs.writeSync(STDOUT_FD, criticalCss);
}).catch(function(err) {
fs.writeSync(STDERR_FD, err);
process.exit(1);
});
56 changes: 56 additions & 0 deletions lib/npm_commands.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

# NPM wrapper with helpful error messages
class NpmCommands

# @return [Boolean] whether the installation succeeded
def install(*args) # rubocop:disable Metrics/MethodLength
return false unless check_nodejs_installed
STDERR.puts 'Installing npm dependencies...'
install_status = Dir.chdir File.expand_path('..', File.dirname(__FILE__)) do
system('npm', 'install', *args)
end
STDERR.puts(
*if install_status
['npm dependencies installed']
else
['-' * 60,
'Error: npm dependencies installation failed',
'-' * 60]
end
)
install_status
end

private

def check_nodejs_installed # rubocop:disable Metrics/MethodLength
return true if executable?('node')
STDERR.puts(
'-' * 60,
'Error: critical-path-css-rails requires NodeJS and NPM.',
*if executable?('brew')
[' To install NodeJS and NPM, run:',
' brew install node']
elsif Gem.win_platform?
[' To install NodeJS and NPM, we recommend:',
' https://github.com/coreybutler/nvm-windows/releases']
else
[' To install NodeJS and NPM, we recommend:',
' https://github.com/creationix/nvm']
end,
'-' * 60
)
end

def executable?(cmd)
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
exts.each do |ext|
exe = File.join(path, "#{cmd}#{ext}")
return exe if File.executable?(exe) && !File.directory?(exe)
end
end
nil
end
end
Loading