├── README.md └── uncss.rb /README.md: -------------------------------------------------------------------------------- 1 | # jekyll-uncss 2 | 3 | A Jekyll plugin that uses [uncss](https://github.com/giakki/uncss) to remove unused css rules in selected stylesheets. 4 | 5 | ## Setup 6 | 7 | cd YOUR_JEKYLL_DIR/_plugins && git submodule add https://github.com/episource/jekyll-uncss.git 8 | 9 | This plugin depends on uncss. See [uncss](https://github.com/giakki/uncss) for installation instructions. 10 | 11 | ## Configure 12 | 13 | The plugin is executed in jekyll environment `JEKYLL_ENV=production`, only. Therefore jekyll must be invoked like `JEKYLL_ENV=production jekyll [...]`. 14 | 15 | Configure uncss by adding a `uncss` node to your _config.yml. The `stylesheets` option is mandatory, all others are optional. By default all html files (`**/*.html`) are considered. Use the `files` option to change this. 16 | 17 | For most option there's a corresponding [uncss option](https://github.com/giakki/uncss) and the configuration is just passed through (maybe with some path adjustments). A noticable exception is the `stylesheets` option: Instead of passing all css files to uncss at once (which would result in the stylesheets being merged), the css files are passed one by one. So each file given is processed separately. 18 | 19 | uncss: 20 | stylesheets: # a list of stylesheets to be processed; mandatory 21 | - assets/css/main.css 22 | files: # html files to consider, globs are supported; default: **/*.html 23 | - "**/*.html" 24 | - "**/*.htm" 25 | compress: true # compress resulting css with sass; default: false 26 | ignore: # always keep rules for these selectors; default: none 27 | - ".is-loading" 28 | - "#titleBar" 29 | media: # additional media queries to consider; default: undefined 30 | - print 31 | timeout: 30 # how long to wait for the JS to be loaded in milliseconds; default: undefined 32 | banner: false # should the output include a banner comment; default: undefined 33 | 34 | Note: The `ignore` option can also be included as a css comment. See [uncss](https://github.com/giakki/uncss) documentation for details. 35 | 36 | ## FAQ 37 | ### `Error: Could not load script` in output files using uncss 0.17.* 38 | Set configuration option `timeout` or increase its value: [uncss-0.17.0 started to evaluate scripts](https://github.com/uncss/uncss/blob/0.17.0/src/jsdom.js#L42). Without timeout or with timeout to short, loading of scripts may fail. The timeout option is given in milliseconds. 39 | -------------------------------------------------------------------------------- /uncss.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'sass' 4 | require 'tempfile' 5 | require 'json' 6 | 7 | Jekyll::Hooks.register(:site, :post_write) do |site| 8 | next if ENV['JEKYLL_ENV'] != 'production' 9 | 10 | print 'UnCSS'.rjust(18), ': ' 11 | config = site.config['uncss'] || {} 12 | 13 | unless config.key?('stylesheets') 14 | raise 'Missing option \'uncss.stylesheets\'!' 15 | end 16 | 17 | # Prefix files with site path 18 | files = config.fetch('files', ['**/*.html']).collect do |file| 19 | File.join(site.dest, file) 20 | end 21 | 22 | # Produce UnCSS instance 23 | uncss = Jekyll::UnCSS.new(files, 24 | htmlroot: site.dest, 25 | ignore: config['ignore'], 26 | media: config['media'], 27 | timeout: config['timeout'], 28 | banner: config['banner']) 29 | 30 | # Process each given stylesheet 31 | config['stylesheets'].each do |stylesheet| 32 | uncss.process(stylesheet, config['compress']) do |output| 33 | File.open(File.join(site.dest, stylesheet), 'w') do |fd| 34 | fd.write(output) # Writes new css back to disk 35 | end 36 | end 37 | end 38 | 39 | print 'Complete, processed ', config['stylesheets'].length, ' css file(s)' 40 | puts 41 | end 42 | 43 | module Jekyll 44 | # Handles the stripping of unnessissary css. 45 | class UnCSS 46 | class Error < StandardError; end 47 | 48 | def initialize(files, **options) 49 | @files = [files].flatten 50 | @options = options.compact 51 | end 52 | 53 | def process(css, compress) 54 | make_config(css) 55 | result = uncss(compress) 56 | 57 | yield(result) if block_given? 58 | result 59 | ensure 60 | cleanup_config 61 | end 62 | 63 | private 64 | 65 | def uncss(compress) 66 | path = @temp_file.path 67 | files = @files.join("' '") 68 | result = `uncss --uncssrc '#{path}' '#{files}'` 69 | result = strip_banner(result) 70 | result = Sass.compile(result, style: :compressed) if compress 71 | result.strip! 72 | result << "\n" 73 | rescue Error => e 74 | raise Error, "uncss failed: #{e} :: #{result}" 75 | end 76 | 77 | def strip_banner(result) 78 | return result unless @options[:banner] == false 79 | return result unless result.start_with?('/*** uncss> filename: ') 80 | 81 | result.partition('***/').last 82 | end 83 | 84 | def make_config(css) 85 | options = @options.clone 86 | 87 | # uncss treats absolute stylesheet paths as relative to htmlroot 88 | options[:stylesheets] = [(css.start_with?('/') ? css : ('/' + css))] 89 | 90 | cleanup_config 91 | @temp_file = Tempfile.new('uncssrc') 92 | @temp_file.write(options.to_json) 93 | @temp_file.flush 94 | end 95 | 96 | def cleanup_config 97 | return if @temp_file.nil? 98 | 99 | @temp_file.close 100 | @temp_file.unlink 101 | end 102 | end 103 | end 104 | --------------------------------------------------------------------------------