jekyll-minibundle 1.5.1 → 1.6.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 +18 -6
- data/README.md +34 -24
- data/Rakefile +19 -14
- data/jekyll-minibundle.gemspec +5 -4
- data/lib/jekyll/minibundle/asset_bundle.rb +24 -4
- data/lib/jekyll/minibundle/asset_file_operations.rb +0 -8
- data/lib/jekyll/minibundle/asset_file_properties.rb +26 -8
- data/lib/jekyll/minibundle/asset_file_registry.rb +57 -21
- data/lib/jekyll/minibundle/asset_tag_markup.rb +13 -4
- data/lib/jekyll/minibundle/bundle_file.rb +9 -6
- data/lib/jekyll/minibundle/compatibility.rb +11 -1
- data/lib/jekyll/minibundle/development_file.rb +6 -0
- data/lib/jekyll/minibundle/development_file_collection.rb +3 -16
- data/lib/jekyll/minibundle/environment.rb +5 -12
- data/lib/jekyll/minibundle/files.rb +18 -0
- data/lib/jekyll/minibundle/hashes.rb +20 -0
- data/lib/jekyll/minibundle/mini_bundle_block.rb +52 -12
- data/lib/jekyll/minibundle/mini_stamp_tag.rb +27 -8
- data/lib/jekyll/minibundle/stamp_file.rb +9 -6
- data/lib/jekyll/minibundle/version.rb +1 -1
- data/test/fixture/site/_bin/with_count +6 -5
- data/test/integration/known_caveats_test.rb +89 -0
- data/test/integration/minibundle_development_mode_test.rb +171 -26
- data/test/integration/minibundle_production_mode_test.rb +234 -42
- data/test/integration/ministamp_development_mode_test.rb +145 -0
- data/test/integration/{ministamp_test.rb → ministamp_production_mode_test.rb} +72 -23
- data/test/integration/static_files_as_asset_sources_test.rb +3 -0
- data/test/support/assertions.rb +24 -0
- data/test/support/fixture_config.rb +13 -10
- data/test/support/static_file_api_config.rb +9 -3
- data/test/support/test_case.rb +7 -6
- data/test/unit/asset_bundle_test.rb +16 -2
- data/test/unit/asset_file_registry_test.rb +117 -14
- data/test/unit/asset_tag_markup_test.rb +17 -5
- data/test/unit/bundle_file_properties_test.rb +32 -8
- data/test/unit/bundle_file_writing_test.rb +10 -11
- data/test/unit/development_file_collection_properties_test.rb +47 -15
- data/test/unit/environment_test.rb +0 -13
- data/test/unit/files_test.rb +46 -0
- data/test/unit/hashes_test.rb +41 -0
- data/test/unit/mini_bundle_block_test.rb +56 -1
- data/test/unit/mini_stamp_tag_test.rb +8 -8
- data/test/unit/stamp_file_properties_test.rb +32 -13
- data/test/unit/stamp_file_writing_test.rb +1 -5
- metadata +38 -16
- data/test/unit/development_file_collection_writing_test.rb +0 -43
- data/test/unit/jekyll_payload_test.rb +0 -64
@@ -1,44 +1,80 @@
|
|
1
|
-
require 'jekyll/minibundle/environment'
|
2
|
-
require 'jekyll/minibundle/development_file_collection'
|
3
1
|
require 'jekyll/minibundle/bundle_file'
|
2
|
+
require 'jekyll/minibundle/development_file'
|
3
|
+
require 'jekyll/minibundle/development_file_collection'
|
4
|
+
require 'jekyll/minibundle/environment'
|
4
5
|
require 'jekyll/minibundle/stamp_file'
|
5
6
|
|
6
7
|
module Jekyll::Minibundle
|
7
8
|
module AssetFileRegistry
|
8
9
|
class << self
|
9
10
|
def clear
|
10
|
-
@
|
11
|
+
@_files = {}
|
11
12
|
end
|
12
13
|
|
13
|
-
def
|
14
|
-
|
15
|
-
@_instances[asset_destination_path] ||= register_bundle_file(site, bundle_config)
|
14
|
+
def register_bundle_file(site, bundle_config)
|
15
|
+
register_file_for_bundle_block(BundleFile, site, bundle_config) { |file| [file] }
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
|
18
|
+
def register_development_file_collection(site, bundle_config)
|
19
|
+
register_file_for_bundle_block(DevelopmentFileCollection, site, bundle_config, &:files)
|
20
|
+
end
|
21
|
+
|
22
|
+
def register_stamp_file(site, asset_source_path, asset_destination_path)
|
23
|
+
register_file_for_stamp_tag(StampFile, site, asset_source_path, asset_destination_path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def register_development_file(site, asset_source_path, asset_destination_path)
|
27
|
+
register_file_for_stamp_tag(DevelopmentFile, site, asset_source_path, asset_destination_path)
|
20
28
|
end
|
21
29
|
|
22
30
|
private
|
23
31
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
32
|
+
def register_file_for_bundle_block(file_class, site, bundle_config, &get_files)
|
33
|
+
asset_destination_path = "#{bundle_config.fetch('destination_path')}.#{bundle_config.fetch('type')}"
|
34
|
+
|
35
|
+
cached = @_files[asset_destination_path]
|
36
|
+
|
37
|
+
if cached
|
38
|
+
raise "minibundle block has same destination path as a ministamp tag: #{asset_destination_path}" if cached.fetch(:type) != :bundle
|
39
|
+
|
40
|
+
cached_file = cached.fetch(:file)
|
41
|
+
|
42
|
+
if bundle_config == cached.fetch(:config)
|
43
|
+
get_files.call(cached_file).each do |file|
|
44
|
+
site.static_files << file unless site.static_files.include?(file)
|
45
|
+
end
|
46
|
+
return cached_file
|
47
|
+
else
|
48
|
+
get_files.call(cached_file).each { |file| site.static_files.delete(file) }
|
49
|
+
end
|
29
50
|
end
|
30
|
-
end
|
31
51
|
|
32
|
-
|
33
|
-
|
52
|
+
new_file = file_class.new(site, bundle_config)
|
53
|
+
@_files[asset_destination_path] = {type: :bundle, file: new_file, config: bundle_config}
|
54
|
+
get_files.call(new_file).each { |file| site.static_files << file }
|
55
|
+
new_file
|
34
56
|
end
|
35
57
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
58
|
+
def register_file_for_stamp_tag(file_class, site, asset_source_path, asset_destination_path)
|
59
|
+
cached = @_files[asset_destination_path]
|
60
|
+
|
61
|
+
if cached
|
62
|
+
raise "ministamp tag has same destination path as a minibundle block: #{asset_destination_path}" if cached.fetch(:type) != :stamp
|
63
|
+
|
64
|
+
cached_file = cached.fetch(:file)
|
65
|
+
|
66
|
+
if asset_source_path == cached.fetch(:config)
|
67
|
+
site.static_files << cached_file unless site.static_files.include?(cached_file)
|
68
|
+
return cached_file
|
69
|
+
else
|
70
|
+
site.static_files.delete(cached_file)
|
71
|
+
end
|
41
72
|
end
|
73
|
+
|
74
|
+
new_file = file_class.new(site, asset_source_path, asset_destination_path)
|
75
|
+
@_files[asset_destination_path] = {type: :stamp, file: new_file, config: asset_source_path}
|
76
|
+
site.static_files << new_file
|
77
|
+
new_file
|
42
78
|
end
|
43
79
|
end
|
44
80
|
|
@@ -3,14 +3,15 @@ require 'cgi'
|
|
3
3
|
module Jekyll::Minibundle
|
4
4
|
module AssetTagMarkup
|
5
5
|
class << self
|
6
|
-
def make_markup(type, path, attributes)
|
6
|
+
def make_markup(type, baseurl, path, attributes)
|
7
|
+
url = make_url(baseurl, path)
|
7
8
|
case type
|
8
9
|
when :js
|
9
|
-
%{<script type="text/javascript" src="#{
|
10
|
+
%{<script type="text/javascript" src="#{url}"#{make_attributes(attributes)}></script>}
|
10
11
|
when :css
|
11
|
-
%{<link rel="stylesheet" href="#{
|
12
|
+
%{<link rel="stylesheet" href="#{url}"#{make_attributes(attributes)}>}
|
12
13
|
else
|
13
|
-
|
14
|
+
raise ArgumentError, "Unknown type for generating bundle markup: #{type}, #{path}"
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
@@ -21,6 +22,14 @@ module Jekyll::Minibundle
|
|
21
22
|
def make_attribute(name, value)
|
22
23
|
%{ #{name}="#{CGI.escape_html(value)}"}
|
23
24
|
end
|
25
|
+
|
26
|
+
def make_url(baseurl, path)
|
27
|
+
if baseurl.empty?
|
28
|
+
path
|
29
|
+
else
|
30
|
+
File.join(baseurl, path)
|
31
|
+
end
|
32
|
+
end
|
24
33
|
end
|
25
34
|
end
|
26
35
|
end
|
@@ -2,7 +2,6 @@ require 'jekyll/minibundle/asset_bundle'
|
|
2
2
|
require 'jekyll/minibundle/asset_file_operations'
|
3
3
|
require 'jekyll/minibundle/asset_file_properties'
|
4
4
|
require 'jekyll/minibundle/asset_stamp'
|
5
|
-
require 'jekyll/minibundle/asset_tag_markup'
|
6
5
|
|
7
6
|
module Jekyll::Minibundle
|
8
7
|
class BundleFile
|
@@ -17,7 +16,6 @@ module Jekyll::Minibundle
|
|
17
16
|
asset_source_dir = File.join(@site.source, config.fetch('source_dir'))
|
18
17
|
@asset_paths = config.fetch('assets').map { |asset_path| File.join(asset_source_dir, "#{asset_path}.#{@type}") }
|
19
18
|
@destination_path = config.fetch('destination_path')
|
20
|
-
@attributes = config.fetch('attributes')
|
21
19
|
@minifier_cmd = config.fetch('minifier_cmd')
|
22
20
|
@stamped_at = nil
|
23
21
|
@is_modified = false
|
@@ -25,7 +23,8 @@ module Jekyll::Minibundle
|
|
25
23
|
|
26
24
|
def destination_path_for_markup
|
27
25
|
# we must rebundle here, if at all, in order to make sure the
|
28
|
-
# markup and generated file have the same
|
26
|
+
# markup destination and generated file paths have the same
|
27
|
+
# fingerprint
|
29
28
|
if modified?
|
30
29
|
@stamped_at = mtime
|
31
30
|
@is_modified = true
|
@@ -33,7 +32,7 @@ module Jekyll::Minibundle
|
|
33
32
|
asset_bundle.make_bundle
|
34
33
|
end
|
35
34
|
|
36
|
-
|
35
|
+
asset_destination_path
|
37
36
|
end
|
38
37
|
|
39
38
|
def path
|
@@ -48,8 +47,12 @@ module Jekyll::Minibundle
|
|
48
47
|
"#{@destination_path}-#{asset_stamp}.#{@type}"
|
49
48
|
end
|
50
49
|
|
51
|
-
def
|
52
|
-
@
|
50
|
+
def extname
|
51
|
+
".#{@type}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def modified_time
|
55
|
+
@asset_paths.map { |f| File.stat(f).mtime }.max
|
53
56
|
end
|
54
57
|
|
55
58
|
# writes destination only after `destination_path_for_markup` has
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Jekyll::Minibundle
|
2
2
|
module Compatibility
|
3
|
+
LOG_TOPIC = 'Minibundle:'.freeze
|
4
|
+
|
3
5
|
class << self
|
4
6
|
# SafeYAML.load is introduced in Jekyll 2.0.0
|
5
7
|
if defined?(::SafeYAML) && ::SafeYAML.respond_to?(:load)
|
@@ -14,10 +16,18 @@ module Jekyll::Minibundle
|
|
14
16
|
|
15
17
|
# Jekyll.logger is introduced in Jekyll 1.0.3
|
16
18
|
if ::Jekyll.respond_to?(:logger)
|
19
|
+
def log_error(msg)
|
20
|
+
::Jekyll.logger.error(LOG_TOPIC, msg)
|
21
|
+
end
|
22
|
+
|
17
23
|
def log_info(msg)
|
18
|
-
::Jekyll.logger.info(
|
24
|
+
::Jekyll.logger.info(LOG_TOPIC, msg)
|
19
25
|
end
|
20
26
|
else
|
27
|
+
def log_error(msg)
|
28
|
+
$stderr.puts(msg)
|
29
|
+
end
|
30
|
+
|
21
31
|
def log_info(msg)
|
22
32
|
$stdout.puts(msg)
|
23
33
|
end
|
@@ -16,6 +16,12 @@ module Jekyll::Minibundle
|
|
16
16
|
@stamped_at = nil
|
17
17
|
end
|
18
18
|
|
19
|
+
alias destination_path_for_markup asset_destination_path
|
20
|
+
|
21
|
+
def extname
|
22
|
+
File.extname(asset_destination_path)
|
23
|
+
end
|
24
|
+
|
19
25
|
def write(site_destination_dir)
|
20
26
|
if modified?
|
21
27
|
@stamped_at = mtime
|
@@ -1,10 +1,12 @@
|
|
1
|
-
require 'jekyll/minibundle/asset_tag_markup'
|
2
1
|
require 'jekyll/minibundle/development_file'
|
3
2
|
|
4
3
|
module Jekyll::Minibundle
|
5
4
|
class DevelopmentFileCollection
|
5
|
+
attr_reader :files
|
6
|
+
|
6
7
|
def initialize(site, config)
|
7
8
|
@type = config.fetch('type')
|
9
|
+
|
8
10
|
asset_source_dir = File.join(site.source, config.fetch('source_dir'))
|
9
11
|
destination_path = config.fetch('destination_path')
|
10
12
|
|
@@ -14,21 +16,6 @@ module Jekyll::Minibundle
|
|
14
16
|
asset_destination = File.join(destination_path, asset_basename)
|
15
17
|
DevelopmentFile.new(site, asset_source, asset_destination)
|
16
18
|
end
|
17
|
-
|
18
|
-
@attributes = config.fetch('attributes')
|
19
|
-
end
|
20
|
-
|
21
|
-
def add_as_static_file_to(site)
|
22
|
-
# NOTE: We could optimize here by iterating over site's static
|
23
|
-
# files only once instead of per each of our file. Seems like a
|
24
|
-
# premature optimization for now, however.
|
25
|
-
@files.each { |f| f.add_as_static_file_to(site) }
|
26
|
-
end
|
27
|
-
|
28
|
-
def destination_path_for_markup
|
29
|
-
@files.
|
30
|
-
map { |f| AssetTagMarkup.make_markup(@type, f.asset_destination_path, @attributes) }.
|
31
|
-
join("\n")
|
32
19
|
end
|
33
20
|
end
|
34
21
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'jekyll/minibundle/hashes'
|
2
|
+
|
1
3
|
module Jekyll::Minibundle
|
2
4
|
module Environment
|
3
5
|
class << self
|
@@ -7,23 +9,14 @@ module Jekyll::Minibundle
|
|
7
9
|
end
|
8
10
|
|
9
11
|
def development?(site)
|
10
|
-
mode = ENV['JEKYLL_MINIBUNDLE_MODE'] || Environment.find_site_config(site,
|
12
|
+
mode = ENV['JEKYLL_MINIBUNDLE_MODE'] || Environment.find_site_config(site, %w{minibundle mode}, String)
|
11
13
|
mode == 'development'
|
12
14
|
end
|
13
15
|
|
14
16
|
def find_site_config(site, keys, type)
|
15
|
-
value =
|
17
|
+
value = Hashes.dig(site.config, *keys)
|
16
18
|
if value && !value.is_a?(type)
|
17
|
-
|
18
|
-
end
|
19
|
-
value
|
20
|
-
end
|
21
|
-
|
22
|
-
def traverse_keys(obj, keys)
|
23
|
-
value = obj
|
24
|
-
keys.each do |key|
|
25
|
-
return nil unless value
|
26
|
-
value = value[key]
|
19
|
+
raise "Invalid site configuration for key #{keys.join('.')}; expecting type #{type}"
|
27
20
|
end
|
28
21
|
value
|
29
22
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Jekyll::Minibundle
|
2
|
+
module Files
|
3
|
+
def self.read_last(path, max_size)
|
4
|
+
File.open(path, 'rb') do |file|
|
5
|
+
return '' if max_size < 1
|
6
|
+
|
7
|
+
file_size = file.stat.size
|
8
|
+
|
9
|
+
if file_size < max_size
|
10
|
+
file.read(file_size)
|
11
|
+
else
|
12
|
+
file.seek(file_size - max_size, ::IO::SEEK_SET)
|
13
|
+
file.read(max_size)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Jekyll::Minibundle
|
2
|
+
module Hashes
|
3
|
+
class << self
|
4
|
+
def dig(obj, *keys)
|
5
|
+
value = obj
|
6
|
+
keys.each do |key|
|
7
|
+
return nil unless value
|
8
|
+
value = value[key]
|
9
|
+
end
|
10
|
+
value
|
11
|
+
end
|
12
|
+
|
13
|
+
def pick(hash, *keys)
|
14
|
+
keys.each_with_object({}) do |key, acc|
|
15
|
+
acc[key] = hash.fetch(key)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,29 +1,33 @@
|
|
1
|
-
require 'jekyll/minibundle/
|
1
|
+
require 'jekyll/minibundle/hashes'
|
2
2
|
require 'jekyll/minibundle/compatibility'
|
3
3
|
require 'jekyll/minibundle/environment'
|
4
|
+
require 'jekyll/minibundle/asset_file_registry'
|
5
|
+
require 'jekyll/minibundle/asset_tag_markup'
|
4
6
|
|
5
7
|
module Jekyll::Minibundle
|
6
8
|
class MiniBundleBlock < Liquid::Block
|
7
9
|
def initialize(tag_name, type, _tokens)
|
8
10
|
super
|
9
11
|
@type = type.strip.downcase.to_sym
|
10
|
-
if @type.empty?
|
11
|
-
fail ArgumentError, "No asset type for minibundle block; pass value such as 'css' or 'js' as the argument"
|
12
|
-
end
|
12
|
+
raise ArgumentError, "No asset type for minibundle block; pass value such as 'css' or 'js' as the argument" if @type.empty?
|
13
13
|
end
|
14
14
|
|
15
15
|
def render(context)
|
16
16
|
site = context.registers.fetch(:site)
|
17
17
|
bundle_config = get_current_bundle_config(Compatibility.load_yaml(super), site)
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
baseurl = bundle_config.fetch('baseurl')
|
19
|
+
attributes = bundle_config.fetch('attributes')
|
20
|
+
|
21
|
+
register_asset_files(site, bundle_config).map do |file|
|
22
|
+
AssetTagMarkup.make_markup(@type, baseurl, file.destination_path_for_markup, attributes)
|
23
|
+
end.join("\n")
|
21
24
|
end
|
22
25
|
|
23
26
|
def self.default_bundle_config
|
24
27
|
{
|
25
28
|
'source_dir' => '_assets',
|
26
29
|
'destination_path' => 'assets/site',
|
30
|
+
'baseurl' => '',
|
27
31
|
'assets' => [],
|
28
32
|
'attributes' => {}
|
29
33
|
}
|
@@ -32,14 +36,50 @@ module Jekyll::Minibundle
|
|
32
36
|
private
|
33
37
|
|
34
38
|
def get_current_bundle_config(local_bundle_config, site)
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
merge(
|
39
|
+
config =
|
40
|
+
MiniBundleBlock
|
41
|
+
.default_bundle_config
|
42
|
+
.merge(environment_bundle_config(site))
|
43
|
+
.merge(local_bundle_config)
|
44
|
+
.merge('type' => @type)
|
45
|
+
|
46
|
+
baseurl, destination_path = normalize_baseurl_and_destination_path(config.fetch('baseurl'), config.fetch('destination_path'))
|
47
|
+
|
48
|
+
config.merge('baseurl' => baseurl, 'destination_path' => destination_path)
|
39
49
|
end
|
40
50
|
|
41
51
|
def environment_bundle_config(site)
|
42
|
-
{
|
52
|
+
{'minifier_cmd' => Environment.minifier_command(site, @type)}
|
53
|
+
end
|
54
|
+
|
55
|
+
def normalize_baseurl_and_destination_path(baseurl, destination_path)
|
56
|
+
baseurl = '' if baseurl.nil?
|
57
|
+
|
58
|
+
unless destination_path.start_with?('/')
|
59
|
+
return [baseurl, destination_path]
|
60
|
+
end
|
61
|
+
|
62
|
+
normalized_baseurl = baseurl.empty? ? '/' : baseurl
|
63
|
+
normalized_destination_path = destination_path.sub(%r{\A/+}, '')
|
64
|
+
|
65
|
+
[normalized_baseurl, normalized_destination_path]
|
66
|
+
end
|
67
|
+
|
68
|
+
def register_asset_files(site, bundle_config)
|
69
|
+
registry_config = Hashes.pick(
|
70
|
+
bundle_config,
|
71
|
+
'type',
|
72
|
+
'source_dir',
|
73
|
+
'destination_path',
|
74
|
+
'assets',
|
75
|
+
'minifier_cmd'
|
76
|
+
)
|
77
|
+
|
78
|
+
if Environment.development?(site)
|
79
|
+
AssetFileRegistry.register_development_file_collection(site, registry_config).files
|
80
|
+
else
|
81
|
+
[AssetFileRegistry.register_bundle_file(site, registry_config)]
|
82
|
+
end
|
43
83
|
end
|
44
84
|
end
|
45
85
|
end
|
@@ -4,20 +4,39 @@ module Jekyll::Minibundle
|
|
4
4
|
class MiniStampTag < Liquid::Tag
|
5
5
|
def initialize(tag_name, text, _tokens)
|
6
6
|
super
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
|
8
|
+
@source_path, destination_path = text.split(/\s+/, 3)[0, 2]
|
9
|
+
|
10
|
+
if !@source_path || @source_path.empty?
|
11
|
+
raise ArgumentError, "No asset source for ministamp tag; pass value such as '_assets/site.css' as the first argument"
|
10
12
|
end
|
11
|
-
|
12
|
-
|
13
|
+
|
14
|
+
if !destination_path || destination_path.empty?
|
15
|
+
raise ArgumentError, "No asset destination for ministamp tag; pass value such as 'assets/site.css' as the second argument"
|
13
16
|
end
|
17
|
+
|
18
|
+
@baseurl, @destination_path = normalize_destination_path(destination_path)
|
14
19
|
end
|
15
20
|
|
16
21
|
def render(context)
|
17
22
|
site = context.registers.fetch(:site)
|
18
|
-
file =
|
19
|
-
|
20
|
-
|
23
|
+
file =
|
24
|
+
if Environment.development?(site)
|
25
|
+
AssetFileRegistry.register_development_file(site, @source_path, @destination_path)
|
26
|
+
else
|
27
|
+
AssetFileRegistry.register_stamp_file(site, @source_path, @destination_path)
|
28
|
+
end
|
29
|
+
@baseurl + file.destination_path_for_markup
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def normalize_destination_path(destination_path)
|
35
|
+
if destination_path.start_with?('/')
|
36
|
+
['/', destination_path.sub(%r{\A/+}, '')]
|
37
|
+
else
|
38
|
+
['', destination_path]
|
39
|
+
end
|
21
40
|
end
|
22
41
|
end
|
23
42
|
end
|