├── .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 | [](https://rubygems.org/gems/jekyll_asset_pipeline)
4 | [](https://travis-ci.org/matthodan/jekyll-asset-pipeline)
5 | [](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 |
--------------------------------------------------------------------------------