Philipp Michalski

Software engineer – deeply passionate about coding, making, understanding and simplifying things

Using Bootstrap in a gem based Jekyll theme

Having external resources included in your gem-based Jekyll theme is pretty messy. Well not exactly: Just place your styles in the _sass directory and drop everything else in assets. Sounds good, doesn’t work – at least for me.

In this site’s theme, I used Bootstrap, so I researched ways to import its resources into the gem-based theme. Those include some stylesheets, Bootstraps own javascript with its dependencies jQuery and popper.js. There are a lot of options out there to get them into your project. Those are the most frequent solutions I found:

  • Load them via CDN.
  • Copy the resources to their appropriate destination by hand.
  • Use NPM or any other JS package manager and add node_modules to your sass load paths, and refer to the javascript within node_modules.
  • Use the jekyll-assets plugin to fetch the resources from their gem-packaged versions.
  • Use the jekyll-bootstrap-sass plugin and include styles from the gem and configure asset loading in _config.yml.

I really liked none of them, there was a show stopper in every solution. But first of all my requirements:

  1. Easy updates and version locking.
  2. All work is done in the theme gem.
  3. No manual copying of files or explicit specifying of copy paths (minimal update effort).
  4. Fetching the resources via any resources manager, preferably from the ruby gems repositories as Jekyll also depends on those technologies.
  5. Benefit from partial includes and sass.

#1 and #3 circumvented the manual solution. The CDN approach was not possible because of #5. The node solution of #4 would work but having NPM as another non-ruby dependency sounded wrong. I like how both of the Jekyll plugins work, but they do not function with a gem-based theme or require additional config in the _config.yml of the site using the theme.

So I started my solution with the premise of putting all resources into their Jekyll destinations: Stylesheets in the _sass dir and all additional resources within assets. I also copied the approach of fetching resources from the gem-packaged versions. The only part missing: How to get the files from the gems to the Jekyll dirs. I went with rake as it is Ruby’s default build tool and automatically there when you start with Jekyll. This is what I did:

Include the additional gems in the themes *.gemspec:

spec.add_development_dependency "jquery-rails", "~> 4.3.3"
spec.add_development_dependency "bootstrap", "~> 4.1.3"

Install them via:

bundle install

Add the following Rakefile in the theme’s root directory:

sass_dir = '_sass/vendor'
assets_dir = 'assets/vendor'

task default: %w[copy]

task :clean do
  puts FileUtils.rm_rf(sass_dir)
  puts FileUtils.rm_rf(assets_dir)
end

task :copy do
  ['bootstrap','jquery-rails', 'popper.js'].each do |gem|
    path = %x{ #{"bundle show %s" % gem} }.strip()

    if Dir.exists?(path + '/assets') then
      path = path + '/assets'
    elsif Dir.exists?(path + '/vendor/assets') then
      path = path + '/vendor/assets'
    else
      path = nil
    end

    if path then
      resource_paths = Dir.glob(path + '/*')

      sass_paths = resource_paths.select{ |file_path| file_path.end_with?('/stylesheets')}
      if not sass_paths.empty? then
        sass_target_dir = [ sass_dir, gem ].join('/')
        unless Dir.exists?(sass_target_dir) then
          FileUtils.mkdir_p(sass_target_dir)
        end
        FileUtils.cp_r(sass_paths, sass_target_dir)
      end

      assets_paths = resource_paths.reject{ |file_path| file_path.end_with?('/stylesheets')}
      if not assets_paths.empty? then
        assets_target_dir = [ assets_dir, gem ].join('/')
        unless Dir.exists?(assets_target_dir) then
          FileUtils.mkdir_p(assets_target_dir)
        end
        FileUtils.cp_r(assets_paths, assets_target_dir)
      end
    end
  end
end

Run it to copy all resources from the gems to the theme directories:

rake copy

Include the Bootstrap styles in your main sass file:

@import 'vendor/bootstrap/stylesheets/bootstrap';

Include the javascripts in your HTML template:

<body><script src="/assets/vendor/jquery-rails/javascripts/jquery3.min.js"></script>
  <script src="/assets/vendor/popper.js/javascripts/popper.js"></script>
  <script src="/assets/vendor/bootstrap/javascripts/bootstrap.min.js"></script>
</body>
</html>

To update the resources just change the dependency version in your themes *.gemspec, and run:

bundle install
rake copy

That’s it!