├── .gitignore ├── .rubocop.yml ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── jekyll_asset_pipeline.gemspec ├── lib ├── jekyll_asset_pipeline.rb └── jekyll_asset_pipeline │ ├── asset.rb │ ├── compressor.rb │ ├── converter.rb │ ├── extensions │ ├── jekyll │ │ ├── site.rb │ │ └── site_extensions.rb │ ├── liquid │ │ ├── asset_tag.rb │ │ ├── asset_tags │ │ │ ├── css_asset_tag.rb │ │ │ └── javascript_asset_tag.rb │ │ └── liquid_block_extensions.rb │ └── ruby │ │ └── subclass_tracking.rb │ ├── pipeline.rb │ ├── template.rb │ ├── templates │ ├── css_tag_template.rb │ ├── javascript_tag_template.rb │ └── template_helper.rb │ └── version.rb └── spec ├── helper.rb ├── helpers └── extensions │ └── ruby │ └── module.rb ├── jekyll_asset_pipeline ├── asset_spec.rb ├── compressor_spec.rb ├── converter_spec.rb ├── extensions │ ├── jekyll │ │ └── site_spec.rb │ ├── jekyll_site_extensions_spec.rb │ ├── liquid │ │ ├── asset_tag_spec.rb │ │ ├── asset_tags │ │ │ ├── css_asset_tag_spec.rb │ │ │ └── java_script_asset_tag_spec.rb │ │ └── liquid_block_extensions_spec.rb │ └── ruby │ │ └── subclass_tracking_spec.rb ├── integration_spec.rb ├── pipeline_spec.rb ├── template_spec.rb └── version_spec.rb └── resources └── source ├── _assets ├── bar.css ├── bar.scss ├── foo.css ├── foo.scss ├── uncompressed.css ├── unconverted.baz ├── unconverted.css.bar.baz ├── unconverted.css.baz └── unconverted.css.baz.bar ├── _plugins └── jekyll_asset_pipeline.rb └── css-manifest.yml /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .DS_Store 3 | .bundle 4 | *.gem 5 | .gems 6 | Gemfile.lock 7 | spec/resources/source/.asset_pipeline/ 8 | coverage 9 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.3 3 | 4 | # Do not count block length in spec files 5 | Metrics/BlockLength: 6 | ExcludedMethods: ['describe', 'it', 'context'] 7 | 8 | Metrics/MethodLength: 9 | Max: 15 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | rvm: 4 | - 2.3 5 | - 2.4 6 | - 2.5 7 | - 2.6 8 | 9 | script: 10 | - bundle exec rake test 11 | - bundle exec rubocop 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.6.2 (2019-10-16) 4 | 5 | * Support Jekyll 4.0 [#53] 6 | 7 | ## 0.6.1 (2019-06-27) 8 | 9 | * Drop support of Ruby 2.2 10 | 11 | ## 0.6.0 (2017-12-20) 12 | 13 | * Merged JAPR into Jekyll Asset Pipeline 14 | 15 | ## 0.5.0, 0.5.1 16 | 17 | * Test releases 18 | 19 | ## 0.4.1 (2017-12-08) 20 | 21 | * [#6] __Test coverage increased to 100%__ 22 | * [#35] __Updated rake dependency to 12.0__ 23 | * [#34] __Fixed or mitigated all Rubocop offenses__ 24 | * [#31] __Documented modules and classes__ 25 | * [#29] Various README updates 26 | * [#28] Gemspec file updates (version dependencies and typos) 27 | * [#31] Fix random coverage jumps 28 | * [#33] Rescue StandardError instead of Exception 29 | * Removed CodeClimate integration 30 | 31 | ## 0.4 (2017-12-03) 32 | 33 | * [#20] Support Jekyll 3.5, Liquid 4.0 34 | * [#25] Permit root level output of asset files 35 | * Fix and refactor to eliminate Rubocop offenses 36 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'http://rubygems.org' 4 | 5 | group :test do 6 | gem 'coveralls', require: false 7 | gem 'rubocop' 8 | end 9 | 10 | gemspec 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012 Matt Hodan (http://www.matthodan.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the 'Software'), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jekyll Asset Pipeline 2 | 3 | [![Gem Version](https://img.shields.io/gem/v/jekyll_asset_pipeline.svg)](https://rubygems.org/gems/jekyll_asset_pipeline) 4 | [![Build Status](https://img.shields.io/travis/matthodan/jekyll-asset-pipeline.svg)](https://travis-ci.org/matthodan/jekyll-asset-pipeline) 5 | [![Coveralls Status](https://img.shields.io/coveralls/github/matthodan/jekyll-asset-pipeline/master.svg)](https://coveralls.io/r/matthodan/jekyll-asset-pipeline?branch=master) 6 | 7 | [Jekyll Asset Pipeline](http://www.matthodan.com/2012/11/22/jekyll-asset-pipeline.html) is a powerful asset pipeline that automatically collects, converts and compresses / minifies your site's JavaScript and CSS assets when you compile your [Jekyll](http://jekyllrb.com/) site. 8 | 9 | ## Table of Contents 10 | 11 | - [Features](#features) 12 | - [How It Works](#how-it-works) 13 | - [Getting Started](#getting-started) 14 | - [Asset Preprocessing](#asset-preprocessing) 15 | - [CoffeeScript](#coffeescript) 16 | - [SASS / SCSS](#sass--scss) 17 | - [LESS](#less) 18 | - [Successive Preprocessing](#successive-preprocessing) 19 | - [Asset Compression](#asset-compression) 20 | - [Yahoo's YUI Compressor](#yahoos-yui-compressor) 21 | - [Google's Closure Compiler](#googles-closure-compiler) 22 | - [Templates](#templates) 23 | - [Configuration](#configuration) 24 | - [Octopress](#octopress) 25 | - [Contribute](#contribute) 26 | - [Community](#community) 27 | - [Credits](#credits) 28 | - [License](#license) 29 | 30 | ## Features 31 | 32 | - Declarative dependency management via asset manifests 33 | - Asset preprocessing/conversion (supports [CoffeeScript](http://coffeescript.org/), [Sass / Scss](http://sass-lang.com/), [Less](http://lesscss.org/), [Erb](http://ruby-doc.org/stdlib-2.2.0/libdoc/erb/rdoc/ERB.html), etc.) 34 | - Asset compression (supports [YUI Compressor](http://yui.github.io/yuicompressor/), [Closure Compiler](https://developers.google.com/closure/compiler/), etc.) 35 | - Fingerprints bundled asset filenames with MD5 hashes for better browser caching 36 | - Automatic generation of HTML `link` and `script` tags that point to bundled assets 37 | - Integrates seamlessly into Jekyll's workflow, including auto site regeneration 38 | 39 | ## How It Works 40 | 41 | Jekyll Asset Pipeline's workflow can be summarized as follows: 42 | 43 | 1. Reviews site markup for instances of the `css_asset_tag` and `javascript_asset_tag` Liquid tags. Each occurrence of either of these tags identifies when a new bundle needs to be created and outlines (via a manifest) which assets to include in the bundle. 44 | 2. Collects raw assets based on the manifest and runs them through converters / preprocessors (if necessary) to convert them into valid CSS or JavaScript. 45 | 3. Combines the processed assets into a single bundle, compresses the bundled assets (if desired) and saves the compressed bundle to the `_site` output folder. 46 | 4. Replaces `css_asset_tag` and `javascript_asset_tag` Liquid tags with HTML `link` and `script` tags, respectively, that link to the finished bundles. 47 | 48 | ## Getting Started 49 | 50 | Jekyll Asset Pipeline is extremely easy to add to your Jekyll project and has no incremental dependencies beyond those required by Jekyll. Once you have a basic Jekyll site up and running, follow the steps below to install and configure Jekyll Asset Pipeline. 51 | 52 | 1. Install the `jekyll_asset_pipeline` gem via [Rubygems](http://rubygems.org/). 53 | 54 | ``` bash 55 | $ gem install jekyll_asset_pipeline 56 | ``` 57 | 58 | If you are using [Bundler](http://gembundler.com/) to manage your project's gems, you can just add `jekyll_asset_pipeline` to your Gemfile and run `bundle install`. 59 | 60 | 2. Add a `_plugins` folder to your project if you do not already have one. Within the `_plugins` folder, add a file named `jekyll_asset_pipeline.rb` with the following require statement as its contents. 61 | 62 | ``` ruby 63 | require 'jekyll_asset_pipeline' 64 | ``` 65 | 66 | 3. Move your assets into a Jekyll ignored folder (i.e. a folder that begins with an underscore `_`) so that Jekyll won't include these raw assets in the site output. It is recommended to use an `_assets` folder to hold your site's assets. 67 | 68 | 4. Add the following [Liquid](http://liquidmarkup.org/) blocks to your site's HTML `head` section. These blocks will be converted into HTML `link` and `script` tags that point to bundled assets. Within each block is a manifest of assets to include in the bundle. Assets are included in the same order that they are listed in the manifest. Replace the `foo` and `bar` assets with your site's assets. At this point we are just using plain old javascript and css files (hence the `.js` and `.css` extensions). See the [Asset Preprocessing](#asset-preprocessing) section to learn how to include files that must be preprocessed (e.g. CoffeeScript, Sass, Less, Erb, etc.). Name the bundle by including a string after the opening tag. We've named our bundles "global" in the below example. 69 | 70 | ``` html 71 | {% css_asset_tag global %} 72 | - /_assets/foo.css 73 | - /_assets/bar.css 74 | {% endcss_asset_tag %} 75 | 76 | {% javascript_asset_tag global %} 77 | - /_assets/foo.js 78 | - /_assets/bar.js 79 | {% endjavascript_asset_tag %} 80 | ``` 81 | Asset manifests must be formatted as YAML arrays and include full paths to each asset from the root of the project. YAML [does not allow tabbed markup](http://www.yaml.org/faq.html), so you must use spaces when indenting your YAML manifest or you will get an error when you compile your site. If you are using assets that must be preprocessed, you should append the appropriate extension (e.g. '.js.coffee', '.css.less') as discussed in the [Asset Preprocessing](#asset-preprocessing) section. 82 | 83 | 5. Run the `jekyll build` command to compile your site. You should see an output that includes the following Jekyll Asset Pipeline status messages. 84 | 85 | ``` bash 86 | $ jekyll build 87 | Generating... 88 | Asset Pipeline: Processing 'css_asset_tag' manifest 'global' 89 | Asset Pipeline: Saved 'global-md5hash.css' to 'yoursitepath/assets' 90 | Asset Pipeline: Processing 'javascript_asset_tag' manifest 'global' 91 | Asset Pipeline: Saved 'global-md5hash.js' to 'yoursitepath/assets' 92 | ``` 93 | 94 | If you do not see these messages, check that you have __not__ set Jekyll's `safe` option to `true` in your site's `_config.yml`. If the `safe` option is set to `true`, Jekyll will not run plugins. 95 | 96 | That is it! You should now have bundled assets. Look in the `_site` folder of your project for an `assets` folder that contains the bundled assets. HTML tags that point to these assets have been placed in the HTML output where you included the Liquid blocks. *You may notice that your assets have not been converted or compressed-- we will add that functionality next.* 97 | 98 | ## Asset Preprocessing 99 | 100 | Asset preprocessing (i.e. conversion) allows us to write our assets in languages such as [CoffeeScript](http://coffeescript.org/), [Sass](http://sass-lang.com/), [Less](http://lesscss.org/), [Erb](http://ruby-doc.org/stdlib-1.9.3/libdoc/erb/rdoc/ERB.html) or any other language. One of Jekyll Asset Pipeline's key strengths is that it works with __any__ preprocessing library that has a ruby wrapper. Adding a preprocessor is straightforward, but requires a small amount of additional code. 101 | 102 | In the following example, we will add a preprocessor that converts CoffeeScript into JavaScript. 103 | 104 | ### CoffeeScript 105 | 106 | 1. In the `jekyll_asset_pipeline.rb` file that we created in the [Getting Started](#getting-started) section, add the following code to the end of the file (i.e. after the `require` statement). 107 | 108 | ``` ruby 109 | module JekyllAssetPipeline 110 | class CoffeeScriptConverter < JekyllAssetPipeline::Converter 111 | require 'coffee-script' 112 | 113 | def self.filetype 114 | '.coffee' 115 | end 116 | 117 | def convert 118 | return CoffeeScript.compile(@content) 119 | end 120 | end 121 | end 122 | ``` 123 | 124 | The above code adds a CoffeeScript converter. You can name a converter anything as long as it inherits from `JekyllAssetPipeline::Converter`. The `self.filetype` method defines the type of asset a converter will process (e.g. `.coffee` for CoffeeScript) based on the extension of the raw asset file. A `@content` instance variable that contains the raw content of our asset is made available within the converter. The converter should process this content and return the processed content (as a string) via a `convert` method. 125 | 126 | 2. If you haven't already, you should now install any dependancies that are required by your converter. In our case, we need to install the `coffee-script` gem. 127 | 128 | ``` bash 129 | $ gem install coffee-script 130 | ``` 131 | 132 | If you are using [Bundler](http://gembundler.com/) to manage your project's gems, you can just add `coffee-script` to your Gemfile and run `bundle install`. 133 | 134 | 3. Append a `.coffee` extension to the filename of any asset that should be converted with the `CoffeeScriptConverter`. For example, `foo.js` would become `foo.js.coffee`. 135 | 136 | 4. Run the `jekyll build` command to compile your site. 137 | 138 | That is it! Your asset pipeline has converted any CoffeeScript assets into JavaScript before adding them to a bundle. 139 | 140 | ### SASS / SCSS 141 | 142 | You probably get the gist of how converters work, but here's an example of a SASS converter for quick reference. 143 | 144 | ``` ruby 145 | module JekyllAssetPipeline 146 | class SassConverter < JekyllAssetPipeline::Converter 147 | require 'sass' 148 | 149 | def self.filetype 150 | '.scss' 151 | end 152 | 153 | def convert 154 | return Sass::Engine.new(@content, syntax: :scss).render 155 | end 156 | end 157 | end 158 | ``` 159 | 160 | Don't forget to install the `sass` gem or add it to your Gemfile and run `bundle install` before you run the `jekyll build` command since the above SASS converter requires the `sass` library as a dependency. 161 | 162 | If you're using `@import` statements in your SASS files, you'll probably need to specify a base load path to the SASS engine in your `convert` method. 163 | You can use the `@dirname` instance variable for this, which contains the path to the current asset's directory: 164 | 165 | ``` ruby 166 | ... 167 | def convert 168 | return Sass::Engine.new(@content, syntax: :scss, load_paths: [@dirname]).render 169 | end 170 | ... 171 | ``` 172 | 173 | ### LESS 174 | 175 | ``` ruby 176 | module JekyllAssetPipeline 177 | class LessConverter < JekyllAssetPipeline::Converter 178 | require 'less' 179 | 180 | def self.filetype 181 | '.less' 182 | end 183 | 184 | def convert 185 | return Less::Parser.new.parse(@content).to_css 186 | end 187 | end 188 | end 189 | ``` 190 | 191 | Don't forget to install the `less` gem or add it to your Gemfile and run `bundle install` before you run the `jekyll build` command since the above LESS converter requires the `less` library as a dependency. 192 | 193 | As with the SASS convertor, you'll probably need to specify a base load path and pass that to the LESS Parser: 194 | 195 | ``` ruby 196 | ... 197 | def convert 198 | return Less::Parser.new(paths: [@dirname]).parse(@content).to_css 199 | end 200 | ... 201 | ``` 202 | 203 | ### Successive Preprocessing 204 | 205 | If you would like to run an asset through multiple preprocessors successively, you can do so by naming your assets with nested file extensions. Nest the extensions in the order (right to left) that the asset should be processed. For example, `.css.scss.erb` would first be processed by an `erb` preprocessor then by a `scss` preprocessor before being rendered. This convention is very similar to the convention used by the [Ruby on Rails asset pipeline](http://guides.rubyonrails.org/asset_pipeline.html#preprocessing). 206 | 207 | Don't forget to define preprocessors for the extensions you use in your filenames, otherwise Jekyll Asset Pipeline will not process your asset. 208 | 209 | ## Asset Compression 210 | 211 | Asset compression allows us to decrease the size of our assets and increase the speed of our site. One of Jekyll Asset Pipeline's key strengths is that it works with __any__ compression library that has a ruby wrapper. Adding asset compression is straightforward, but requires a small amount of additional code. 212 | 213 | In the following example, we will add a compressor that uses Yahoo's YUI Compressor to compress our CSS and JavaScript assets. 214 | 215 | ### Yahoo's YUI Compressor 216 | 217 | 1. In the `jekyll_asset_pipeline.rb` file that we created in the [Getting Started](#getting-started) section, add the following code to the end of the file (i.e. after the `require` statement). 218 | 219 | ``` ruby 220 | module JekyllAssetPipeline 221 | class CssCompressor < JekyllAssetPipeline::Compressor 222 | require 'yui/compressor' 223 | 224 | def self.filetype 225 | '.css' 226 | end 227 | 228 | def compress 229 | return YUI::CssCompressor.new.compress(@content) 230 | end 231 | end 232 | 233 | class JavaScriptCompressor < JekyllAssetPipeline::Compressor 234 | require 'yui/compressor' 235 | 236 | def self.filetype 237 | '.js' 238 | end 239 | 240 | def compress 241 | return YUI::JavaScriptCompressor.new(munge: true).compress(@content) 242 | end 243 | end 244 | end 245 | ``` 246 | 247 | The above code adds a CSS and a JavaScript compressor. You can name a compressor anything as long as it inherits from `JekyllAssetPipeline::Compressor`. The `self.filetype` method defines the type of asset a compressor will process (either `'.js'` or `'.css'`). The `compress` method is where the magic happens. A `@content` instance variable that contains the raw content of our bundle is made available within the compressor. The compressor should process this content and return the processed content (as a string) via a `compress` method. 248 | 249 | 2. If you haven't already, you should now install any dependencies that are required by your compressor. In our case, we need to install the `yui-compressor` gem. 250 | 251 | ``` bash 252 | $ gem install yui-compressor 253 | ``` 254 | 255 | If you are using [Bundler](http://gembundler.com/) to manage your project's gems, you can just add `yui-compressor` to your Gemfile and run `bundle install`. 256 | 257 | 3. Run the `jekyll build` command to compile your site. 258 | 259 | That is it! Your asset pipeline has compressed your CSS and JavaScript assets. You can verify that this is the case by looking at the contents of the bundles generated in the `_site/assets` folder of your project. 260 | 261 | ### Google's Closure Compiler 262 | 263 | You probably get the gist of how compressors work, but here's an example of a Google Closure Compiler compressor for quick reference. 264 | 265 | ``` ruby 266 | class JavaScriptCompressor < JekyllAssetPipeline::Compressor 267 | require 'closure-compiler' 268 | 269 | def self.filetype 270 | '.js' 271 | end 272 | 273 | def compress 274 | return Closure::Compiler.new.compile(@content) 275 | end 276 | end 277 | ``` 278 | 279 | Don't forget to install the `closure-compiler` gem before you run the `jekyll build` command since the above compressor requires the `closure-compiler` library as a dependency. 280 | 281 | ## Templates 282 | 283 | When Jekyll Asset Pipeline creates a bundle, it returns an HTML tag that points to the bundle. This tag is either a `link` tag for CSS or a `script` tag for JavaScript. Under most circumstances the default tags will suffice, but you may want to customize this output for special cases (e.g. if you want to add a CSS media attribute). 284 | 285 | In the following example, we will override the default CSS link tag by adding a custom template that produces a link tag with a `media` attribute. 286 | 287 | 1. In the `jekyll_asset_pipeline.rb` file that we created in the [Getting Started](#getting-started) section, add the following code. 288 | 289 | ``` ruby 290 | module JekyllAssetPipeline 291 | class CssTagTemplate < JekyllAssetPipeline::Template 292 | def self.filetype 293 | '.css' 294 | end 295 | 296 | def html 297 | "\n" 299 | end 300 | end 301 | end 302 | ``` 303 | 304 | If you already added a compressor and/or a converter, you can include your template class alongside your compressor and/or converter within the same Jekyll Asset Pipeline module. 305 | 306 | The “self.filetype” method defines the type of bundle a template will target (either `.js` or `.css`). The “html” method is where the magic happens. `output_path` is a helper method and `@filename` is an instance variable which are available within the class and contain the path and filename of the generated bundle, respectively. The template should return a string that contains an HTML tag pointing to the generated bundle via an `html` method. 307 | 308 | 2. Run the `jekyll` command to compile your site. 309 | 310 | That is it! Your asset pipeline used your template to generate an HTML `link` tag that includes a media attribute with the value `screen`. You can verify that this is the case by viewing the generated source within your project's `_site` folder. 311 | 312 | ## Configuration 313 | 314 | Jekyll Asset Pipeline provides the following configuration options that can be controlled by adding them to your project's `_config.yml` file. If you don't have a `_config.yml` file, consider reading the [configuration section](https://github.com/mojombo/jekyll/wiki/Configuration) of the Jekyll documentation. 315 | 316 | ``` yaml 317 | asset_pipeline: 318 | bundle: true 319 | compress: true 320 | output_path: assets 321 | display_path: nil 322 | gzip: false 323 | ``` 324 | 325 | Setting | Default | Description 326 | ---------------|----------|----------------------------------------------------- 327 | `bundle` | `true` | controls whether Jekyll Asset Pipeline bundles the assets defined in each manifest. If set to `false`, each asset will be saved individually and individual html tags pointing to each unbundled asset will be produced when you compile your site. It is useful to set this to `false` while you are debugging your site. 328 | `compress` | `true` | tells Jekyll Asset Pipeline whether or not to compress the bundled assets. It is useful to set this setting to `false` while you are debugging your site. 329 | `output_path` | `assets` | defines where generated bundles should be saved within the `_site` folder of your project. 330 | `display_path` | `nil` | overrides the path to assets in generated html tags. This is useful if you are hosting your site at a path other than the root of your domain (e.g. `http://example.com/blog/`). 331 | `gzip` | `false` | controls whether Jekyll Asset Pipeline saves gzipped versions of your assets alongside un-gzipped versions. 332 | 333 | 334 | ## Octopress 335 | 336 | [Octopress](http://octopress.org/) is a popular framework for Jekyll that can help you get a blog up and running quickly. Jekyll Asset Pipeline can be added to an Octopress site using the [Getting Started](#getting-started) steps above with the following modifications: 337 | 338 | 1. Octopress uses Bundler to manage your site's dependencies. You should add `gem jekyll_asset_pipeline` to your Gemfile and then run `bundle install` to install. 339 | 340 | 2. Instead of adding a `_plugins` folder, you should put `jekyll_asset_pipeline.rb` in the `plugins` folder included by default in the root of your Octopress site. 341 | 342 | 3. You should still store your assets in an Jekyll ignored folder (i.e. a folder that begins with an underscore `_`), but note that this folder should be located within the `source` folder of your Octopress site (e.g. `source/_assets`). 343 | 344 | 4. No change to this step. 345 | 346 | 5. Instead of running the `jekyll` command to compile your site, you should use Octopress' rake commands (e.g. `rake generate`) as outlined [here](http://octopress.org/docs/blogging/). 347 | 348 | If you have any difficulties using Jekyll Asset Pipeline with Octopress, please [open an issue](http://github.com/matthodan/jekyll-asset-pipeline/issues). 349 | 350 | ## Contribute 351 | 352 | You can contribute to the Jekyll Asset Pipeline by submitting a pull request [via GitHub](https://github.com/matthodan/jekyll-asset-pipeline). There are a few areas that need improvement: 353 | 354 | - __Tests, tests, tests.__ **This project is now fully tested.** 355 | - __Successive preprocessing.__ Currently you can only preprocess a file once. It would be better if you could run an asset through multiple preprocessors before it gets compressed and bundled. **As of v0.1.0, Jekyll Asset Pipeline now supports successive preprocessing.** 356 | - __Handle remote assets.__ Right now, Jekyll Asset Pipeline does not provide any way to include remote assets in bundles unless you save them locally before generating your site. Moshen's [Jekyll Asset Bundler](https://github.com/moshen/jekyll-asset_bundler) allows you to include remote assets, which is pretty interesting. That said, it is generally better to keep remote assets separate so that they load asynchronously. 357 | 358 | If you have any ideas or you would like to see anything else improved please use the [issues section](https://github.com/matthodan/jekyll-asset-pipeline/issues). 359 | 360 | ## Changelog 361 | 362 | See [the changelog](CHANGELOG.md). 363 | 364 | ## Community 365 | 366 | - Here is [GitHub's list of projects that use the gem](https://github.com/matthodan/jekyll-asset-pipeline/network/dependents). 367 | - Here is a currated list of [sites that use Jekyll Asset Pipeline](http://github.com/matthodan/jekyll-asset-pipeline/wiki/Sites-that-use-Jekyll-Asset-Pipeline). Feel free to add your site to the list if you want. 368 | 369 | ## Credits 370 | 371 | * [Moshen](https://github.com/moshen/) for creating the [Jekyll Asset Bundler](https://github.com/moshen/jekyll-asset_bundler). 372 | * [Mojombo](https://github.com/mojombo) for creating [Jekyll](https://github.com/mojombo/jekyll) in the first place. 373 | 374 | ## License 375 | 376 | Jekyll Asset Pipeline is released under the [MIT License](http://opensource.org/licenses/MIT). 377 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rake' 4 | require 'rake/testtask' 5 | 6 | task default: [:test] 7 | 8 | Rake::TestTask.new(:test) do |test| 9 | test.libs << 'spec' 10 | test.pattern = 'spec/**/*_spec.rb' 11 | test.verbose = false 12 | test.warning = false 13 | end 14 | -------------------------------------------------------------------------------- /jekyll_asset_pipeline.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path('lib/jekyll_asset_pipeline/version', __dir__) 4 | 5 | Gem::Specification.new do |s| 6 | # Metadata 7 | s.name = 'jekyll_asset_pipeline' 8 | s.version = JekyllAssetPipeline::VERSION 9 | s.date = Time.now 10 | 11 | s.summary = <<-SUMMARY 12 | A powerful asset pipeline for Jekyll that bundles, converts, and minifies 13 | CSS and JavaScript assets. 14 | SUMMARY 15 | 16 | s.description = <<-DESCRIPTION 17 | Jekyll Asset Pipeline adds asset preprocessing (CoffeeScript, Sass, 18 | Less, ERB, etc.) and asset compression / minification / gzip (Yahoo YUI 19 | Compressor, Google Closure Compiler, etc.) to Jekyll. 20 | DESCRIPTION 21 | 22 | s.authors = ['Matt Hodan', 'Janos Rusiczki'] 23 | s.email = ['matthew.c.hodan@gmail.com', 'janos.rusiczki@gmail.com'] 24 | s.homepage = 'https://github.com/matthodan/jekyll-asset-pipeline' 25 | s.license = 'MIT' 26 | 27 | s.required_ruby_version = '>= 2.3.0' 28 | s.rubygems_version = '2.2.2' 29 | 30 | # Runtime dependencies 31 | s.add_runtime_dependency 'jekyll', '>= 3.5', '< 5.0' 32 | s.add_runtime_dependency 'liquid', '~> 4.0' 33 | 34 | # Development dependencies 35 | s.add_development_dependency 'minitest', '~> 5.2' 36 | s.add_development_dependency 'rake', '~> 12.0' 37 | 38 | # Files 39 | s.files = Dir['lib/**/*.rb', 'LICENSE', 'README.md', 'CHANGELOG.md'].to_a 40 | end 41 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Stdlib dependencies 4 | require 'digest/md5' 5 | require 'fileutils' 6 | require 'time' 7 | require 'yaml' 8 | require 'zlib' 9 | 10 | # Third-party dependencies 11 | require 'jekyll' 12 | require 'liquid' 13 | 14 | # Jekyll extensions 15 | require 'jekyll_asset_pipeline/extensions/jekyll/site_extensions' 16 | require 'jekyll_asset_pipeline/extensions/jekyll/site' 17 | 18 | # Liquid extensions 19 | require 'jekyll_asset_pipeline/extensions/liquid/liquid_block_extensions' 20 | require 'jekyll_asset_pipeline/extensions/liquid/asset_tag' 21 | require 'jekyll_asset_pipeline/extensions/liquid/asset_tags/css_asset_tag' 22 | # rubocop:disable Metrics/LineLength 23 | require 'jekyll_asset_pipeline/extensions/liquid/asset_tags/javascript_asset_tag' 24 | # rubocop:enable Metrics/LineLength 25 | 26 | # Ruby extensions 27 | require 'jekyll_asset_pipeline/extensions/ruby/subclass_tracking' 28 | 29 | # Jekyll Asset Pipeline 30 | require 'jekyll_asset_pipeline/version' 31 | require 'jekyll_asset_pipeline/asset' 32 | require 'jekyll_asset_pipeline/converter' 33 | require 'jekyll_asset_pipeline/compressor' 34 | require 'jekyll_asset_pipeline/templates/template_helper' 35 | require 'jekyll_asset_pipeline/template' 36 | require 'jekyll_asset_pipeline/templates/javascript_tag_template' 37 | require 'jekyll_asset_pipeline/templates/css_tag_template' 38 | require 'jekyll_asset_pipeline/pipeline' 39 | 40 | module JekyllAssetPipeline 41 | # Default configuration settings for Jekyll Asset Pipeline 42 | # Strings used for keys to play nice when merging with _config.yml 43 | # 44 | # 'output_path' Destination for bundle file (within the '_site' directory) 45 | # 'display_path' Optional. Override path to assets for output HTML refs 46 | # 'staging_path' Destination for staged assets (within project root directory) 47 | # 'bundle' true = Bundle assets, false = Leave assets unbundled 48 | # 'compress' true = Minify assets, false = Leave assets unminified 49 | # 'gzip' true = Create gzip versions, 50 | # false = Do not create gzip versions 51 | DEFAULTS = { 52 | 'output_path' => 'assets', 53 | 'display_path' => nil, 54 | 'staging_path' => '.asset_pipeline', 55 | 'bundle' => true, 56 | 'compress' => true, 57 | 'gzip' => false 58 | }.freeze 59 | end 60 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/asset.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllAssetPipeline 4 | # Holds an asset (file) 5 | class Asset 6 | def initialize(content, filename, dirname = '.') 7 | @content = content 8 | @filename = filename 9 | @dirname = dirname 10 | end 11 | 12 | attr_accessor :content, :filename, :dirname, :output_path 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/compressor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllAssetPipeline 4 | # Base class for asset compressors 5 | # See https://github.com/matthodan/jekyll-asset-pipeline#asset-compression 6 | class Compressor 7 | extend JekyllAssetPipeline::SubclassTracking 8 | 9 | def initialize(content) 10 | @content = content 11 | @compressed = compress 12 | end 13 | 14 | # Returns compressed content 15 | attr_reader :compressed 16 | 17 | # Filetype to process (e.g. '.js') 18 | def self.filetype 19 | '' 20 | end 21 | 22 | # Logic to compress assets 23 | # 24 | # Available instance variables: 25 | # @content Content to be compressed 26 | # 27 | # Returns compressed string 28 | def compress 29 | @content 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/converter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllAssetPipeline 4 | # Base class for asset converters 5 | # See https://github.com/matthodan/jekyll-asset-pipeline#asset-preprocessing 6 | class Converter 7 | extend JekyllAssetPipeline::SubclassTracking 8 | 9 | def initialize(asset) 10 | @content = asset.content 11 | @type = File.extname(asset.filename).downcase 12 | @dirname = asset.dirname 13 | @converted = convert 14 | end 15 | 16 | attr_reader :converted 17 | 18 | # Filetype to process (e.g. '.coffee') 19 | def self.filetype 20 | '' 21 | end 22 | 23 | # Finds a converter class based on a filename 24 | def self.klass(filename) 25 | ::JekyllAssetPipeline::Converter.subclasses.select do |c| 26 | c.filetype == File.extname(filename).downcase 27 | end.last 28 | end 29 | 30 | # Logic to convert assets 31 | # 32 | # Available instance variables: 33 | # @file File to be converted 34 | # @content Contents of @file as a string 35 | # @type Filetype of file (e.g. '.coffee') 36 | # 37 | # Returns converted string 38 | def convert 39 | @content 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/extensions/jekyll/site.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | # Contains overrides for the needed Jekyll:Site methods 5 | # The actual code is in JekyllAssetPipeline::JekyllSiteExtensions 6 | class Site 7 | include JekyllAssetPipeline::JekyllSiteExtensions 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/extensions/jekyll/site_extensions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllAssetPipeline 4 | # Contains overrides for the needed Jekyll:Site methods 5 | # Included in Jekyll::Site 6 | module JekyllSiteExtensions 7 | def self.included(base) 8 | base.class_eval do 9 | # Store the original Jekyll::Site#cleanup method 10 | old_cleanup_method = instance_method(:cleanup) 11 | 12 | # Override Jekyll::Site#cleanup 13 | define_method(:cleanup) do 14 | # Run the Jekyll::Site#cleanup method 15 | original_return_val = old_cleanup_method.bind(self).call 16 | 17 | # Clear Jekyll Asset Pipeline cache 18 | Pipeline.clear_cache 19 | 20 | original_return_val 21 | end 22 | 23 | # Store the original Jekyll::Site#write method 24 | old_write_method = instance_method(:write) 25 | 26 | # Override Jekyll::Site#write 27 | define_method(:write) do 28 | # Run the Jekyll::Site#write method 29 | original_return_value = old_write_method.bind(self).call 30 | 31 | # Clear Jekyll Asset Pipeline staged assets 32 | config = self.config['asset_pipeline'] || {} 33 | Pipeline.remove_staged_assets(source, config) 34 | 35 | original_return_value 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/extensions/liquid/asset_tag.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllAssetPipeline 4 | # This is a Liquid tag block extension 5 | # See documentation here: 6 | # https://github.com/Shopify/liquid/wiki/liquid-for-programmers#create-your-own-tag-blocks 7 | class AssetTag < ::Liquid::Block 8 | extend JekyllAssetPipeline::LiquidBlockExtensions::ClassMethods 9 | include JekyllAssetPipeline::LiquidBlockExtensions 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/extensions/liquid/asset_tags/css_asset_tag.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This comment is needed, otherwise Rubocop complains because of the 4 | # register_tag below and a verbose comment is better than a :nodoc: :) 5 | module JekyllAssetPipeline 6 | # css_asset_tag Liquid block 7 | # See JekyllAssetPipeline::AssetTag and 8 | # JekyllAssetPipeline::LiquidBlockExtensions 9 | class CssAssetTag < JekyllAssetPipeline::AssetTag 10 | def self.tag_name 11 | 'css_asset_tag' 12 | end 13 | 14 | def self.output_type 15 | '.css' 16 | end 17 | end 18 | 19 | # Register CssAssetTag tag with Liquid 20 | ::Liquid::Template 21 | .register_tag(JekyllAssetPipeline::CssAssetTag.tag_name, 22 | JekyllAssetPipeline::CssAssetTag) 23 | end 24 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/extensions/liquid/asset_tags/javascript_asset_tag.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This comment is needed, otherwise Rubocop complains because of the 4 | # register_tag below and a verbose comment is better than a :nodoc: :) 5 | module JekyllAssetPipeline 6 | # javascript_asset_tag Liquid block 7 | # See JekyllAssetPipeline::AssetTag and 8 | # JekyllAssetPipeline::LiquidBlockExtensions 9 | class JavaScriptAssetTag < JekyllAssetPipeline::AssetTag 10 | def self.tag_name 11 | 'javascript_asset_tag' 12 | end 13 | 14 | def self.output_type 15 | '.js' 16 | end 17 | end 18 | 19 | # Register JavaScriptAssetTag tag with Liquid 20 | ::Liquid::Template 21 | .register_tag(JekyllAssetPipeline::JavaScriptAssetTag.tag_name, 22 | JekyllAssetPipeline::JavaScriptAssetTag) 23 | end 24 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/extensions/liquid/liquid_block_extensions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllAssetPipeline 4 | # Helper module used by JekyllAssetPipeline::AssetTag as well as 5 | # classed derived from it (Liquid tag block extensions) 6 | # See documentation here: 7 | # https://github.com/Shopify/liquid/wiki/liquid-for-programmers#create-your-own-tag-blocks 8 | module LiquidBlockExtensions 9 | # Unsurprisingly, class methods 10 | module ClassMethods 11 | def output_type 12 | '' 13 | end 14 | 15 | def tag_name 16 | '' 17 | end 18 | end 19 | 20 | def render(context) 21 | site = context.registers[:site] 22 | config = site.config.fetch('asset_pipeline', {}) 23 | 24 | # Run Jekyll Asset Pipeline 25 | pipeline, cached = run_pipeline(site, config) 26 | 27 | return nil unless pipeline.is_a?(Pipeline) 28 | 29 | # Prevent Jekyll from cleaning up saved assets if new pipeline 30 | preserve_assets(site, config, pipeline) unless cached 31 | 32 | # Return HTML tag pointing to asset 33 | pipeline.html 34 | end 35 | 36 | private 37 | 38 | def run_pipeline(site, config) 39 | Pipeline.run(nodelist.first, @markup.strip, site.source, site.dest, 40 | self.class.tag_name, self.class.output_type, config) 41 | end 42 | 43 | def preserve_assets(site, config, pipeline) 44 | pipeline.assets.each do |asset| 45 | config = JekyllAssetPipeline::DEFAULTS.merge(config) 46 | staging_path = File.expand_path(File.join(site.source, 47 | config['staging_path'])) 48 | site.static_files << Jekyll::StaticFile.new(site, staging_path, 49 | asset.output_path, 50 | asset.filename) 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/extensions/ruby/subclass_tracking.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllAssetPipeline 4 | # Allows classes that extend this to return an array of their subclasses 5 | module SubclassTracking 6 | # Record subclasses of this class (this method is automatically called by 7 | # ruby) 8 | def inherited(base) 9 | subclasses << base 10 | end 11 | 12 | # Return an array of classes that are subclasses of this object 13 | def subclasses 14 | @subclasses ||= [] 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/pipeline.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllAssetPipeline 4 | # The pipeline itself, the run method is where it all happens 5 | # rubocop:disable Metrics/ClassLength 6 | class Pipeline 7 | # rubocop:enable Metrics/ClassLength 8 | class << self 9 | # Generate hash based on manifest 10 | def hash(source, manifest, options = {}) 11 | options = DEFAULTS.merge(options) 12 | begin 13 | Digest::MD5.hexdigest(YAML.safe_load(manifest).map! do |path| 14 | "#{path}#{File.mtime(File.join(source, path)).to_i}" 15 | end.join.concat(options.to_s)) 16 | rescue StandardError => e 17 | puts "Failed to generate hash from provided manifest: #{e.message}" 18 | raise e 19 | end 20 | end 21 | 22 | # Run the pipeline 23 | # This is called from JekyllAssetPipeline::LiquidBlockExtensions.render 24 | # or, to be more precise, from JekyllAssetPipeline::CssAssetTag.render and 25 | # JekyllAssetPipeline::JavaScriptAssetTag.render 26 | # rubocop:disable Metrics/ParameterLists 27 | def run(manifest, prefix, source, destination, tag, type, config) 28 | # rubocop:enable Metrics/ParameterLists 29 | # Get hash for pipeline 30 | hash = hash(source, manifest, config) 31 | 32 | # Check if pipeline has been cached 33 | return cache[hash], true if cache.key?(hash) 34 | 35 | begin 36 | puts "Processing '#{tag}' manifest '#{prefix}'" 37 | pipeline = new(manifest, prefix, source, destination, type, config) 38 | process_pipeline(hash, pipeline) 39 | rescue StandardError => e 40 | # Add exception to cache 41 | cache[hash] = e 42 | 43 | # Re-raise the exception 44 | raise e 45 | end 46 | end 47 | 48 | # Cache processed pipelines 49 | def cache 50 | @cache ||= {} 51 | end 52 | 53 | # Empty cache 54 | def clear_cache 55 | @cache = {} 56 | end 57 | 58 | # Remove staged assets 59 | def remove_staged_assets(source, config) 60 | config = DEFAULTS.merge(config) 61 | staging_path = File.join(source, config['staging_path']) 62 | FileUtils.rm_rf(staging_path) 63 | end 64 | 65 | # Add prefix to output 66 | def puts(message) 67 | $stdout.puts("Asset Pipeline: #{message}") 68 | end 69 | 70 | private 71 | 72 | def process_pipeline(hash, pipeline) 73 | pipeline.assets.each do |asset| 74 | puts "Saved '#{asset.filename}' to " \ 75 | "'#{pipeline.destination}/#{asset.output_path}'" 76 | end 77 | 78 | # Add processed pipeline to cache 79 | cache[hash] = pipeline 80 | 81 | # Return newly processed pipeline and cached status 82 | [pipeline, false] 83 | end 84 | end 85 | 86 | # Initialize new pipeline 87 | # rubocop:disable Metrics/ParameterLists 88 | def initialize(manifest, prefix, source, destination, type, options = {}) 89 | # rubocop:enable Metrics/ParameterLists 90 | @manifest = manifest 91 | @prefix = prefix 92 | @source = source 93 | @destination = destination 94 | @type = type 95 | @options = ::JekyllAssetPipeline::DEFAULTS.merge(options) 96 | 97 | process 98 | end 99 | 100 | attr_reader :assets, :html, :destination 101 | 102 | private 103 | 104 | # Process the pipeline 105 | def process 106 | collect 107 | convert 108 | bundle if @options['bundle'] 109 | compress if @options['compress'] 110 | gzip if @options['gzip'] 111 | save 112 | markup 113 | end 114 | 115 | # Collect assets based on manifest 116 | def collect 117 | @assets = YAML.safe_load(@manifest).map! do |path| 118 | full_path = File.join(@source, path) 119 | File.open(File.join(@source, path)) do |file| 120 | ::JekyllAssetPipeline::Asset.new(file.read, File.basename(path), 121 | File.dirname(full_path)) 122 | end 123 | end 124 | rescue StandardError => e 125 | puts 'Asset Pipeline: Failed to load assets from provided ' \ 126 | "manifest: #{e.message}" 127 | raise e 128 | end 129 | 130 | # Convert assets based on the file extension if converter is defined 131 | def convert 132 | @assets.each do |asset| 133 | # Convert asset multiple times if more than one converter is found 134 | finished = false 135 | while finished == false 136 | # Find a converter to use 137 | klass = ::JekyllAssetPipeline::Converter.klass(asset.filename) 138 | 139 | # Convert asset if converter is found 140 | if klass.nil? 141 | finished = true 142 | else 143 | convert_asset(klass, asset) 144 | end 145 | end 146 | end 147 | end 148 | 149 | # Convert an asset with a given converter class 150 | def convert_asset(klass, asset) 151 | # Convert asset content 152 | converter = klass.new(asset) 153 | 154 | # Replace asset content and filename 155 | asset.content = converter.converted 156 | asset.filename = File.basename(asset.filename, '.*') 157 | 158 | # Add back the output extension if no extension left 159 | if File.extname(asset.filename) == '' 160 | asset.filename = "#{asset.filename}#{@type}" 161 | end 162 | rescue StandardError => e 163 | puts "Asset Pipeline: Failed to convert '#{asset.filename}' " \ 164 | "with '#{klass}': #{e.message}" 165 | raise e 166 | end 167 | 168 | # Bundle multiple assets into a single asset 169 | def bundle 170 | content = @assets.map(&:content).join("\n") 171 | 172 | hash = ::JekyllAssetPipeline::Pipeline.hash(@source, @manifest, @options) 173 | @assets = [ 174 | ::JekyllAssetPipeline::Asset.new(content, "#{@prefix}-#{hash}#{@type}") 175 | ] 176 | end 177 | 178 | # Compress assets if compressor is defined 179 | def compress 180 | @assets.each do |asset| 181 | # Find a compressor to use 182 | klass = ::JekyllAssetPipeline::Compressor.subclasses.select do |c| 183 | c.filetype == @type 184 | end.last 185 | 186 | break unless klass 187 | 188 | begin 189 | asset.content = klass.new(asset.content).compressed 190 | rescue StandardError => e 191 | puts "Asset Pipeline: Failed to compress '#{asset.filename}' " \ 192 | "with '#{klass}': #{e.message}" 193 | raise e 194 | end 195 | end 196 | end 197 | 198 | # Create Gzip versions of assets 199 | def gzip 200 | @assets.map! do |asset| 201 | gzip_content = Zlib::Deflate.deflate(asset.content) 202 | [ 203 | asset, 204 | ::JekyllAssetPipeline::Asset 205 | .new(gzip_content, "#{asset.filename}.gz", asset.dirname) 206 | ] 207 | end.flatten! 208 | end 209 | 210 | # Save assets to file 211 | def save 212 | output_path = @options['output_path'] 213 | staging_path = @options['staging_path'] 214 | 215 | @assets.each do |asset| 216 | directory = File.join(@source, staging_path, output_path) 217 | write_asset_file(directory, asset) 218 | 219 | # Store output path of saved file 220 | asset.output_path = output_path 221 | end 222 | end 223 | 224 | # Write asset file to disk 225 | def write_asset_file(directory, asset) 226 | FileUtils.mkpath(directory) unless File.directory?(directory) 227 | begin 228 | # Save file to disk 229 | File.open(File.join(directory, asset.filename), 'w') do |file| 230 | file.write(asset.content) 231 | end 232 | rescue StandardError => e 233 | puts "Asset Pipeline: Failed to save '#{asset.filename}' to " \ 234 | "disk: #{e.message}" 235 | raise e 236 | end 237 | end 238 | 239 | # Generate html markup pointing to assets 240 | def markup 241 | # Use display_path if defined, otherwise use output_path in url 242 | display_path = @options['display_path'] || @options['output_path'] 243 | 244 | @html = @assets.map do |asset| 245 | klass = ::JekyllAssetPipeline::Template.klass(asset.filename) 246 | html = klass.new(display_path, asset.filename).html unless klass.nil? 247 | 248 | html 249 | end.join 250 | end 251 | end 252 | end 253 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/template.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllAssetPipeline 4 | # Base class for the tag templates 5 | # See https://github.com/matthodan/jekyll-asset-pipeline#templates 6 | class Template 7 | include JekyllAssetPipeline::TemplateHelper 8 | extend JekyllAssetPipeline::SubclassTracking 9 | 10 | def initialize(path, filename) 11 | @path = path 12 | @filename = filename 13 | end 14 | 15 | # Filetype to process (e.g. '.js') 16 | def self.filetype 17 | '' 18 | end 19 | 20 | # Priority of template (to override default templates) 21 | def self.priority 22 | 0 23 | end 24 | 25 | # Finds a template class based on a filename 26 | def self.klass(filename) 27 | klasses = JekyllAssetPipeline::Template.subclasses.select do |t| 28 | t.filetype == File.extname(filename).downcase 29 | end 30 | klasses.sort! { |x, y| x.priority <=> y.priority }.last 31 | end 32 | 33 | # HTML output to return 34 | # 35 | # Available instance variables: 36 | # @filename Name of bundle file 37 | # @path Path to bundle file 38 | # 39 | # Returns string 40 | def html 41 | "#{@path}/#{@filename}\n" 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/templates/css_tag_template.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllAssetPipeline 4 | # Default output for CSS assets 5 | class CssTagTemplate < JekyllAssetPipeline::Template 6 | def self.filetype 7 | '.css' 8 | end 9 | 10 | def self.priority 11 | -1 12 | end 13 | 14 | def html 15 | "" 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/templates/javascript_tag_template.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllAssetPipeline 4 | # Default output for JavaScript assets 5 | class JavaScriptTagTemplate < JekyllAssetPipeline::Template 6 | def self.filetype 7 | '.js' 8 | end 9 | 10 | def self.priority 11 | -1 12 | end 13 | 14 | def html 15 | "" 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/templates/template_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllAssetPipeline 4 | # Contains helper methods used by the tag template classes 5 | module TemplateHelper 6 | def output_path 7 | root_path? ? '' : "/#{@path}" 8 | end 9 | 10 | def root_path? 11 | stripped_path = @path.to_s.strip 12 | stripped_path.nil? || 13 | stripped_path.empty? || 14 | stripped_path == '/' 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/jekyll_asset_pipeline/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllAssetPipeline 4 | VERSION = '0.6.2' 5 | end 6 | -------------------------------------------------------------------------------- /spec/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'coveralls' 4 | Coveralls.wear! 5 | 6 | require 'rubygems' 7 | gem 'minitest' # Ensures we are using the gem and not the stdlib 8 | require 'minitest/autorun' 9 | require 'minitest/pride' 10 | require './spec/helpers/extensions/ruby/module' 11 | require 'jekyll_asset_pipeline' 12 | 13 | module MiniTest 14 | class Spec 15 | def source_path 16 | File.join(__dir__, 'resources', 'source') 17 | end 18 | 19 | def temp_path 20 | File.join(__dir__, 'resources', 'temp') 21 | end 22 | 23 | def clear_temp_path 24 | FileUtils.remove_dir(temp_path, force: true) 25 | end 26 | 27 | # Let us use 'context' in specs 28 | class << self 29 | alias context describe 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/helpers/extensions/ruby/module.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Allow us to test if a module extends another module/class 4 | # 5 | # For example: 6 | # class SomeClass 7 | # extend SomeModule 8 | # end 9 | # 10 | # SomeClass.extend?(SomeModule) 11 | # => true 12 | class Module 13 | def extend?(object) 14 | is_a?(object) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/jekyll_asset_pipeline/asset_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require './spec/helper' 4 | 5 | module JekyllAssetPipeline 6 | describe Asset do 7 | subject { Asset.new('foo', 'bar') } 8 | 9 | describe '#new(content, filename)' do 10 | specify do 11 | _(subject.instance_variable_get(:@content)).must_equal('foo') 12 | _(subject.instance_variable_get(:@filename)).must_equal('bar') 13 | end 14 | end 15 | 16 | describe '#content' do 17 | before { subject.instance_variable_set(:@content, 'foobar') } 18 | specify { _(subject.content).must_equal('foobar') } 19 | end 20 | 21 | describe '#content=' do 22 | before { subject.content = 'foobar' } 23 | specify do 24 | _(subject.instance_variable_get(:@content)).must_equal('foobar') 25 | end 26 | end 27 | 28 | describe '#filename' do 29 | before { subject.instance_variable_set(:@filename, 'foobar') } 30 | specify { _(subject.filename).must_equal('foobar') } 31 | end 32 | 33 | describe '#filename=' do 34 | before { subject.filename = 'foobar' } 35 | specify do 36 | _(subject.instance_variable_get(:@filename)).must_equal('foobar') 37 | end 38 | end 39 | 40 | describe '#output_path' do 41 | before { subject.instance_variable_set(:@output_path, 'foobar') } 42 | specify { _(subject.output_path).must_equal('foobar') } 43 | end 44 | 45 | describe '#output_path=' do 46 | before { subject.output_path = 'foobar' } 47 | specify do 48 | _(subject.instance_variable_get(:@output_path)).must_equal('foobar') 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/jekyll_asset_pipeline/compressor_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require './spec/helper' 4 | 5 | module JekyllAssetPipeline 6 | describe Compressor do 7 | specify { _(Compressor.extend?(SubclassTracking)).must_equal(true) } 8 | 9 | context 'with default compressor class' do 10 | describe 'class methods' do 11 | describe '::filetype' do 12 | specify { _(Compressor.filetype).must_be_instance_of(String) } 13 | end 14 | end 15 | 16 | describe 'instance methods' do 17 | subject { Compressor.new('uncompressed') } 18 | 19 | describe '#new(content)' do 20 | specify do 21 | _( 22 | subject.instance_variable_get(:@content) 23 | ).must_equal('uncompressed') 24 | _( 25 | subject.instance_variable_get(:@compressed) 26 | ).must_equal('uncompressed') 27 | end 28 | end 29 | 30 | describe '#compressed' do 31 | specify { _(subject.compressed).must_equal('uncompressed') } 32 | end 33 | 34 | describe '#compress' do 35 | specify { _(subject.compress).must_equal('uncompressed') } 36 | end 37 | end 38 | end 39 | 40 | context 'with default compressor class' do 41 | before do 42 | require './spec/resources/source/_plugins/jekyll_asset_pipeline' 43 | end 44 | 45 | describe 'class methods' do 46 | describe '::filetype' do 47 | specify { _(TestCompressor.filetype).must_equal('.foo') } 48 | end 49 | end 50 | 51 | describe 'instance methods' do 52 | subject { TestCompressor.new('uncompressed') } 53 | 54 | describe '#new(content)' do 55 | specify do 56 | _( 57 | subject.instance_variable_get(:@content) 58 | ).must_equal('uncompressed') 59 | _( 60 | subject.instance_variable_get(:@compressed) 61 | ).must_equal('compressed') 62 | end 63 | end 64 | 65 | describe '#compressed' do 66 | specify { _(subject.compressed).must_equal('compressed') } 67 | end 68 | 69 | describe '#compress' do 70 | specify { _(subject.compress).must_equal('compressed') } 71 | end 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/jekyll_asset_pipeline/converter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require './spec/helper' 4 | 5 | module JekyllAssetPipeline 6 | describe Converter do 7 | specify { _(Converter.extend?(SubclassTracking)).must_equal(true) } 8 | 9 | context 'with default converter class' do 10 | describe 'class methods' do 11 | describe '::filetype' do 12 | specify { _(Converter.filetype).must_be_instance_of(String) } 13 | end 14 | end 15 | 16 | describe 'instance methods' do 17 | let(:asset) { MiniTest::Mock.new } 18 | 19 | before do 20 | asset.expect(:content, 'foo') 21 | asset.expect(:filename, 'bar.baz') 22 | asset.expect(:dirname, '.') 23 | end 24 | 25 | subject { Converter.new(asset) } 26 | 27 | describe '#new(asset)' do 28 | specify do 29 | _(subject.instance_variable_get(:@content)).must_equal('foo') 30 | _(subject.instance_variable_get(:@type)).must_equal('.baz') 31 | _(subject.instance_variable_get(:@converted)).must_equal('foo') 32 | end 33 | end 34 | 35 | describe '#converted' do 36 | specify { _(subject.converted).must_equal('foo') } 37 | end 38 | 39 | describe '#convert' do 40 | specify { _(subject.convert).must_equal('foo') } 41 | end 42 | end 43 | end 44 | 45 | context 'with custom converter class' do 46 | before do 47 | require './spec/resources/source/_plugins/jekyll_asset_pipeline' 48 | end 49 | 50 | describe 'class methods' do 51 | describe '::filetype' do 52 | specify { _(TestConverter.filetype).must_equal('.foo') } 53 | end 54 | end 55 | 56 | describe 'instance methods' do 57 | let(:asset) { MiniTest::Mock.new } 58 | 59 | before do 60 | asset.expect(:content, 'unconverted') 61 | asset.expect(:filename, 'some_filename.foo') 62 | asset.expect(:dirname, '/some/path') 63 | end 64 | 65 | subject { TestConverter.new(asset) } 66 | 67 | describe '#new(asset)' do 68 | specify do 69 | _( 70 | subject.instance_variable_get(:@content) 71 | ).must_equal('unconverted') 72 | _( 73 | subject.instance_variable_get(:@type) 74 | ).must_equal('.foo') 75 | _( 76 | subject.instance_variable_get(:@converted) 77 | ).must_equal('converted') 78 | end 79 | end 80 | 81 | describe '#converted' do 82 | specify { _(subject.converted).must_equal('converted') } 83 | end 84 | 85 | describe '#convert' do 86 | specify { _(subject.convert).must_equal('converted') } 87 | end 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/jekyll_asset_pipeline/extensions/jekyll/site_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require './spec/helper' 4 | 5 | module JekyllAssetPipeline 6 | describe Jekyll::Site do 7 | specify do 8 | _(Jekyll::Site.include?(JekyllSiteExtensions)).must_equal(true) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/jekyll_asset_pipeline/extensions/jekyll_site_extensions_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require './spec/helper' 4 | 5 | module JekyllAssetPipeline 6 | describe JekyllSiteExtensions do 7 | before do 8 | # Add dummy data to cache for testing 9 | Pipeline.cache['foo'] = 'bar' 10 | end 11 | 12 | describe '#cleanup' do 13 | subject do 14 | # Create mock class 15 | class MockSite 16 | def cleanup 17 | 'old_return_value' 18 | end 19 | 20 | def write; end 21 | 22 | include JekyllSiteExtensions 23 | end 24 | 25 | obj = MockSite.new 26 | obj.cleanup 27 | end 28 | 29 | # rubocop:disable Metrics/LineLength 30 | it 'clears JekyllAssetPipeline::Cache (when Jekyll::Site#cleanup is called)' do 31 | # rubocop:enable Metrics/LineLength 32 | subject # Setup subject 33 | _(Pipeline.cache.key?('foo')).must_equal(false) 34 | end 35 | 36 | it 'returns the same value as the original Jekyll::Site#cleanup method' do 37 | _(subject).must_equal('old_return_value') 38 | end 39 | end 40 | 41 | describe '#write' do 42 | subject do 43 | # Create mock class 44 | class MockSite 45 | def cleanup; end 46 | 47 | def write 48 | 'old_write_return_value' 49 | end 50 | 51 | def config 52 | {} 53 | end 54 | 55 | def source 56 | '/tmp/' 57 | end 58 | 59 | include JekyllSiteExtensions 60 | end 61 | 62 | obj = MockSite.new 63 | obj.write 64 | end 65 | 66 | it 'returns the same value as the original Jekyll::Site#write method' do 67 | _(subject).must_equal('old_write_return_value') 68 | end 69 | 70 | it 'removes the staged assets via Pipeline.remove_staged_assets' do 71 | FileUtils.touch('/tmp/.asset_pipeline') 72 | subject 73 | _(File.exist?('/tmp/.asset_pipeline')).must_equal(false) 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /spec/jekyll_asset_pipeline/extensions/liquid/asset_tag_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require './spec/helper' 4 | 5 | module JekyllAssetPipeline 6 | describe AssetTag do 7 | specify do 8 | _(AssetTag.extend?(LiquidBlockExtensions::ClassMethods)).must_equal(true) 9 | _(AssetTag.include?(LiquidBlockExtensions)).must_equal(true) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/jekyll_asset_pipeline/extensions/liquid/asset_tags/css_asset_tag_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require './spec/helper' 4 | 5 | module JekyllAssetPipeline 6 | describe CssAssetTag do 7 | specify do 8 | _(CssAssetTag.tag_name).must_equal('css_asset_tag') 9 | _(CssAssetTag.output_type).must_equal('.css') 10 | _(CssAssetTag.superclass == JekyllAssetPipeline::AssetTag) 11 | .must_equal(true) 12 | end 13 | 14 | it 'registers tag with Liquid' do 15 | _( 16 | ::Liquid::Template.tags[JekyllAssetPipeline::CssAssetTag.tag_name] 17 | ).must_equal(JekyllAssetPipeline::CssAssetTag) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/jekyll_asset_pipeline/extensions/liquid/asset_tags/java_script_asset_tag_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require './spec/helper' 4 | 5 | module JekyllAssetPipeline 6 | describe JavaScriptAssetTag do 7 | specify do 8 | _(JavaScriptAssetTag.tag_name).must_equal('javascript_asset_tag') 9 | _(JavaScriptAssetTag.output_type).must_equal('.js') 10 | _(JavaScriptAssetTag.superclass == JekyllAssetPipeline::AssetTag) 11 | .must_equal(true) 12 | end 13 | 14 | it 'registers tag with Liquid' do 15 | _( 16 | ::Liquid::Template 17 | .tags[JekyllAssetPipeline::JavaScriptAssetTag.tag_name] 18 | ).must_equal(JekyllAssetPipeline::JavaScriptAssetTag) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/jekyll_asset_pipeline/extensions/liquid/liquid_block_extensions_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require './spec/helper' 4 | 5 | module JekyllAssetPipeline 6 | describe LiquidBlockExtensions do 7 | describe LiquidBlockExtensions::ClassMethods do 8 | before do 9 | class MockLiquidBlockClassMethods 10 | extend LiquidBlockExtensions::ClassMethods 11 | end 12 | end 13 | 14 | describe '#output_type' do 15 | subject { MockLiquidBlockClassMethods.output_type } 16 | it 'is an empty string' do 17 | _(subject).must_be_instance_of(String) 18 | _(subject).must_equal('') 19 | end 20 | end 21 | 22 | describe '#tag_name' do 23 | subject { MockLiquidBlockClassMethods.tag_name } 24 | it 'is an empty string' do 25 | _(subject).must_be_instance_of(String) 26 | _(subject).must_equal('') 27 | end 28 | end 29 | end 30 | 31 | describe '#render(context)' do 32 | before do 33 | class MockLiquidBlock 34 | include LiquidBlockExtensions 35 | 36 | def initialize 37 | @markup = 'foobar_prefix' 38 | end 39 | 40 | def nodelist 41 | ['foobar_manifest'] 42 | end 43 | 44 | def self.tag_name 45 | 'test_tag' 46 | end 47 | 48 | def self.output_type 49 | '.baz' 50 | end 51 | end 52 | end 53 | 54 | subject { MockLiquidBlock.new.render(context) } 55 | 56 | context 'previously processed pipeline found in cache' do 57 | let(:site) do 58 | site = MiniTest::Mock.new 59 | site.expect(:config, {}) 60 | site.expect(:source, source_path) 61 | site.expect(:dest, temp_path) 62 | site 63 | end 64 | 65 | let(:context) do 66 | context = MiniTest::Mock.new 67 | context.expect(:registers, site: site) 68 | context 69 | end 70 | 71 | let(:pipeline) do 72 | pipeline = MiniTest::Mock.new 73 | pipeline.expect(:is_a?, true, [Pipeline]) 74 | pipeline.expect(:html, 'foobar_html') 75 | pipeline 76 | end 77 | 78 | it 'returns html of previously processed pipeline' do 79 | Pipeline.stub(:run, [pipeline, true]) do 80 | _(subject).must_equal('foobar_html') 81 | end 82 | end 83 | end 84 | 85 | context 'pipeline has not been previously processed' do 86 | let(:site) do 87 | site = MiniTest::Mock.new 88 | site.expect(:config, {}) 89 | 2.times { site.expect(:source, source_path) } 90 | 2.times { site.expect(:dest, temp_path) } 91 | site.expect(:static_files, []) 92 | site 93 | end 94 | 95 | let(:context) do 96 | context = MiniTest::Mock.new 97 | context.expect(:registers, site: site) 98 | context 99 | end 100 | 101 | let(:asset) do 102 | asset = MiniTest::Mock.new 103 | asset.expect(:filename, 'foobar.baz') 104 | asset.expect(:output_path, 'foo/bar') 105 | asset 106 | end 107 | 108 | let(:assets) do 109 | [asset] 110 | end 111 | 112 | let(:pipeline) do 113 | pipeline = MiniTest::Mock.new 114 | pipeline.expect(:is_a?, true, [Pipeline]) 115 | pipeline.expect(:assets, assets) 116 | pipeline.expect(:html, 'foobaz_html') 117 | pipeline 118 | end 119 | 120 | it 'creates new pipeline and processes it' do 121 | $stdout.stub(:puts, nil) do 122 | Pipeline.stub(:run, [pipeline, false]) do 123 | Jekyll::StaticFile.stub(:new, 'foobar') do 124 | _(subject).must_equal('foobaz_html') 125 | end 126 | end 127 | end 128 | end 129 | end 130 | end 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /spec/jekyll_asset_pipeline/extensions/ruby/subclass_tracking_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require './spec/helper' 4 | 5 | module JekyllAssetPipeline 6 | describe SubclassTracking do 7 | describe '::subclasses' do 8 | before do 9 | class MockClass 10 | extend SubclassTracking 11 | end 12 | 13 | class MockSubclass < MockClass; end 14 | end 15 | 16 | it 'returns an array' do 17 | _(MockClass.subclasses).must_be_instance_of(Array) 18 | end 19 | 20 | it 'returned array contains all subclasses of MockSubclass' do 21 | _(MockClass.subclasses).must_include(MockSubclass) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/jekyll_asset_pipeline/integration_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require './spec/helper' 4 | 5 | describe 'Integration' do 6 | # Sensible defaults 7 | let(:manifest) { "- /_assets/foo.css\n- /_assets/bar.css" } 8 | let(:prefix) { 'global' } 9 | let(:config) { {} } 10 | let(:tag_name) { 'css_asset_tag' } 11 | let(:extension) { '.css' } 12 | 13 | after do 14 | JekyllAssetPipeline::Pipeline.clear_cache 15 | clear_temp_path 16 | end 17 | 18 | it 'saves assets to staging path' do 19 | $stdout.stub(:puts, nil) do 20 | config['output_path'] = '/foobar_assets' 21 | pipeline, = JekyllAssetPipeline::Pipeline 22 | .run(manifest, prefix, source_path, temp_path, 23 | tag_name, extension, config) 24 | pipeline.assets.each do |asset| 25 | file_path = File.join(source_path, 26 | JekyllAssetPipeline::DEFAULTS['staging_path'], 27 | config['output_path'], asset.filename) 28 | File.open(file_path) do |file| 29 | _(file.read).must_equal(asset.content) 30 | end 31 | end 32 | end 33 | end 34 | 35 | it 'outputs processing and saved file status messages' do 36 | hash = JekyllAssetPipeline::Pipeline.hash(source_path, manifest, config) 37 | filename = "#{prefix}-#{hash}#{extension}" 38 | path = File.join(temp_path, JekyllAssetPipeline::DEFAULTS['output_path']) 39 | 40 | expected = 41 | "Asset Pipeline: Processing '#{tag_name}' manifest '#{prefix}'\n" \ 42 | "Asset Pipeline: Saved '#{filename}' to '#{path}'\n" 43 | 44 | _(proc do 45 | JekyllAssetPipeline::Pipeline 46 | .run(manifest, prefix, source_path, temp_path, 47 | tag_name, extension, config) 48 | end).must_output(expected) 49 | end 50 | 51 | it 'uses cached pipeline if manifest has been previously processed' do 52 | $stdout.stub(:puts, nil) do 53 | pipeline1, cached1 = JekyllAssetPipeline::Pipeline 54 | .run(manifest, prefix, source_path, temp_path, 55 | tag_name, extension, config) 56 | _(cached1).must_equal(false) 57 | 58 | pipeline2, cached2 = JekyllAssetPipeline::Pipeline 59 | .run(manifest, prefix, source_path, temp_path, 60 | tag_name, extension, config) 61 | _(cached2).must_equal(true) 62 | _(pipeline2).must_equal(pipeline1) 63 | end 64 | end 65 | 66 | describe 'templating' do 67 | it 'overrides default if custom css template is defined' do 68 | # Define test template 69 | module JekyllAssetPipeline 70 | class NewCssTagTemplate < Template 71 | def self.filetype 72 | '.css' 73 | end 74 | 75 | def html 76 | 'foobar_template' 77 | end 78 | end 79 | end 80 | 81 | $stdout.stub(:puts, nil) do 82 | pipeline, = JekyllAssetPipeline::Pipeline 83 | .run(manifest, prefix, source_path, temp_path, 84 | tag_name, '.css', config) 85 | _(pipeline.html).must_equal('foobar_template') 86 | end 87 | 88 | # Clean up test template 89 | JekyllAssetPipeline::Template 90 | .subclasses.delete(JekyllAssetPipeline::NewCssTagTemplate) 91 | Object::JekyllAssetPipeline.send(:remove_const, :NewCssTagTemplate) 92 | end 93 | 94 | it 'overrides default if custom js template is defined' do 95 | # Define test template 96 | module JekyllAssetPipeline 97 | class NewJsTagTemplate < Template 98 | def self.filetype 99 | '.js' 100 | end 101 | 102 | def html 103 | 'foobar_template' 104 | end 105 | end 106 | end 107 | 108 | $stdout.stub(:puts, nil) do 109 | pipeline, = JekyllAssetPipeline::Pipeline 110 | .run(manifest, prefix, source_path, temp_path, 111 | tag_name, '.js', config) 112 | _(pipeline.html).must_equal('foobar_template') 113 | end 114 | 115 | # Clean up test template 116 | JekyllAssetPipeline::Template 117 | .subclasses.delete(JekyllAssetPipeline::NewJsTagTemplate) 118 | Object::JekyllAssetPipeline.send(:remove_const, :NewJsTagTemplate) 119 | end 120 | end 121 | 122 | describe 'pipeline#html' do 123 | it 'returns html link tag if css' do 124 | $stdout.stub(:puts, nil) do 125 | pipeline, = JekyllAssetPipeline::Pipeline 126 | .run(manifest, prefix, source_path, temp_path, 127 | tag_name, '.css', config) 128 | _(pipeline.html).must_match(/link/i) 129 | end 130 | end 131 | 132 | it 'returns html script tag if js' do 133 | $stdout.stub(:puts, nil) do 134 | pipeline, = JekyllAssetPipeline::Pipeline 135 | .run(manifest, prefix, source_path, temp_path, 136 | tag_name, '.js', config) 137 | _(pipeline.html).must_match(/script/i) 138 | end 139 | end 140 | 141 | it 'links to display_path if option is set' do 142 | $stdout.stub(:puts, nil) do 143 | config['display_path'] = 'foo/bar/baz' 144 | pipeline, = JekyllAssetPipeline::Pipeline 145 | .run(manifest, prefix, source_path, temp_path, 146 | tag_name, '.js', config) 147 | _(pipeline.html).must_match(%r{/foo\/bar\/baz/}) 148 | end 149 | end 150 | end 151 | 152 | context 'bundle => true' do 153 | before do 154 | config['bundle'] = true 155 | end 156 | 157 | it 'bundles assets into one file when bundle => true' do 158 | $stdout.stub(:puts, nil) do 159 | pipeline, = JekyllAssetPipeline::Pipeline 160 | .run(manifest, prefix, source_path, temp_path, 161 | tag_name, extension, config) 162 | _(pipeline.assets.size).must_equal(1) 163 | end 164 | end 165 | 166 | it 'saves bundled file with filename starting with prefix' do 167 | $stdout.stub(:puts, nil) do 168 | pipeline, = JekyllAssetPipeline::Pipeline 169 | .run(manifest, prefix, source_path, temp_path, 170 | tag_name, extension, config) 171 | pipeline.assets.each do |asset| 172 | _(asset.filename[0, prefix.length]).must_equal(prefix) 173 | end 174 | end 175 | end 176 | end 177 | 178 | context 'bundle => false' do 179 | before do 180 | config['bundle'] = false 181 | end 182 | 183 | it 'saves each file in manifest' do 184 | $stdout.stub(:puts, nil) do 185 | pipeline, = JekyllAssetPipeline::Pipeline 186 | .run(manifest, prefix, source_path, temp_path, 187 | tag_name, extension, config) 188 | file_paths = YAML.safe_load(manifest) 189 | _(pipeline.assets.size).must_equal(file_paths.size) 190 | files = file_paths.map { |f| File.basename(f) } 191 | pipeline.assets.each do |asset| 192 | _(files).must_include(asset.filename) 193 | end 194 | end 195 | end 196 | end 197 | 198 | describe 'asset conversion' do 199 | it 'converts asset with converter based on file extension' do 200 | # Define test converter 201 | module JekyllAssetPipeline 202 | class BazConverter < Converter 203 | def self.filetype 204 | '.baz' 205 | end 206 | 207 | def convert 208 | 'converted' 209 | end 210 | end 211 | end 212 | 213 | manifest = '- /_assets/unconverted.css.baz' 214 | $stdout.stub(:puts, nil) do 215 | pipeline, = JekyllAssetPipeline::Pipeline 216 | .run(manifest, prefix, source_path, temp_path, 217 | tag_name, extension, config) 218 | pipeline.assets.each do |asset| 219 | _(asset.content).must_equal('converted') 220 | end 221 | end 222 | 223 | # Clean up test converters 224 | JekyllAssetPipeline::Converter 225 | .subclasses.delete(JekyllAssetPipeline::BazConverter) 226 | Object::JekyllAssetPipeline.send(:remove_const, :BazConverter) 227 | end 228 | 229 | it 'ensures that converted asset is saved with expected extension' do 230 | # Define test converter 231 | module JekyllAssetPipeline 232 | class BazConverter < Converter 233 | def self.filetype 234 | '.baz' 235 | end 236 | 237 | def convert 238 | 'converted' 239 | end 240 | end 241 | end 242 | 243 | manifest = '- /_assets/unconverted.baz' 244 | $stdout.stub(:puts, nil) do 245 | pipeline, = JekyllAssetPipeline::Pipeline 246 | .run(manifest, prefix, source_path, temp_path, 247 | tag_name, extension, config) 248 | pipeline.assets.each do |asset| 249 | _(asset.content).must_equal('converted') 250 | _(File.extname(asset.filename)).must_equal('.css') 251 | end 252 | end 253 | 254 | # Clean up test converters 255 | JekyllAssetPipeline::Converter 256 | .subclasses.delete(JekyllAssetPipeline::BazConverter) 257 | Object::JekyllAssetPipeline.send(:remove_const, :BazConverter) 258 | end 259 | 260 | context 'when using multiple converters' do 261 | before do 262 | # Define test converters 263 | module JekyllAssetPipeline 264 | class BarConverter < Converter 265 | def self.filetype 266 | '.bar' 267 | end 268 | 269 | def convert 270 | 'converted to bar' 271 | end 272 | end 273 | 274 | class BazConverter < Converter 275 | def self.filetype 276 | '.baz' 277 | end 278 | 279 | def convert 280 | 'converted to baz' 281 | end 282 | end 283 | end 284 | end 285 | 286 | after do 287 | # Clean up test converters 288 | JekyllAssetPipeline::Converter 289 | .subclasses.delete(JekyllAssetPipeline::BarConverter) 290 | JekyllAssetPipeline::Converter 291 | .subclasses.delete(JekyllAssetPipeline::BazConverter) 292 | Object::JekyllAssetPipeline.send(:remove_const, :BarConverter) 293 | Object::JekyllAssetPipeline.send(:remove_const, :BazConverter) 294 | end 295 | 296 | it 'converts asset multiple times if needed in order based on ' \ 297 | 'extension' do 298 | $stdout.stub(:puts, nil) do 299 | manifest = '- /_assets/unconverted.css.baz.bar' 300 | pipeline, = JekyllAssetPipeline::Pipeline 301 | .run(manifest, prefix, source_path, temp_path, 302 | tag_name, extension, config) 303 | pipeline.assets.each do |asset| 304 | _(asset.content).must_equal('converted to baz') 305 | end 306 | 307 | manifest = '- /_assets/unconverted.css.bar.baz' 308 | pipeline, = JekyllAssetPipeline::Pipeline 309 | .run(manifest, prefix, source_path, temp_path, 310 | tag_name, extension, config) 311 | pipeline.assets.each do |asset| 312 | _(asset.content).must_equal('converted to bar') 313 | end 314 | end 315 | end 316 | end 317 | end 318 | 319 | describe 'asset compression' do 320 | it 'compresses assets with compressor based on file extension' do 321 | # Define test compressor 322 | module JekyllAssetPipeline 323 | class CssCompressor < Compressor 324 | def self.filetype 325 | '.css' 326 | end 327 | 328 | def compress 329 | 'compressed' 330 | end 331 | end 332 | end 333 | 334 | $stdout.stub(:puts, nil) do 335 | manifest = '- /_assets/uncompressed.css' 336 | pipeline, = JekyllAssetPipeline::Pipeline 337 | .run(manifest, prefix, source_path, temp_path, 338 | tag_name, extension, config) 339 | pipeline.assets.each do |asset| 340 | _(asset.content).must_equal('compressed') 341 | end 342 | end 343 | 344 | # Clean up test compressor 345 | JekyllAssetPipeline::Compressor 346 | .subclasses.delete(JekyllAssetPipeline::CssCompressor) 347 | Object::JekyllAssetPipeline.send(:remove_const, :CssCompressor) 348 | end 349 | end 350 | 351 | describe 'error handling' do 352 | it 'outputs error message if fails to read manifest' do 353 | manifest = 'invalid_manifest' 354 | _(proc do 355 | proc do 356 | JekyllAssetPipeline::Pipeline 357 | .run(manifest, prefix, source_path, temp_path, 358 | tag_name, extension, config) 359 | end.must_raise(NoMethodError) 360 | end).must_output(/failed/i) 361 | end 362 | 363 | it 'outputs error message if failure to convert asset' do 364 | # Define test converter 365 | module JekyllAssetPipeline 366 | class BazConverter < Converter 367 | def self.filetype 368 | '.baz' 369 | end 370 | 371 | def convert 372 | raise StandardError 373 | end 374 | end 375 | end 376 | 377 | manifest = '- /_assets/unconverted.baz' 378 | _(proc do 379 | proc do 380 | JekyllAssetPipeline::Pipeline 381 | .run(manifest, prefix, source_path, temp_path, 382 | tag_name, extension, config) 383 | end.must_raise(StandardError) 384 | end).must_output(/failed/i) 385 | 386 | # Clean up test converters 387 | JekyllAssetPipeline::Converter 388 | .subclasses.delete(JekyllAssetPipeline::BazConverter) 389 | Object::JekyllAssetPipeline.send(:remove_const, :BazConverter) 390 | end 391 | 392 | it 'outputs error message if failure to compress asset' do 393 | # Define test compressor 394 | module JekyllAssetPipeline 395 | class CssCompressor < Compressor 396 | def self.filetype 397 | '.css' 398 | end 399 | 400 | def compress 401 | raise StandardError 402 | end 403 | end 404 | end 405 | 406 | manifest = '- /_assets/uncompressed.css' 407 | _(proc do 408 | proc do 409 | JekyllAssetPipeline::Pipeline 410 | .run(manifest, prefix, source_path, temp_path, 411 | tag_name, extension, config) 412 | end.must_raise(StandardError) 413 | end).must_output(/failed/i) 414 | 415 | # Clean up test compressor 416 | JekyllAssetPipeline::Compressor 417 | .subclasses.delete(JekyllAssetPipeline::CssCompressor) 418 | Object::JekyllAssetPipeline.send(:remove_const, :CssCompressor) 419 | end 420 | 421 | it 'stops processing pipeline if previously generated error' do 422 | # Define test converter 423 | module JekyllAssetPipeline 424 | class BazConverter < Converter 425 | def self.filetype 426 | '.baz' 427 | end 428 | 429 | def convert 430 | raise StandardError 431 | end 432 | end 433 | end 434 | 435 | manifest = '- /_assets/unconverted.baz' 436 | _(proc do 437 | proc do 438 | JekyllAssetPipeline::Pipeline 439 | .run(manifest, prefix, source_path, temp_path, 440 | tag_name, extension, config) 441 | end.must_raise(StandardError) 442 | end).must_output(/failed/i) 443 | 444 | _(proc do 445 | JekyllAssetPipeline::Pipeline 446 | .run(manifest, prefix, source_path, temp_path, 447 | tag_name, extension, config) 448 | end).must_output(nil) 449 | 450 | # Clean up test converters 451 | JekyllAssetPipeline::Converter 452 | .subclasses.delete(JekyllAssetPipeline::BazConverter) 453 | Object::JekyllAssetPipeline.send(:remove_const, :BazConverter) 454 | end 455 | 456 | it 'outputs error message if failure to collect asset' do 457 | # File.open is first used in the flow in 458 | # JekyllAssetPipeline::Pipeline.collect 459 | # The exception checking in JekyllAssetPipeline::Pipeline.collect is 460 | # actually a bit of overkill as JekyllAssetPipeline::Pipeline.hash (which 461 | # happens before in the flow) should catch if a manifest file can not been 462 | # opened 463 | File.stub(:open, -> { raise StandardError }) do 464 | manifest = '- /_assets/unconverted.baz' 465 | _(proc do 466 | proc do 467 | JekyllAssetPipeline::Pipeline 468 | .run(manifest, prefix, source_path, temp_path, 469 | tag_name, extension, config) 470 | end.must_raise(StandardError) 471 | end).must_output(/failed/i) 472 | end 473 | end 474 | 475 | it 'outputs error message if failure to write asset file' do 476 | # FileUtils.mkpath is first used in the flow Integration 477 | # JekyllAssetPipeline::Pipeline.write_asset_file 478 | FileUtils.stub(:mkpath, nil) do 479 | config['staging_path'] = 'we_probably_cant_write_here' 480 | manifest = '- /_assets/unconverted.baz' 481 | _(proc do 482 | proc do 483 | JekyllAssetPipeline::Pipeline 484 | .run(manifest, prefix, source_path, temp_path, 485 | tag_name, extension, config) 486 | end.must_raise(StandardError) 487 | end).must_output(/failed/i) 488 | end 489 | end 490 | end 491 | end 492 | -------------------------------------------------------------------------------- /spec/jekyll_asset_pipeline/pipeline_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require './spec/helper' 4 | 5 | # rubocop:disable Metrics/ModuleLength 6 | module JekyllAssetPipeline 7 | # rubocop:enable Metrics/ModuleLength 8 | describe Pipeline do 9 | describe 'class methods' do 10 | describe '::hash(source, manifest, options = {})' do 11 | let(:manifest) { "- /_assets/foo.css\n- /_assets/bar.css" } 12 | let(:expected_hash) do 13 | Digest::MD5.hexdigest(YAML.safe_load(manifest).map! do |path| 14 | "#{path}#{File.mtime(File.join(source_path, path)).to_i}" 15 | end.join.concat(JekyllAssetPipeline::DEFAULTS.to_s)) 16 | end 17 | 18 | subject { JekyllAssetPipeline::Pipeline.hash(source_path, manifest) } 19 | 20 | it 'returns a md5 hash of the manifest contents' do 21 | _(subject).must_equal(expected_hash) 22 | end 23 | end 24 | end 25 | 26 | describe 'instance methods' do 27 | # Clean up temp files saved to spec/resources/temp 28 | after { FileUtils.remove_dir(temp_path, force: true) } 29 | 30 | let(:manifest) { "- /_assets/foo.css\n- /_assets/bar.css" } 31 | let(:prefix) { 'foobar' } 32 | let(:type) { '.css' } 33 | let(:options) { {} } 34 | let(:pipeline) do 35 | Pipeline.new(manifest, prefix, source_path, temp_path, type, options) 36 | end 37 | 38 | describe '#html' do 39 | subject { pipeline.html } 40 | 41 | before do 42 | # Mock custom converter 43 | template = MiniTest::Mock.new 44 | klass = MiniTest::Mock.new 45 | 46 | YAML.safe_load(manifest).size.times do 47 | template.expect(:html, 'html') 48 | klass.expect(:filetype, '.css') 49 | klass.expect(:new, template, [String, String]) 50 | klass.expect(:nil?, false) 51 | end 52 | 53 | JekyllAssetPipeline::Template.stub(:subclasses, [klass]) do 54 | pipeline 55 | end 56 | end 57 | 58 | context 'with custom template' do 59 | it 'outputs template html' do 60 | _(subject).must_equal('html') 61 | end 62 | end 63 | end 64 | 65 | describe '#assets' do 66 | subject { pipeline.assets } 67 | 68 | context 'with custom converter' do 69 | let(:manifest) { '- /_assets/foo.scss' } 70 | 71 | before do 72 | # Mock custom converter 73 | converter = MiniTest::Mock.new 74 | klass = MiniTest::Mock.new 75 | 76 | YAML.safe_load(manifest).size.times do 77 | converter.expect(:converted, 'converted') 78 | 2.times { klass.expect(:filetype, '.scss') } 79 | klass.expect(:new, converter, [JekyllAssetPipeline::Asset]) 80 | klass.expect(:nil?, false) 81 | end 82 | 83 | JekyllAssetPipeline::Converter.stub(:subclasses, [klass]) do 84 | pipeline 85 | end 86 | end 87 | 88 | it 'converts asset content' do 89 | _(subject.last.content).must_equal('converted') 90 | end 91 | end 92 | 93 | context 'bundle => true' do 94 | let(:options) { { 'bundle' => true } } 95 | 96 | before { pipeline } 97 | 98 | it 'has one asset when multiple files are in manifest' do 99 | _(YAML.safe_load(manifest).size).must_be :>, 1 100 | _(subject.size).must_equal(1) 101 | end 102 | 103 | it 'generates a filename with md5 for the bundled asset' do 104 | hash = JekyllAssetPipeline::Pipeline 105 | .hash(source_path, manifest, options) 106 | _(subject.last.filename).must_equal("#{prefix}-#{hash}#{type}") 107 | end 108 | 109 | it 'saves asset to disk at the staging path' do 110 | asset = subject.last 111 | staging_path = File.join(source_path, DEFAULTS['staging_path'], 112 | asset.output_path, asset.filename) 113 | _(File.exist?(staging_path)).must_equal(true) 114 | end 115 | end 116 | 117 | context 'bundle => false' do 118 | let(:options) { { 'bundle' => false } } 119 | 120 | before { pipeline } 121 | 122 | it 'has same number of assets as files in manifest' do 123 | _(subject.size).must_equal(YAML.safe_load(manifest).size) 124 | end 125 | 126 | it 'does not change the filenames of the assets' do 127 | YAML.safe_load(manifest).each do |p| 128 | _(subject.select do |a| 129 | a.filename == File.basename(p) 130 | end.size).must_equal(1) 131 | end 132 | end 133 | 134 | it 'saves assets to disk at the staging path' do 135 | subject.each do |a| 136 | staging_path = File.join(source_path, DEFAULTS['staging_path'], 137 | a.output_path, a.filename) 138 | _(File.exist?(staging_path)).must_equal(true) 139 | end 140 | end 141 | end 142 | 143 | context 'compress => true' do 144 | let(:options) { { 'compress' => true } } 145 | 146 | before do 147 | # Mock custom compressor 148 | compressor = MiniTest::Mock.new 149 | klass = MiniTest::Mock.new 150 | 151 | YAML.safe_load(manifest).size.times do 152 | compressor.expect(:compressed, 'compressed') 153 | klass.expect(:filetype, '.css') 154 | klass.expect(:new, compressor, [String]) 155 | klass.expect(:nil?, false) 156 | end 157 | 158 | JekyllAssetPipeline::Compressor.stub(:subclasses, [klass]) do 159 | pipeline 160 | end 161 | end 162 | 163 | it 'compresses asset content' do 164 | subject.each { |a| _(a.content).must_equal('compressed') } 165 | end 166 | end 167 | 168 | context 'gzip => true' do 169 | let(:options) { { 'gzip' => true } } 170 | let(:manifest) { '- /_assets/foo.css' } 171 | 172 | before do 173 | Zlib::Deflate.stub(:deflate, 'gzipped') do 174 | pipeline 175 | end 176 | end 177 | 178 | it 'has twice as many assets as files in manifest' do 179 | _(subject.size).must_equal(YAML.safe_load(manifest).size * 2) 180 | end 181 | 182 | it 'creates half of assets with filenames ending in .gz' do 183 | _(subject.select do |asset| 184 | File.extname(asset.filename) == '.gz' 185 | end.size).must_equal(subject.size / 2) 186 | end 187 | 188 | it 'gzips asset content' do 189 | _(subject.last.content).must_equal('gzipped') 190 | end 191 | end 192 | end 193 | end 194 | end 195 | end 196 | -------------------------------------------------------------------------------- /spec/jekyll_asset_pipeline/template_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require './spec/helper' 4 | 5 | # rubocop:disable Metrics/ModuleLength 6 | module JekyllAssetPipeline 7 | # rubocop:enable Metrics/ModuleLength 8 | describe Template do 9 | context 'with default template' do 10 | describe 'class methods' do 11 | describe '::filetype' do 12 | specify { _(Template.filetype).must_be_instance_of(String) } 13 | end 14 | 15 | describe '::priority' do 16 | specify { _(Template.priority).must_be_kind_of(Integer) } 17 | end 18 | end 19 | 20 | describe 'instance methods' do 21 | subject { Template.new('path', 'somefile.foo') } 22 | 23 | describe '#new(path, filename)' do 24 | specify do 25 | _(subject.instance_variable_get(:@path)).must_equal('path') 26 | _( 27 | subject.instance_variable_get(:@filename) 28 | ).must_equal('somefile.foo') 29 | end 30 | end 31 | 32 | describe 'html' do 33 | specify { _(subject.html).must_equal("path/somefile.foo\n") } 34 | end 35 | end 36 | end 37 | 38 | context 'with css_tag_template' do 39 | describe 'class methods' do 40 | describe '::filetype' do 41 | specify { _(CssTagTemplate.filetype).must_equal('.css') } 42 | end 43 | 44 | describe '::priority' do 45 | specify { _(CssTagTemplate.priority).must_equal(-1) } 46 | end 47 | end 48 | 49 | describe 'instance methods' do 50 | subject { CssTagTemplate.new('path', 'somefile.foo') } 51 | 52 | describe '#new(path, filename)' do 53 | specify do 54 | _(subject.instance_variable_get(:@path)).must_equal('path') 55 | _( 56 | subject.instance_variable_get(:@filename) 57 | ).must_equal('somefile.foo') 58 | end 59 | end 60 | 61 | describe 'html' do 62 | specify do 63 | _(subject.html).must_equal("") 65 | end 66 | end 67 | end 68 | end 69 | 70 | context 'with javascript_tag_template' do 71 | describe 'class methods' do 72 | describe '::filetype' do 73 | specify { _(JavaScriptTagTemplate.filetype).must_equal('.js') } 74 | end 75 | 76 | describe '::priority' do 77 | specify { _(JavaScriptTagTemplate.priority).must_equal(-1) } 78 | end 79 | end 80 | 81 | describe 'instance methods' do 82 | subject { JavaScriptTagTemplate.new('path', 'somefile.foo') } 83 | 84 | describe '#new(path, filename)' do 85 | specify do 86 | _(subject.instance_variable_get(:@path)).must_equal('path') 87 | _( 88 | subject.instance_variable_get(:@filename) 89 | ).must_equal('somefile.foo') 90 | end 91 | end 92 | 93 | describe 'html' do 94 | specify do 95 | _(subject.html).must_equal("") 97 | end 98 | end 99 | end 100 | end 101 | 102 | context 'with custom template' do 103 | before do 104 | require './spec/resources/source/_plugins/jekyll_asset_pipeline' 105 | end 106 | describe 'class methods' do 107 | describe '::filetype' do 108 | specify { _(TestTemplate.filetype).must_equal('.foo') } 109 | end 110 | 111 | describe '::priority' do 112 | specify { _(TestTemplate.priority).must_equal(1) } 113 | end 114 | end 115 | 116 | describe 'instance methods' do 117 | before do 118 | require './spec/resources/source/_plugins/jekyll_asset_pipeline' 119 | end 120 | subject { TestTemplate.new('path', 'somefile.foo') } 121 | 122 | describe '#new(path, filename)' do 123 | specify do 124 | _(subject.instance_variable_get(:@path)).must_equal('path') 125 | _( 126 | subject.instance_variable_get(:@filename) 127 | ).must_equal('somefile.foo') 128 | end 129 | end 130 | 131 | describe 'html' do 132 | specify { _(subject.html).must_equal('test_template_html') } 133 | end 134 | end 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /spec/jekyll_asset_pipeline/version_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require './spec/helper' 4 | 5 | module JekyllAssetPipeline 6 | describe VERSION do 7 | subject { JekyllAssetPipeline::VERSION } 8 | 9 | it 'returns a string' do 10 | _(subject).must_be_instance_of(String) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/resources/source/_assets/bar.css: -------------------------------------------------------------------------------- 1 | .bar { 2 | display: inline; 3 | } 4 | -------------------------------------------------------------------------------- /spec/resources/source/_assets/bar.scss: -------------------------------------------------------------------------------- 1 | .bar { 2 | .foo { display: block; } 3 | } 4 | -------------------------------------------------------------------------------- /spec/resources/source/_assets/foo.css: -------------------------------------------------------------------------------- 1 | .foo { 2 | display: inline; 3 | } 4 | -------------------------------------------------------------------------------- /spec/resources/source/_assets/foo.scss: -------------------------------------------------------------------------------- 1 | .foo { 2 | .bar { display: none; } 3 | } 4 | -------------------------------------------------------------------------------- /spec/resources/source/_assets/uncompressed.css: -------------------------------------------------------------------------------- 1 | uncompressed 2 | -------------------------------------------------------------------------------- /spec/resources/source/_assets/unconverted.baz: -------------------------------------------------------------------------------- 1 | unconverted 2 | -------------------------------------------------------------------------------- /spec/resources/source/_assets/unconverted.css.bar.baz: -------------------------------------------------------------------------------- 1 | unconverted 2 | -------------------------------------------------------------------------------- /spec/resources/source/_assets/unconverted.css.baz: -------------------------------------------------------------------------------- 1 | unconverted 2 | -------------------------------------------------------------------------------- /spec/resources/source/_assets/unconverted.css.baz.bar: -------------------------------------------------------------------------------- 1 | unconverted 2 | -------------------------------------------------------------------------------- /spec/resources/source/_plugins/jekyll_asset_pipeline.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllAssetPipeline 4 | class TestConverter < JekyllAssetPipeline::Converter 5 | def self.filetype 6 | '.foo' 7 | end 8 | 9 | def convert 10 | 'converted' 11 | end 12 | end 13 | 14 | class TestCompressor < JekyllAssetPipeline::Compressor 15 | def self.filetype 16 | '.foo' 17 | end 18 | 19 | def compress 20 | 'compressed' 21 | end 22 | end 23 | 24 | class TestTemplate < JekyllAssetPipeline::Template 25 | def self.filetype 26 | '.foo' 27 | end 28 | 29 | def self.priority 30 | 1 31 | end 32 | 33 | def html 34 | 'test_template_html' 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/resources/source/css-manifest.yml: -------------------------------------------------------------------------------- 1 | - /_assets/foo.css 2 | - /_assets/bar.css 3 | --------------------------------------------------------------------------------