From 2c8014b52d425a44a6892cfa5e84b22764c5bfab Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 3 Jun 2017 20:14:01 +0100 Subject: [PATCH 1/3] Install npm dependencies on Gem.pre_install --- .gitignore | 1 + Gemfile | 12 +- Rakefile | 41 +++ lib/npm_commands.rb | 54 ++++ lib/rubygems_plugin.rb | 4 + package-lock.json | 595 +++++++++++++++++++++++++++++++++++++++++ package.json | 13 + 7 files changed, 717 insertions(+), 3 deletions(-) create mode 100644 Rakefile create mode 100644 lib/npm_commands.rb create mode 100644 lib/rubygems_plugin.rb create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore index b36aa3d..3e20912 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ test/version_tmp tmp .DS_Store +/node_modules/ diff --git a/Gemfile b/Gemfile index 07d2b13..c3a87cf 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,10 @@ -# A sample Gemfile -source "https://rubygems.org" +source 'https://rubygems.org' -gem 'rubocop', require: false \ No newline at end of file +gemspec + +gem 'rubocop', require: false + +# Gem hooks are not triggered for the gemspec gem +# https://github.com/bundler/bundler/issues/2354 +# Work around this by requiring the hooks here +require_relative './lib/rubygems_plugin' diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..85964ad --- /dev/null +++ b/Rakefile @@ -0,0 +1,41 @@ +desc 'Update the vendored version of penthouse' +task :update_penthouse do # rubocop:disable Metrics/BlockLength + require 'net/http' + require 'uri' + fetch = lambda do |url| + uri = URI.parse(url) + response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| + http.request(Net::HTTP::Get.new(uri.path, 'User-Agent' => 'Ruby')) + end + case response + when Net::HTTPSuccess + response.body + when Net::HTTPRedirection + # unpkg.com returns a relative URL in the `location` field. + location = uri.clone + location.path = URI.parse(response['location']).path + location + else + response.error! + end + end + local_root = 'lib/penthouse' + remote_root = "#{fetch['https://unpkg.com/penthouse']}/lib" + + File.write File.join(local_root, 'timeago.js'), + fetch["#{remote_root}/dist/timeago.js"] + File.write File.join(local_root, 'timeago.locales.js'), + fetch["#{remote_root}/dist/timeago.locales.min.js"] + src = fetch.call.body + # Remove the source mapping comment as this gem does not bundle source maps: + src.sub!(%r{^//# sourceMappingURL=.*\n\z}, '') + File.write(File.join('lib/penthouse/penthouse.js'), src) + + version_path = File.join('lib/popper_js/version.rb') + File.write version_path, + File.read(version_path) + .sub(/VERSION = '.*?'/, + "VERSION = '#{uri.path.split('@')[-1]}'") + + STDERR.puts "Updated from #{uri}" +end diff --git a/lib/npm_commands.rb b/lib/npm_commands.rb new file mode 100644 index 0000000..ae64cdd --- /dev/null +++ b/lib/npm_commands.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +# NPM wrapper with helpful error messages +class NpmCommands + + # @return [Boolean] whether the installation succeeded + def install # rubocop:disable Metrics/MethodLength + return false unless check_nodejs_installed + STDERR.puts + install_status = system('npm install') + 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 diff --git a/lib/rubygems_plugin.rb b/lib/rubygems_plugin.rb new file mode 100644 index 0000000..68404e0 --- /dev/null +++ b/lib/rubygems_plugin.rb @@ -0,0 +1,4 @@ +Gem.pre_install do + require_relative './npm_commands' + NpmCommands.new.install +end diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f2a0244 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,595 @@ +{ + "name": "critical-path-css-rails", + "version": "1.0.0", + "lockfileVersion": 1, + "dependencies": { + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "apartment": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/apartment/-/apartment-1.1.1.tgz", + "integrity": "sha1-/ZQGzcyodTWULxWzYKGrWkqpfiY=" + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "atob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/atob/-/atob-1.1.3.tgz", + "integrity": "sha1-lfE2KbEsOlGl0hWr3OKqnzL4B3M=" + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "babel-polyfill": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.23.0.tgz", + "integrity": "sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0=" + }, + "babel-runtime": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=" + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=" + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=" + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=" + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=" + }, + "concat-stream": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz", + "integrity": "sha1-U/fUPFHF5D+ByP3QMyHGMb5o1hE=" + }, + "core-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=" + }, + "css": { + "version": "git+https://github.com/pocketjoso/css.git#8ddea7e3cbc0a183ecf694a7a5fbc84326893893" + }, + "css-mediaquery": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", + "integrity": "sha1-aiw3NEkoYYYxxUvTPO3TAdoYvqA=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "debug": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true + }, + "es6-promise": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.0.5.tgz", + "integrity": "sha1-eILzCt3lskDM+n99eMVIMwlRrkI=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extract-zip": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.5.0.tgz", + "integrity": "sha1-ksz22B73Cp+kwXRxFMzvbYaIpsQ=" + }, + "extsprintf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=" + }, + "fs-extra": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=" + }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=" + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=" + }, + "get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" + }, + "har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=" + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=" + }, + "hasha": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", + "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=" + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=" + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "is-my-json-valid": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz", + "integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=" + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jodid25519": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", + "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", + "optional": true + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=" + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" + }, + "jsprim": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "kew": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", + "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=" + }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=" + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + }, + "mime-db": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" + }, + "mime-types": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mkdirp": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, + "penthouse": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/penthouse/-/penthouse-0.11.3.tgz", + "integrity": "sha1-XxyUB2toau7L01x8Fzpfer+WuA8=" + }, + "phantomjs-prebuilt": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.14.tgz", + "integrity": "sha1-1T0xH8+30dCN2yQBRVjxGIxRbaA=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=" + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", + "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=" + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=" + }, + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" + }, + "request": { + "version": "2.79.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", + "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=" + }, + "request-progress": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", + "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=" + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=" + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=" + }, + "source-map-resolve": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.3.1.tgz", + "integrity": "sha1-YQ9hIqRFuN1RU1oqcbeD38Ekh2E=" + }, + "source-map-url": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.3.0.tgz", + "integrity": "sha1-fsrxO1e80J2opAxdJp2zN5nUqvk=" + }, + "sshpk": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", + "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=" + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=" + }, + "tmp": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", + "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=" + }, + "tough-cookie": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=" + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" + }, + "verror": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=" + }, + "which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..266a315 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "critical-path-css-rails", + "version": "1.0.0", + "description": "NPM dependencies of critical-path-css-rails", + "private": true, + "directories": { + "lib": "lib" + }, + "dependencies": { + "penthouse": "=0.11.3" + }, + "license": "MIT" +} From 27cdd0217d3781cf62429ac853c77f8f5e9ec835 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 3 Jun 2017 20:14:25 +0100 Subject: [PATCH 2/3] Remove bundled penthouse.js --- lib/penthouse/penthouse.js | 601 ------------------------------------- 1 file changed, 601 deletions(-) delete mode 100644 lib/penthouse/penthouse.js diff --git a/lib/penthouse/penthouse.js b/lib/penthouse/penthouse.js deleted file mode 100644 index ff585dd..0000000 --- a/lib/penthouse/penthouse.js +++ /dev/null @@ -1,601 +0,0 @@ -/* -Penthouse CSS Critical Path Generator -https://github.com/pocketjoso/penthouse -Author: Jonas Ohlsson -License: MIT -Version: 0.3.4 - -USAGE: - phantomjs penthouse.js [options] - Options: - --width The viewport width in pixels. Defaults to 1300 - --height The viewport height in pixels. Defaults to 900 - - to run on HTTPS sites two flags must be passed in, directly after phantomjs in the call: - --ignore-ssl-errors=true --ssl-protocol=tlsv1 - -DEPENDENCIES - + "phantomjs" : "~1.9.7" - -*/ - - -(function() { "use strict"; -/* - * parser for the script - can be used both for the standalone node binary and the phantomjs script - */ - -/*jshint unused:false*/ - -var usageString = '[--width ] [--height ] '; - -function buildError(msg, problemToken, args) { - var error = new Error(msg + problemToken); - error.token = problemToken; - error.args = args; - throw error; -} - -// Parses the arguments passed in -// @returns { width, height, url, css } -// throws an error on wrong options or parsing error -function parseOptions(argsOriginal) { - var args = argsOriginal.slice(0), - validOptions = ['--width', '--height'], - parsed = {}, - val, - len = args.length, - optIndex, - option; - - if (len < 2) buildError('Not enough arguments, ', args); - - while (args.length > 2 && args[0].match(/^(--width|--height)$/)) { - optIndex = validOptions.indexOf(args[0]); - if (optIndex === -1) buildError('Logic/Parsing error ', args[0], args); - - // lose the dashes - option = validOptions[optIndex].slice(2); - val = args[1]; - - parsed[option] = parseInt(val, 10); - if (isNaN(parsed[option])) buildError('Parsing error when parsing ', val, args); - - // remove the two parsed arguments from the list - args = args.slice(2); - } - parsed.url = args[0]; - parsed.css = args[1]; - - if (!parsed.url) { - buildError('Missing url/path to html file', '', args); - } - - if (!parsed.css) { - buildError('Missing css file', '', args); - } - - - return parsed; -} - -if (typeof module !== 'undefined') { - module.exports = exports = { - parse: parseOptions, - usage: usageString - }; -} -/* -module for removing unused fontface rules - can be used both for the standalone node binary and the phantomjs script -*/ -/*jshint unused:false*/ - -function unusedFontfaceRemover (css){ - var toDeleteSections = []; - - //extract full @font-face rules - var fontFaceRegex = /(@font-face[ \s\S]*?\{([\s\S]*?)\})/gm, - ff; - - while ((ff = fontFaceRegex.exec(css)) !== null) { - - //grab the font name declared in the @font-face rule - //(can still be in quotes, f.e. 'Lato Web' - var t = /font-family[^:]*?:[ ]*([^;]*)/.exec(ff[1]); - if (typeof t[1] === 'undefined') - continue; //no font-family in @fontface rule! - - //rm quotes - var fontName = t[1].replace(/['"]/gm, ''); - - // does this fontname appear as a font-family or font (shorthand) value? - var fontNameRegex = new RegExp('([^{}]*?){[^}]*?font(-family)?[^:]*?:[^;]*' + fontName + '[^,;]*[,;]', 'gmi'); - - - var fontFound = false, - m; - - while ((m = fontNameRegex.exec(css)) !== null) { - if (m[1].indexOf('@font-face') === -1) { - //log('FOUND, keep rule'); - fontFound = true; - break; - } - } - if (!fontFound) { - //NOT FOUND, rm! - - //can't remove rule here as it will screw up ongoing while (exec ...) loop. - //instead: save indices and delete AFTER for loop - var closeRuleIndex = css.indexOf('}', ff.index); - //unshift - add to beginning of array - we need to remove rules in reverse order, - //otherwise indeces will become incorrect again. - toDeleteSections.unshift({ - start: ff.index, - end: closeRuleIndex + 1 - }); - } - } - //now delete the @fontface rules we registed as having no matches in the css - for (var i = 0; i < toDeleteSections.length; i++) { - var start = toDeleteSections[i].start, - end = toDeleteSections[i].end; - css = css.substring(0, start) + css.substring(end); - } - - return css; -}; - - - -if(typeof module !== 'undefined') { - module.exports = unusedFontfaceRemover; -} -/*jshint unused:false*/ - -/* === preFormatCSS === - * preformats the css to ensure we won't run into and problems in our parsing - * removes comments (actually would be anough to remove/replace {} chars.. TODO - * replaces } char inside content: '' properties. - */ - -function cssPreformatter (css){ - //remove comments from css (including multi-line coments) - css = css.replace(/\/\*[\s\S]*?\*\//g, ''); - - //replace Windows \r\n with \n, - //otherwise final output might get converted into /r/r/n - css = css.replace(/\r\n/gm, '\n'); - - //we also need to replace eventual close curly bracket characters inside content: '' property declarations, replace them with their ASCI code equivalent - //\7d (same as: '\' + '}'.charCodeAt(0).toString(16) ); - - var m, - regexP = /(content\s*:\s*['"][^'"]*)}([^'"]*['"])/gm, - matchedData = []; - - //for each content: '' rule that contains at least one end bracket ('}') - while ((m = regexP.exec(css)) !== null) { - //we need to replace ALL end brackets in the rule - //we can't do it in here, because it will mess up ongoing exec, store data and do after - - //unshift - add to beginning of array - we need to remove rules in reverse order, - //otherwise indeces will become incorrect. - matchedData.unshift({ - start: m.index, - end: m.index + m[0].length, - replaceStr: m[0].replace(/\}/gm, '\\7d') - }); - } - - for (var i = 0; i < matchedData.length; i++) { - var item = matchedData[0]; - css = css.substring(0, item.start) + item.replaceStr + css.substring(item.end); - } - - return css; -}; - -if(typeof module !== 'undefined') { - module.exports = cssPreformatter; -} -var standaloneMode = true; -'use strict'; -var standaloneMode = standaloneMode || false; - -var page = require('webpage').create(), - fs = require('fs'), - system = require('system'), - DEBUG = false, - stdout = system.stdout; // for using this as a file - -var combineArgsString = function(argsArr) { - return [].join.call(argsArr, ' ') + '\n'; -}; - -// monkey patch for directing errors to stderr -// https://github.com/ariya/phantomjs/issues/10150#issuecomment-28707859 -var errorlog = function() { - system.stderr.write(combineArgsString(arguments)); -}; - -var debug = function() { - if (DEBUG) errorlog('DEBUG: ' + combineArgsString(arguments)); -}; - -// discard stdout from phantom exit; -var phantomExit = function(code) { - if (page) { - page.close(); - } - setTimeout(function() { - phantom.exit(code); - }, 0); -}; - -//don't confuse analytics more than necessary when visiting websites -page.settings.userAgent = 'Penthouse Critical Path CSS Generator'; - -/* prevent page JS errors from being output to final CSS */ -page.onError = function(msg, trace) { - //do nothing -}; - -page.onResourceError = function(resourceError) { - page.reason = resourceError.errorString; - page.reason_url = resourceError.url; -}; - -var main = function(options) { - debug('main(): ', JSON.stringify(options)); -//final cleanup -//remove all empty rules, and remove leading/trailing whitespace - try { - var f = fs.open(options.css, 'r'); - - //preformat css - var cssPreformat; - if (standaloneMode) { - cssPreformat = cssPreformatter; - } else { - cssPreformat = require('./css-preformatter.js'); - } - options.css = cssPreformat(f.read()); - } catch (e) { - errorlog(e); - phantomExit(1); - } - - // start the critical path CSS generation - getCriticalPathCss(options); -}; - -function cleanup(css) { - //remove all animation rules, as keyframes have already been removed - css = css.replace(/(-webkit-|-moz-|-ms-|-o-)?animation[ ]?:[^;{}]*;/gm, ''); - //remove all empty rules, and remove leading/trailing whitespace - return css.replace(/[^{}]*\{\s*\}/gm, '').trim(); -} - -/* Final function - * Get's called from getCriticalPathCss when CSS extraction from page is done*/ -page.onCallback = function(css) { - debug('phantom.onCallback'); - - try { - if (css) { - // we are done - clean up the final css - var finalCss = cleanup(css); - - // remove unused @fontface rules - var ffRemover; - if (standaloneMode) { - ffRemover = unusedFontfaceRemover; - } else { - ffRemover = require('./unused-fontface-remover.js'); - } - finalCss = ffRemover(finalCss); - - if(finalCss.trim().length === 0){ - errorlog('Note: Generated critical css was empty for URL: ' + options.url); - } - - // return the critical css! - stdout.write(finalCss); - phantomExit(0); - } else { - // No css. This is not an error on our part - // but still safer to warn the end user, in case they made a mistake - errorlog('Note: Generated critical css was empty for URL: ' + options.url); - // for consisteny, still generate output (will be empty) - stdout.write(css); - phantomExit(0); - } - - } catch (ex) { - debug('phantom.onCallback -> error', ex); - errorlog('error: ' + ex); - phantomExit(1); - } -}; - -/* - * Tests each selector in css file at specified resolution, - * to see if any such elements appears above the fold on the page - * modifies CSS - removes selectors that don't appear, and empty rules - * - * @param options.url the url as a string - * @param options.css the css as a string - * @param options.width the width of viewport - * @param options.height the height of viewport - ---------------------------------------------------------*/ -function getCriticalPathCss(options) { - debug('getCriticalPathCss():', JSON.stringify(options)); - - page.viewportSize = { - width: options.width, - height: options.height - }; - - page.open(options.url, function(status) { - if (status !== 'success') { - errorlog('Error opening url \'' + page.reason_url + '\': ' + page.reason); - phantomExit(1); - } else { - - debug('Starting sandboxed evaluation of CSS\n', options.css); - // sandboxed environments - no outside references - // arguments and return value must be primitives - // @see http://phantomjs.org/api/webpage/method/evaluate.html - page.evaluate(function sandboxed(css) { - var h = window.innerHeight, - renderWaitTime = 100, //ms TODO: user specifiable through options object - finished = false, - currIndex = 0, - forceRemoveNestedRule = false; - - //split CSS so we can value the (selector) rules separately. - //but first, handle stylesheet initial non nested @-rules. - //they don't come with any associated rules, and should all be kept, - //so just keep them in critical css, but don't include them in split - var splitCSS = css.replace(/@(import|charset|namespace)[^;]*;/g, ''); - var split = splitCSS.split(/[{}]/g); - - var getNewValidCssSelector = function(i) { - var newSel = split[i]; - /* HANDLE Nested @-rules */ - - /*Case 1: @-rule with CSS properties inside [REMAIN] - Can't remove @font-face rules here, don't know if used or not. - Another check at end for this purpose. - */ - if (/@(font-face)/gi.test(newSel)) { - //skip over this rule - currIndex = css.indexOf('}', currIndex) + 1; - return getNewValidCssSelector(i + 2); - } - /*Case 2: @-rule with CSS properties inside [REMOVE] - @page - This case doesn't need any special handling, - as this "selector" won't match anything on the page, - and will therefor be removed, together with it's css props - */ - - /*Case 4: @-rule with full CSS (rules) inside [REMOVE] - @media print|speech|aural, @keyframes - Delete this rule and all its contents - doesn't belong in critical path CSS - */ - else if (/@(media (print|speech|aural)|(([a-z\-])*keyframes))/gi.test(newSel)) { - //force delete on child css rules - forceRemoveNestedRule = true; - return getNewValidCssSelector(i + 1); - } - - /*Case 3: @-rule with full CSS (rules) inside [REMAIN] - This test is executed AFTER Case 4, - since we here match every remaining @media, - after @media print has been removed by Case 4 rule) - - just skip this particular line (i.e. keep), and continue checking the CSS inside as normal - */ - else if (/@(media|(-moz-)?document|supports)/gi.test(newSel)) { - return getNewValidCssSelector(i + 1); - } - /* - Resume normal execution after end of @-media rule with inside CSS rules (Case 3) - Also identify abrupt file end. - */ - else if (newSel.trim().length === 0) { - //abrupt file end - if (i + 1 >= split.length) { - //end of file - finished = true; - return false; - } - //end of @-rule (Case 3) - forceRemoveNestedRule = false; - return getNewValidCssSelector(i + 1); - } - return i; - }; - - var removeSelector = function(sel, selectorsKept) { - var selPos = css.indexOf(sel, currIndex); - - //check what comes next: { or , - var nextComma = css.indexOf(',', selPos); - var nextOpenBracket = css.indexOf('{', selPos); - - if (selectorsKept > 0 || (nextComma > 0 && nextComma < nextOpenBracket)) { - //we already kept selectors from this rule, so rule will stay - - //more selectors in selectorList, cut until (and including) next comma - if (nextComma > 0 && nextComma < nextOpenBracket) { - css = css.substring(0, selPos) + css.substring(nextComma + 1); - } - //final selector, cut until open bracket. Also remove previous comma, as the (new) last selector should not be followed by a comma. - else { - var prevComma = css.lastIndexOf(',', selPos); - css = css.substring(0, prevComma) + css.substring(nextOpenBracket); - } - } else { - //no part of selector (list) matched elements above fold on page - remove whole rule CSS rule - var endRuleBracket = css.indexOf('}', nextOpenBracket); - - css = css.substring(0, selPos) + css.substring(endRuleBracket + 1); - } - }; - - - var processCssRules = function() { - for (var i = 0; i < split.length; i = i + 2) { - //step over non DOM CSS selectors (@-rules) - i = getNewValidCssSelector(i); - - //reach end of CSS - if (finished) { - //call final function to exit outside of phantom evaluate scope - window.callPhantom(css); - } - - var fullSel = split[i]; - //fullSel can contain combined selectors - //,f.e. body, html {} - //split and check one such selector at the time. - var selSplit = fullSel.split(','); - //keep track - if we remove all selectors, we also want to remove the whole rule. - var selectorsKept = 0; - var aboveFold; - - for (var j = 0; j < selSplit.length; j++) { - var sel = selSplit[j]; - - //some selectors can't be matched on page. - //In these cases we test a slightly modified selectors instead, temp. - var temp = sel; - - if (sel.indexOf(':') > -1) { - //handle special case selectors, the ones that contain a semi colon (:) - //many of these selectors can't be matched to anything on page via JS, - //but that still might affect the above the fold styling - - //these psuedo selectors depend on an element, - //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) - temp = temp.replace(/(:?:before|:?:after)*/g, ''); - - //if selector is purely psuedo (f.e. ::-moz-placeholder), just keep as is. - //we can't match it to anything on page, but it can impact above the fold styles - if (temp.replace(/:[:]?([a-zA-Z0-9\-\_])*/g, '').trim().length === 0) { - currIndex = css.indexOf(sel, currIndex) + sel.length; - selectorsKept++; - continue; - } - - //handle browser specific psuedo selectors bound to elements, - //Example, button::-moz-focus-inner, input[type=number]::-webkit-inner-spin-button - //remove browser specific pseudo and test for element - temp = temp.replace(/:?:-[a-z-]*/g, ''); - } - - if (!forceRemoveNestedRule) { - //now we have a selector to test, first grab any matching elements - var el; - try { - el = document.querySelectorAll(temp); - } catch (e) { - //not a valid selector, remove it. - removeSelector(sel, 0); - continue; - } - - //check if selector matched element(s) on page.. - aboveFold = false; - - for (var k = 0; k < el.length; k++) { - var testEl = el[k]; - //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) - testEl.style.clear = 'none'; - - //check to see if any matched element is above the fold on current page - //(in current viewport size) - if (testEl.getBoundingClientRect().top < h) { - //then we will save this selector - aboveFold = true; - selectorsKept++; - - //update currIndex so we only search from this point from here on. - currIndex = css.indexOf(sel, currIndex); - - //set clear style back to what it was - testEl.style.clear = ''; - //break, because matching 1 element is enough - break; - } - //set clear style back to what it was - testEl.style.clear = ''; - } - } else { - aboveFold = false; - } //force removal of selector - - //if selector didn't match any elements above fold - delete selector from CSS - if (aboveFold === false) { - //update currIndex so we only search from this point from here on. - currIndex = css.indexOf(sel, currIndex); - //remove seletor (also removes rule, if nnothing left) - removeSelector(sel, selectorsKept); - } - } - //if rule stayed, move our cursor forward for matching new selectors - if (selectorsKept > 0) { - currIndex = css.indexOf('}', currIndex) + 1; - } - } - - //we're done - call final function to exit outside of phantom evaluate scope - window.callPhantom(css); - }; - - //give some time (renderWaitTime) for sites like facebook that build their page dynamically, - //otherwise we can miss some selectors (and therefor rules) - //--tradeoff here: if site is too slow with dynamic content, - // it doesn't deserve to be in critical path. - setTimeout(processCssRules, renderWaitTime); - - }, options.css); - } - }); -} - -var parser, parse, usage, options; - -// test to see if we are running as a standalone script -// or as part of the node module -if (standaloneMode) { - parse = parseOptions; - usage = usageString; -} else { - parser = require('../options-parser'); - parse = parser.parse; - usage = parser.usage; -} - -try { - options = parse(system.args.slice(1)); -} catch (ex) { - - errorlog('Caught error parsing arguments: ' + ex.message); - - // the usage string does not make sense to show if running via Node - if(standaloneMode) { - errorlog('\nUsage: phantomjs penthouse.js ' + usage); - } - - phantomExit(1); -} - -// set defaults -if (!options.width) options.width = 1300; -if (!options.height) options.height = 900; - -main(options); -})(); \ No newline at end of file From e50eb376e34b26ec65ea028f7023e632a1cf5785 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 3 Jun 2017 20:31:06 +0100 Subject: [PATCH 3/3] Add a penthouse wrapper script --- .codeclimate.yml | 2 -- critical-path-css-rails.gemspec | 2 -- lib/critical_path_css/css_fetcher.rb | 31 +++++++++++++-------- lib/fetch-css.js | 40 ++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 lib/fetch-css.js diff --git a/.codeclimate.yml b/.codeclimate.yml index df67882..7561b07 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,4 +1,2 @@ languages: Ruby: true -exclude_paths: - - lib/penthouse/penthouse.js \ No newline at end of file diff --git a/critical-path-css-rails.gemspec b/critical-path-css-rails.gemspec index 404e650..6a7b43c 100644 --- a/critical-path-css-rails.gemspec +++ b/critical-path-css-rails.gemspec @@ -10,8 +10,6 @@ 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' diff --git a/lib/critical_path_css/css_fetcher.rb b/lib/critical_path_css/css_fetcher.rb index 90fbb2f..afc5e2a 100644 --- a/lib/critical_path_css/css_fetcher.rb +++ b/lib/critical_path_css/css_fetcher.rb @@ -1,9 +1,11 @@ +require 'open3' + module CriticalPathCss class CssFetcher - require 'phantomjs' require 'critical_path_css/configuration' - PENTHOUSE_PATH = "#{File.dirname(__FILE__)}/../penthouse/penthouse.js" + JS_FETCHER_PATH = File.expand_path('../fetch-css.js', + File.dirname(__FILE__)).freeze def initialize @config = Configuration.new @@ -20,15 +22,22 @@ 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, + cssPath: @config.css_path, + phantomJsOptions: { + :'ignore-ssl-errors' => true, + :'ssl-protocol' => 'tlsv1' + } + } + out, err, st = Open3.capture3('node', JS_FETCHER_PATH, JSON.dump(options)) + unless st.exitstatus.zero? + STDOUT.puts out + STDERR.puts err + raise "Failed to get CSS for route #{route}\n" \ + " with options=#{options.inspect}" + end + out end end end diff --git a/lib/fetch-css.js b/lib/fetch-css.js new file mode 100644 index 0000000..911e563 --- /dev/null +++ b/lib/fetch-css.js @@ -0,0 +1,40 @@ +const penthouse = require('penthouse'); +const fs = require('fs'); + +const argOptions = JSON.parse(process.argv[2]); +const penthouseOptions = Object.assign({ + url: null, // required + css: null, // required + // OPTIONAL params + width: 1300, // viewport width + height: 900, // viewport height + forceInclude: [ // CSS selectors to always include, e.g.: + // '.keepMeEvenIfNotSeenInDom', + // /^\.regexWorksToo/ + ], + timeout: 30000, // ms; abort critical CSS generation after this timeout + strict: false, // set to true to throw on CSS errors (will run faster if no errors) + maxEmbeddedBase64Length: 1000, // characters; strip out inline base64 encoded resources larger than this + userAgent: 'Penthouse Critical Path CSS Generator', // specify which user agent string when loading the page + renderWaitTime: 100, // ms; render wait timeout before CSS processing starts (default: 100) + blockJSRequests: true, // set to false to load (external) JS (default: true) + phantomJsOptions: { // see `phantomjs --help` for the list of all available options + // 'proxy': 'http://proxy.company.com:8080', + // 'ssl-protocol': 'SSLv3' + }, + customPageHeaders: { + 'Accept-Encoding': 'identity' // add if getting compression errors like 'Data corrupted' + } +}, argOptions); + +const STDOUT_FD = 1; +const STDERR_FD = 2; + +penthouse(penthouseOptions).then(function(criticalCss) { + fs.writeSync(STDOUT_FD, criticalCss); + fs.fsyncSync(STDOUT_FD); +}).catch(function(err) { + fs.writeSync(STDERR_FD, err); + fs.fsyncSync(STDERR_FD); + process.exit(1); +});