hanami-assets 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +49 -25
- data/bin/hanami-assets +4 -4
- data/hanami-assets.gemspec +7 -7
- data/lib/hanami/assets/bundler/asset.rb +98 -0
- data/lib/hanami/assets/bundler/compressor.rb +61 -0
- data/lib/hanami/assets/bundler/manifest_entry.rb +62 -0
- data/lib/hanami/assets/bundler.rb +35 -62
- data/lib/hanami/assets/cache.rb +58 -15
- data/lib/hanami/assets/compiler.rb +66 -45
- data/lib/hanami/assets/compilers/less.rb +29 -0
- data/lib/hanami/assets/compilers/sass.rb +46 -0
- data/lib/hanami/assets/compressors/sass_stylesheet.rb +2 -2
- data/lib/hanami/assets/config/global_sources.rb +1 -1
- data/lib/hanami/assets/config/manifest.rb +23 -3
- data/lib/hanami/assets/config/sources.rb +8 -3
- data/lib/hanami/assets/configuration.rb +90 -23
- data/lib/hanami/assets/helpers.rb +81 -9
- data/lib/hanami/assets/precompiler.rb +31 -8
- data/lib/hanami/assets/version.rb +1 -1
- data/lib/hanami/assets.rb +12 -10
- metadata +15 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ac62b03f4a875750283f436f0c830152e9decba
|
4
|
+
data.tar.gz: 31d8b0704edd9bf86756ebe3ca481226e12f1c73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 906de34660db373299b9e779a770c67a7192b209927050f4743f4b58510df4e4be9c8f51c13a4db2b3b1a8b6c89628f6fee087526cbd31b51071bfcd78500539
|
7
|
+
data.tar.gz: fd378bbdf3479319c9d8b0a5e1aa53384551eef44b7c9c6ed746397c4114d3efb78c25f14711d5221194ae19bf44c5d450a3a882b8b30ea6516061b44b9d7884
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,20 @@
|
|
1
1
|
# Hanami::Assets
|
2
2
|
Assets management for Ruby web applications
|
3
3
|
|
4
|
+
## v0.3.0 - 2016-07-22
|
5
|
+
### Added
|
6
|
+
- [Matthew Gibbons & Sean Collins] Subresource Integrity (SRI)
|
7
|
+
- [Matthew Gibbons & Sean Collins] Allow `javascript` and `stylesheet` helpers to accept a Hash representing HTML attributes. Eg. `<%= javascript 'application', async: true %>`
|
8
|
+
|
9
|
+
### Fixed
|
10
|
+
- [Alexander Gräfe] Safely precompile assets from directories with a dot in their name.
|
11
|
+
- [Luca Guidi] Detect changes for Sass/SCSS dependencies.
|
12
|
+
- [Maxim Dorofienko & Luca Guidi] Preserve static assets under public directory, by removing only assets directory and manifest at the precompile time.
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
– [Luca Guidi] Drop support for Ruby 2.0 and 2.1. Official support for JRuby 9.0.5.0+.
|
16
|
+
- [Luca Guidi] Don't create digest version of files under public directory, but only for precompiled files.
|
17
|
+
|
4
18
|
## v0.2.1 - 2016-02-05
|
5
19
|
### Changed
|
6
20
|
- [Derk-Jan Karrenbeld] Don't precompile `.map` files
|
data/README.md
CHANGED
@@ -51,9 +51,9 @@ $ gem install hanami-assets
|
|
51
51
|
|
52
52
|
### Helpers
|
53
53
|
|
54
|
-
|
54
|
+
`Hanami::Assets` provides asset-specific helpers to be used in templates.
|
55
55
|
They resolve one or multiple sources into corresponding HTML tags.
|
56
|
-
Those sources can be
|
56
|
+
Those sources can be either a name of a local asset or an absolute URL.
|
57
57
|
|
58
58
|
Given the following template:
|
59
59
|
|
@@ -73,7 +73,7 @@ Given the following template:
|
|
73
73
|
</html>
|
74
74
|
```
|
75
75
|
|
76
|
-
It will output this markup
|
76
|
+
It will output this markup:
|
77
77
|
|
78
78
|
```html
|
79
79
|
<!doctype HTML>
|
@@ -124,7 +124,7 @@ For advanced configurations, please have a look at
|
|
124
124
|
|
125
125
|
### Available Helpers
|
126
126
|
|
127
|
-
This
|
127
|
+
This gem ships with the following helpers:
|
128
128
|
|
129
129
|
* `javascript`
|
130
130
|
* `stylesheet`
|
@@ -191,10 +191,10 @@ that file would be copied into the public directory instead of the one under
|
|
191
191
|
`Hanami::Assets` is able to run assets preprocessors and **lazily compile** them
|
192
192
|
under `public/assets` (by default), before the markup is generated.
|
193
193
|
|
194
|
-
Imagine
|
194
|
+
Imagine you have `main.css.scss` under `app/assets/stylesheets` and `reset.css` under
|
195
195
|
`vendor/stylesheets`.
|
196
196
|
|
197
|
-
**The extensions
|
197
|
+
**The two extensions are important.**
|
198
198
|
The first one is mandatory and it's used to understand which asset type we are
|
199
199
|
handling: `.css` for stylesheets.
|
200
200
|
The second one is optional and it's for a preprocessor: `.scss` for Sass.
|
@@ -213,13 +213,13 @@ Hanami::Assets.configure do
|
|
213
213
|
end
|
214
214
|
```
|
215
215
|
|
216
|
-
|
216
|
+
And in a template you can use the `stylesheet` helper:
|
217
217
|
|
218
218
|
```erb
|
219
219
|
<%= stylesheet 'reset', 'main' %>
|
220
220
|
```
|
221
221
|
|
222
|
-
Your public directory will
|
222
|
+
Your public directory will look like this:
|
223
223
|
|
224
224
|
```shell
|
225
225
|
% tree public
|
@@ -231,23 +231,25 @@ public/
|
|
231
231
|
|
232
232
|
### Preprocessors engines
|
233
233
|
|
234
|
-
`Hanami::Assets` uses [Tilt](https://github.com/rtomayko/tilt) to provide support for the most common preprocessors, such as [Sass](http://sass-lang.com/) (including `sassc-ruby`), [Less](http://lesscss.org/), ES6, [JSX](https://jsx.github.io/), [CoffeScript](http://coffeescript.org), [Opal](http://opalrb.org), [Handlebars](http://handlebarsjs.com), [JBuilder](https://github.com/rails/jbuilder).
|
234
|
+
`Hanami::Assets` uses [Tilt](https://github.com/rtomayko/tilt) to provide support for the most common preprocessors, such as [Sass](http://sass-lang.com/) (including `sassc-ruby`), [Less](http://lesscss.org/), [ES6](https://babeljs.io/), [JSX](https://jsx.github.io/), [CoffeScript](http://coffeescript.org), [Opal](http://opalrb.org), [Handlebars](http://handlebarsjs.com), [JBuilder](https://github.com/rails/jbuilder).
|
235
235
|
|
236
|
-
In order to use one or more of them, be sure to
|
236
|
+
In order to use one or more of them, be sure to add the corresponding gem to your `Gemfile` and require the library.
|
237
237
|
|
238
238
|
#### EcmaScript 6
|
239
239
|
|
240
|
-
We strongly suggest
|
241
|
-
It isn't fully [supported](https://kangax.github.io/compat-table/es6/) yet by
|
240
|
+
We strongly suggest you use [EcmaScript 6](http://es6-features.org/) for your next project.
|
241
|
+
It isn't fully [supported](https://kangax.github.io/compat-table/es6/) yet by browsers, but it's the future of JavaScript.
|
242
242
|
|
243
|
-
As of today, you need to transpile ES6 code into
|
244
|
-
|
243
|
+
As of today, you need to 'transpile' ES6 code into ES5, which current browsers understand.
|
244
|
+
The most popular tool for this is [Babel](https://babeljs.io), which we support.
|
245
245
|
|
246
246
|
### Deployment
|
247
247
|
|
248
248
|
`Hanami::Assets` ships with an executable (`hanami-assets`), which can be used to precompile assets and make them cacheable by browsers (via checksum suffix).
|
249
249
|
|
250
|
-
|
250
|
+
__NOTE__: If you're using `Hanami::Assets` with the full `Hanami` framework, you should use `bundle exec hanami assets precompile` instead of `hanami-assets`.
|
251
|
+
|
252
|
+
Let's say we have an application that has a main file that requires the entire codebase (`config/environment.rb`), a gem that brings in Ember.js code, and the following sources:
|
251
253
|
|
252
254
|
```shell
|
253
255
|
% tree .
|
@@ -321,14 +323,14 @@ public
|
|
321
323
|
|
322
324
|
#### Compressors
|
323
325
|
|
324
|
-
Minification is a process that
|
325
|
-
The goal of this step
|
326
|
+
Minification is a process that shrinks file size in production, by removing unnecessary spaces and characters.
|
327
|
+
The goal of this step is to have lighter assets, which will be served faster to browsers.
|
326
328
|
|
327
|
-
Hanami supports JavaScript and
|
329
|
+
Hanami supports JavaScript and stylesheet minifiers.
|
328
330
|
|
329
331
|
Because this framework relies on external gems for minification, this feature is **turned off by default**.
|
330
332
|
|
331
|
-
To
|
333
|
+
To use minification, we need to specify which gem we want to use and add it to our `Gemfile`.
|
332
334
|
|
333
335
|
##### JavaScript Compressors
|
334
336
|
|
@@ -347,7 +349,7 @@ end
|
|
347
349
|
|
348
350
|
##### Stylesheet Compressors
|
349
351
|
|
350
|
-
Hanami can use the following compressors (aka minifiers) for
|
352
|
+
Hanami can use the following compressors (aka minifiers) for stylesheets.
|
351
353
|
|
352
354
|
* `:builtin` - Ruby based compressor. It doesn't require any external gem. It's fast, but not an efficient compressor.
|
353
355
|
* `:yui` - [YUI Compressor](http://yui.github.io/yuicompressor), it depends on [`yui-compressor`](https://rubygems.org/gems/yui-compressor) gem and it requires Java 1.4+
|
@@ -372,7 +374,9 @@ end
|
|
372
374
|
|
373
375
|
### Digest Mode
|
374
376
|
|
375
|
-
This is a mode that can be activated via
|
377
|
+
This is a mode that can be activated via configuration and it's suitable for production environments.
|
378
|
+
When generating files, it adds a string to the end of each file name, which is a cachesum of its contents.
|
379
|
+
This lets you leverage caching while still ensuring that clients get the most up-to-date assets (this is known as *cache busting*).
|
376
380
|
|
377
381
|
```ruby
|
378
382
|
Hanami::Assets.configure do
|
@@ -390,9 +394,29 @@ Once turned on, it will look at `/public/assets.json`, and helpers such as `java
|
|
390
394
|
<script src="/assets/application-d1829dc353b734e3adc24855693b70f9.js" type="text/javascript"></script>
|
391
395
|
```
|
392
396
|
|
397
|
+
### Subresource Integrity (SRI) Mode
|
398
|
+
|
399
|
+
This is a mode that can be activated via the configuration and it's suitable for production environments.
|
400
|
+
|
401
|
+
```ruby
|
402
|
+
Hanami::Assets.configure do
|
403
|
+
subresource_integrity true
|
404
|
+
end
|
405
|
+
```
|
406
|
+
|
407
|
+
Once turned on, it will look at `/public/assets.json`, and helpers such as `javascript` will include an `integrity` and `crossorigin` attribute.
|
408
|
+
|
409
|
+
```erb
|
410
|
+
<%= javascript 'application' %>
|
411
|
+
```
|
412
|
+
|
413
|
+
```html
|
414
|
+
<script src="/assets/application-d1829dc353b734e3adc24855693b70f9.js" type="text/javascript" integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC" crossorigin="anonymous"></script>
|
415
|
+
```
|
416
|
+
|
393
417
|
### CDN Mode
|
394
418
|
|
395
|
-
A Hanami project can serve assets via CDN.
|
419
|
+
A Hanami project can serve assets via a Content Delivery Network (CDN).
|
396
420
|
|
397
421
|
```ruby
|
398
422
|
Hanami::Assets.configure do
|
@@ -403,7 +427,7 @@ Hanami::Assets.configure do
|
|
403
427
|
end
|
404
428
|
```
|
405
429
|
|
406
|
-
|
430
|
+
From now on, helpers will return the absolute URL for the asset, hosted on the CDN specified.
|
407
431
|
|
408
432
|
```erb
|
409
433
|
<%= javascript 'application' %>
|
@@ -417,7 +441,7 @@ Since now on, helpers will return the CDN absolute URL for the asset.
|
|
417
441
|
|
418
442
|
Developers can maintain gems that distribute assets for Hanami. For instance `hanami-ember` or `hanami-jquery`.
|
419
443
|
|
420
|
-
|
444
|
+
To do this, inside your gem you have tell `Hanami::Assets` where to look for assets:
|
421
445
|
|
422
446
|
```ruby
|
423
447
|
# lib/hanami/jquery.rb
|
@@ -428,7 +452,7 @@ Hanami::Assets.sources << '/path/to/jquery'
|
|
428
452
|
|
429
453
|
* Make sure you have one of [ExecJS](https://github.com/rails/execjs)
|
430
454
|
supported runtime on your machine.
|
431
|
-
* Java 1.4+
|
455
|
+
* Java 1.4+ (for YUI Compressor and Google Closure Compiler)
|
432
456
|
|
433
457
|
```sh
|
434
458
|
bundle exec rake test
|
data/bin/hanami-assets
CHANGED
@@ -5,16 +5,16 @@ require 'pathname'
|
|
5
5
|
|
6
6
|
options = {}
|
7
7
|
OptionParser.new do |opts|
|
8
|
-
opts.banner =
|
8
|
+
opts.banner = 'Usage: hanami-assets --config=path/to/config.rb'
|
9
9
|
|
10
|
-
opts.on(
|
10
|
+
opts.on('-c', '--config FILE', 'Path to config') do |c|
|
11
11
|
options[:config] = c
|
12
12
|
end
|
13
13
|
end.parse!
|
14
14
|
|
15
|
-
config = options.fetch(:config) { raise ArgumentError.new(
|
15
|
+
config = options.fetch(:config) { raise ArgumentError.new('You must specify a configuration file') }
|
16
16
|
config = Pathname.new(config)
|
17
|
-
config.exist? or raise ArgumentError.new("Cannot find configuration file: #{
|
17
|
+
config.exist? or raise ArgumentError.new("Cannot find configuration file: #{config}") # rubocop:disable Style/AndOr
|
18
18
|
|
19
19
|
require 'hanami/assets'
|
20
20
|
load config
|
data/hanami-assets.gemspec
CHANGED
@@ -8,23 +8,23 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Hanami::Assets::VERSION
|
9
9
|
spec.authors = ['Luca Guidi', 'Trung Lê', 'Alfonso Uceda']
|
10
10
|
spec.email = ['me@lucaguidi.com', 'trung.le@ruby-journal.com', 'uceda73@gmail.com']
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
11
|
+
spec.summary = 'Assets management'
|
12
|
+
spec.description = 'Assets management for Ruby web applications'
|
13
13
|
spec.homepage = 'http://hanamirb.org'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
16
|
-
spec.files = `git ls-files -- lib/* bin/* CHANGELOG.md LICENSE.md README.md hanami-assets.gemspec`.split($/)
|
16
|
+
spec.files = `git ls-files -- lib/* bin/* CHANGELOG.md LICENSE.md README.md hanami-assets.gemspec`.split($/) # rubocop:disable Style/SpecialGlobalVars
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
|
-
spec.required_ruby_version = '>= 2.
|
20
|
+
spec.required_ruby_version = '>= 2.2.0'
|
21
21
|
|
22
|
-
spec.add_runtime_dependency 'hanami-utils', '~> 0.
|
23
|
-
spec.add_runtime_dependency 'hanami-helpers', '~> 0.
|
22
|
+
spec.add_runtime_dependency 'hanami-utils', '~> 0.8'
|
23
|
+
spec.add_runtime_dependency 'hanami-helpers', '~> 0.4'
|
24
24
|
spec.add_runtime_dependency 'tilt', '~> 2.0', '>= 2.0.2'
|
25
25
|
|
26
26
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
27
|
-
spec.add_development_dependency 'rake', '~>
|
27
|
+
spec.add_development_dependency 'rake', '~> 11'
|
28
28
|
spec.add_development_dependency 'minitest', '~> 5'
|
29
29
|
|
30
30
|
spec.add_development_dependency 'yui-compressor', '~> 0.12'
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Assets
|
5
|
+
# Bundle assets from a single application.
|
6
|
+
#
|
7
|
+
# @since 0.1.0
|
8
|
+
# @api private
|
9
|
+
class Bundler
|
10
|
+
# @since 0.3.0
|
11
|
+
# @api private
|
12
|
+
class Asset
|
13
|
+
# @since 0.3.0
|
14
|
+
# @api private
|
15
|
+
attr_reader :path
|
16
|
+
|
17
|
+
# @since 0.3.0
|
18
|
+
# @api private
|
19
|
+
attr_reader :configuration
|
20
|
+
|
21
|
+
# @since 0.3.0
|
22
|
+
# @api private
|
23
|
+
WILDCARD_EXT = '.*'.freeze
|
24
|
+
|
25
|
+
# Return a new instance
|
26
|
+
#
|
27
|
+
# @since 0.3.0
|
28
|
+
# @api private
|
29
|
+
def initialize(path, configuration)
|
30
|
+
@path = path
|
31
|
+
@configuration = configuration
|
32
|
+
end
|
33
|
+
|
34
|
+
# @since 0.3.0
|
35
|
+
# @api private
|
36
|
+
def expanded_path
|
37
|
+
::File.expand_path(@path)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @since 0.3.0
|
41
|
+
# @api private
|
42
|
+
def fingerprinted_target
|
43
|
+
::File.join(directory, "#{filename}-#{fingerprint}#{extension}")
|
44
|
+
end
|
45
|
+
|
46
|
+
# @since 0.3.0
|
47
|
+
# @api private
|
48
|
+
def expanded_fingerprinted_target
|
49
|
+
::File.expand_path(fingerprinted_target)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @since 0.3.0
|
53
|
+
# @api private
|
54
|
+
def base64_digest(algorithm)
|
55
|
+
raw_digest(algorithm).base64digest
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# @since 0.3.0
|
61
|
+
# @api private
|
62
|
+
def directory
|
63
|
+
::File.dirname(@path)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @since 0.3.0
|
67
|
+
# @api private
|
68
|
+
def filename
|
69
|
+
::File.basename(@path, WILDCARD_EXT)
|
70
|
+
end
|
71
|
+
|
72
|
+
# @since 0.3.0
|
73
|
+
# @api private
|
74
|
+
def extension
|
75
|
+
::File.extname(@path)
|
76
|
+
end
|
77
|
+
|
78
|
+
# @since 0.3.0
|
79
|
+
# @api private
|
80
|
+
def fingerprint
|
81
|
+
raw_digest(:md5).hexdigest
|
82
|
+
end
|
83
|
+
|
84
|
+
# @since 0.3.0
|
85
|
+
# @api private
|
86
|
+
def raw_digest(algorithm)
|
87
|
+
OpenSSL::Digest.new(algorithm.to_s, contents)
|
88
|
+
end
|
89
|
+
|
90
|
+
# @since 0.3.0
|
91
|
+
# @api private
|
92
|
+
def contents
|
93
|
+
::File.read(@path)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Hanami
|
2
|
+
module Assets
|
3
|
+
class Bundler
|
4
|
+
# Compresses a JS or CSS file
|
5
|
+
#
|
6
|
+
# @since 0.3.0
|
7
|
+
# @api private
|
8
|
+
class Compressor
|
9
|
+
# @since 0.3.0
|
10
|
+
# @api private
|
11
|
+
JAVASCRIPT_EXT = '.js'.freeze
|
12
|
+
|
13
|
+
# @since 0.3.0
|
14
|
+
# @api private
|
15
|
+
STYLESHEET_EXT = '.css'.freeze
|
16
|
+
|
17
|
+
# Return a new instance
|
18
|
+
#
|
19
|
+
# @since 0.3.0
|
20
|
+
# @api private
|
21
|
+
def initialize(path, configuration)
|
22
|
+
@path = path
|
23
|
+
@configuration = configuration
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [String, nil] the compressed contents of the file OR nil if it's not compressable
|
27
|
+
#
|
28
|
+
# @since 0.3.0
|
29
|
+
# @api private
|
30
|
+
def compress
|
31
|
+
case File.extname(@path)
|
32
|
+
when JAVASCRIPT_EXT then _compress(compressor(:js))
|
33
|
+
when STYLESHEET_EXT then _compress(compressor(:css))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# @since 0.3.0
|
40
|
+
# @api private
|
41
|
+
def compressor(type)
|
42
|
+
@configuration.__send__(:"#{ type }_compressor")
|
43
|
+
end
|
44
|
+
|
45
|
+
# @since 0.3.0
|
46
|
+
# @api private
|
47
|
+
def _compress(compressor)
|
48
|
+
compressor.compress(@path)
|
49
|
+
rescue => e
|
50
|
+
warn(
|
51
|
+
[
|
52
|
+
"Skipping compression of: `#{@path}'",
|
53
|
+
"Reason: #{e}\n",
|
54
|
+
"\t#{e.backtrace.join("\n\t")}\n\n"
|
55
|
+
].join("\n")
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Hanami
|
2
|
+
module Assets
|
3
|
+
class Bundler
|
4
|
+
# Constructs a hash for a single asset's manifest file entry
|
5
|
+
#
|
6
|
+
# @since 0.3.0
|
7
|
+
# @api private
|
8
|
+
class ManifestEntry
|
9
|
+
# @since 0.3.0
|
10
|
+
# @api private
|
11
|
+
SUBRESOURCE_INTEGRITY_SEPARATOR = '-'.freeze
|
12
|
+
|
13
|
+
# Return a new instance
|
14
|
+
#
|
15
|
+
# @since 0.3.0
|
16
|
+
# @api private
|
17
|
+
def initialize(asset)
|
18
|
+
@asset = asset
|
19
|
+
end
|
20
|
+
|
21
|
+
# A single entry for this asset, to go into manifest file
|
22
|
+
# @since 0.3.0
|
23
|
+
# @api private
|
24
|
+
def entry
|
25
|
+
{ name => values }
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# @since 0.3.0
|
31
|
+
# @api private
|
32
|
+
def name
|
33
|
+
_convert_to_url(@asset.expanded_path)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @since 0.3.0
|
37
|
+
# @api private
|
38
|
+
def values
|
39
|
+
Hash[
|
40
|
+
target: _convert_to_url(@asset.expanded_fingerprinted_target),
|
41
|
+
sri: subresource_integrity_values
|
42
|
+
]
|
43
|
+
end
|
44
|
+
|
45
|
+
# @since 0.3.0
|
46
|
+
# @api private
|
47
|
+
def subresource_integrity_values
|
48
|
+
@asset.configuration.subresource_integrity_algorithms.map do |algorithm|
|
49
|
+
[algorithm, @asset.base64_digest(algorithm)].join(SUBRESOURCE_INTEGRITY_SEPARATOR)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @since 0.3.0
|
54
|
+
# @api private
|
55
|
+
def _convert_to_url(path)
|
56
|
+
path.sub(@asset.configuration.public_directory.to_s, URL_REPLACEMENT)
|
57
|
+
.gsub(File::SEPARATOR, URL_SEPARATOR)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,7 +1,10 @@
|
|
1
|
-
require 'digest'
|
2
1
|
require 'fileutils'
|
3
2
|
require 'json'
|
4
3
|
|
4
|
+
require 'hanami/assets/bundler/compressor'
|
5
|
+
require 'hanami/assets/bundler/asset'
|
6
|
+
require 'hanami/assets/bundler/manifest_entry'
|
7
|
+
|
5
8
|
module Hanami
|
6
9
|
module Assets
|
7
10
|
# Bundle assets from a single application.
|
@@ -11,19 +14,7 @@ module Hanami
|
|
11
14
|
class Bundler
|
12
15
|
# @since 0.1.0
|
13
16
|
# @api private
|
14
|
-
DEFAULT_PERMISSIONS =
|
15
|
-
|
16
|
-
# @since 0.1.0
|
17
|
-
# @api private
|
18
|
-
JAVASCRIPT_EXT = '.js'.freeze
|
19
|
-
|
20
|
-
# @since 0.1.0
|
21
|
-
# @api private
|
22
|
-
STYLESHEET_EXT = '.css'.freeze
|
23
|
-
|
24
|
-
# @since 0.1.0
|
25
|
-
# @api private
|
26
|
-
WILDCARD_EXT = '.*'.freeze
|
17
|
+
DEFAULT_PERMISSIONS = 0o644
|
27
18
|
|
28
19
|
# @since 0.1.0
|
29
20
|
# @api private
|
@@ -45,7 +36,7 @@ module Hanami
|
|
45
36
|
# @since 0.1.0
|
46
37
|
# @api private
|
47
38
|
def initialize(configuration, duplicates)
|
48
|
-
@manifest = Hash
|
39
|
+
@manifest = Hash[]
|
49
40
|
@configuration = configuration
|
50
41
|
@configurations = if duplicates.empty?
|
51
42
|
[@configuration]
|
@@ -60,6 +51,7 @@ module Hanami
|
|
60
51
|
#
|
61
52
|
# * Compress
|
62
53
|
# * Create a checksum version
|
54
|
+
# * Generate an integrity digest
|
63
55
|
#
|
64
56
|
# At the end it will generate a digest manifest
|
65
57
|
#
|
@@ -67,78 +59,57 @@ module Hanami
|
|
67
59
|
# @see Hanami::Assets::Configuration#manifest
|
68
60
|
# @see Hanami::Assets::Configuration#manifest_path
|
69
61
|
def run
|
70
|
-
assets.each do |
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
62
|
+
assets.each do |path|
|
63
|
+
unless File.directory?(path)
|
64
|
+
configuration = _configuration_for(path)
|
65
|
+
process(Asset.new(path, configuration))
|
66
|
+
end
|
75
67
|
end
|
76
68
|
|
77
|
-
|
69
|
+
write_manifest_file
|
78
70
|
end
|
79
71
|
|
80
72
|
private
|
81
73
|
|
82
|
-
# @since 0.
|
74
|
+
# @since 0.3.0
|
83
75
|
# @api private
|
84
|
-
def
|
85
|
-
|
76
|
+
def process(asset)
|
77
|
+
compress_in_place!(asset)
|
78
|
+
copy_to_fingerprinted_location!(asset)
|
79
|
+
@manifest.merge!(ManifestEntry.new(asset).entry)
|
86
80
|
end
|
87
81
|
|
88
|
-
# @since 0.
|
82
|
+
# @since 0.3.0
|
89
83
|
# @api private
|
90
|
-
def
|
91
|
-
|
92
|
-
|
93
|
-
when STYLESHEET_EXT then _compress(compressor(:css, asset), asset)
|
94
|
-
end
|
84
|
+
def copy_to_fingerprinted_location!(asset)
|
85
|
+
FileUtils.cp(asset.path, asset.fingerprinted_target)
|
86
|
+
_set_permissions(asset.fingerprinted_target)
|
95
87
|
end
|
96
88
|
|
97
|
-
# @since 0.
|
89
|
+
# @since 0.3.0
|
98
90
|
# @api private
|
99
|
-
def
|
100
|
-
|
101
|
-
|
102
|
-
directory = ::File.dirname(asset)
|
103
|
-
target = [directory, "#{ filename }-#{ digest }#{ ext }"].join(::File::SEPARATOR)
|
104
|
-
|
105
|
-
FileUtils.cp(asset, target)
|
106
|
-
_set_permissions(target)
|
107
|
-
|
108
|
-
store_manifest(asset, target)
|
91
|
+
def compress_in_place!(asset)
|
92
|
+
compressed = Compressor.new(asset.path, asset.configuration).compress
|
93
|
+
_write(asset.path, compressed) unless compressed.nil?
|
109
94
|
end
|
110
95
|
|
111
|
-
# @since 0.
|
96
|
+
# @since 0.3.0
|
112
97
|
# @api private
|
113
|
-
def
|
98
|
+
def write_manifest_file
|
114
99
|
_write(@configuration.manifest_path, JSON.dump(@manifest))
|
115
100
|
end
|
116
101
|
|
117
102
|
# @since 0.1.0
|
118
103
|
# @api private
|
119
|
-
def
|
120
|
-
@
|
121
|
-
end
|
122
|
-
|
123
|
-
# @since 0.1.0
|
124
|
-
# @api private
|
125
|
-
def compressor(type, asset)
|
126
|
-
_configuration_for(asset).__send__(:"#{ type }_compressor")
|
127
|
-
end
|
128
|
-
|
129
|
-
# @since 0.1.0
|
130
|
-
# @api private
|
131
|
-
def _compress(compressor, asset)
|
132
|
-
_write(asset, compressor.compress(asset))
|
133
|
-
rescue => e
|
134
|
-
warn "Skipping compression of: `#{ asset }'\nReason: #{ e }\n\t#{ e.backtrace.join("\n\t") }\n\n"
|
104
|
+
def assets
|
105
|
+
Dir.glob("#{@configuration.destination_directory}#{::File::SEPARATOR}**#{::File::SEPARATOR}*")
|
135
106
|
end
|
136
107
|
|
137
108
|
# @since 0.1.0
|
138
109
|
# @api private
|
139
110
|
def _convert_to_url(path)
|
140
|
-
path.sub(public_directory.to_s, URL_REPLACEMENT)
|
141
|
-
|
111
|
+
path.sub(public_directory.to_s, URL_REPLACEMENT)
|
112
|
+
.gsub(File::SEPARATOR, URL_SEPARATOR)
|
142
113
|
end
|
143
114
|
|
144
115
|
# @since 0.1.0
|
@@ -156,10 +127,12 @@ module Hanami
|
|
156
127
|
::File.chmod(DEFAULT_PERMISSIONS, path)
|
157
128
|
end
|
158
129
|
|
130
|
+
# @since 0.3.0
|
131
|
+
# @api private
|
159
132
|
def _configuration_for(asset)
|
160
133
|
url = _convert_to_url(asset)
|
161
134
|
|
162
|
-
@configurations.find {|config| url.start_with?(config.prefix) } ||
|
135
|
+
@configurations.find { |config| url.start_with?(config.prefix) } ||
|
163
136
|
@configuration
|
164
137
|
end
|
165
138
|
|