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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -6
  3. data/README.md +34 -24
  4. data/Rakefile +19 -14
  5. data/jekyll-minibundle.gemspec +5 -4
  6. data/lib/jekyll/minibundle/asset_bundle.rb +24 -4
  7. data/lib/jekyll/minibundle/asset_file_operations.rb +0 -8
  8. data/lib/jekyll/minibundle/asset_file_properties.rb +26 -8
  9. data/lib/jekyll/minibundle/asset_file_registry.rb +57 -21
  10. data/lib/jekyll/minibundle/asset_tag_markup.rb +13 -4
  11. data/lib/jekyll/minibundle/bundle_file.rb +9 -6
  12. data/lib/jekyll/minibundle/compatibility.rb +11 -1
  13. data/lib/jekyll/minibundle/development_file.rb +6 -0
  14. data/lib/jekyll/minibundle/development_file_collection.rb +3 -16
  15. data/lib/jekyll/minibundle/environment.rb +5 -12
  16. data/lib/jekyll/minibundle/files.rb +18 -0
  17. data/lib/jekyll/minibundle/hashes.rb +20 -0
  18. data/lib/jekyll/minibundle/mini_bundle_block.rb +52 -12
  19. data/lib/jekyll/minibundle/mini_stamp_tag.rb +27 -8
  20. data/lib/jekyll/minibundle/stamp_file.rb +9 -6
  21. data/lib/jekyll/minibundle/version.rb +1 -1
  22. data/test/fixture/site/_bin/with_count +6 -5
  23. data/test/integration/known_caveats_test.rb +89 -0
  24. data/test/integration/minibundle_development_mode_test.rb +171 -26
  25. data/test/integration/minibundle_production_mode_test.rb +234 -42
  26. data/test/integration/ministamp_development_mode_test.rb +145 -0
  27. data/test/integration/{ministamp_test.rb → ministamp_production_mode_test.rb} +72 -23
  28. data/test/integration/static_files_as_asset_sources_test.rb +3 -0
  29. data/test/support/assertions.rb +24 -0
  30. data/test/support/fixture_config.rb +13 -10
  31. data/test/support/static_file_api_config.rb +9 -3
  32. data/test/support/test_case.rb +7 -6
  33. data/test/unit/asset_bundle_test.rb +16 -2
  34. data/test/unit/asset_file_registry_test.rb +117 -14
  35. data/test/unit/asset_tag_markup_test.rb +17 -5
  36. data/test/unit/bundle_file_properties_test.rb +32 -8
  37. data/test/unit/bundle_file_writing_test.rb +10 -11
  38. data/test/unit/development_file_collection_properties_test.rb +47 -15
  39. data/test/unit/environment_test.rb +0 -13
  40. data/test/unit/files_test.rb +46 -0
  41. data/test/unit/hashes_test.rb +41 -0
  42. data/test/unit/mini_bundle_block_test.rb +56 -1
  43. data/test/unit/mini_stamp_tag_test.rb +8 -8
  44. data/test/unit/stamp_file_properties_test.rb +32 -13
  45. data/test/unit/stamp_file_writing_test.rb +1 -5
  46. metadata +38 -16
  47. data/test/unit/development_file_collection_writing_test.rb +0 -43
  48. 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
- @_instances = {}
11
+ @_files = {}
11
12
  end
12
13
 
13
- def bundle_file(site, bundle_config)
14
- asset_destination_path = "#{bundle_config.fetch('destination_path')}.#{bundle_config.fetch('type')}"
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 stamp_file(site, asset_source_path, asset_destination_path)
19
- @_instances[asset_destination_path] ||= register_stamp_file(site, asset_source_path, asset_destination_path)
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 register_bundle_file(site, bundle_config)
25
- if Environment.development?(site)
26
- DevelopmentFileCollection.new(site, bundle_config)
27
- else
28
- BundleFile.new(site, bundle_config)
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
- def register_stamp_file(site, asset_source_path, asset_destination_path)
33
- StampFile.new(site, asset_source_path, asset_destination_path, &get_stamp_file_basenamer(site))
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 get_stamp_file_basenamer(site)
37
- if Environment.development?(site)
38
- ->(base, ext, _) { base + ext }
39
- else
40
- ->(base, ext, stamper) { "#{base}-#{stamper.call}#{ext}" }
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="#{path}"#{make_attributes(attributes)}></script>}
10
+ %{<script type="text/javascript" src="#{url}"#{make_attributes(attributes)}></script>}
10
11
  when :css
11
- %{<link rel="stylesheet" href="#{path}"#{make_attributes(attributes)}>}
12
+ %{<link rel="stylesheet" href="#{url}"#{make_attributes(attributes)}>}
12
13
  else
13
- fail ArgumentError, "Unknown type for generating bundle markup: #{type}, #{path}"
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 fingerprint
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
- AssetTagMarkup.make_markup(@type, asset_destination_path, @attributes)
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 mtime
52
- @asset_paths.map { |f| File.stat(f).mtime.to_i }.max
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('Minibundle:', msg)
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, ['minibundle', 'mode'], String)
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 = traverse_keys(site.config, keys)
17
+ value = Hashes.dig(site.config, *keys)
16
18
  if value && !value.is_a?(type)
17
- fail "Invalid site configuration for key #{keys.join('.')}; expecting type #{type}"
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/asset_file_registry'
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
- file = AssetFileRegistry.bundle_file(site, bundle_config)
19
- file.add_as_static_file_to(site)
20
- file.destination_path_for_markup
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
- MiniBundleBlock.default_bundle_config.
36
- merge(environment_bundle_config(site)).
37
- merge(local_bundle_config).
38
- merge('type' => @type)
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
- { 'minifier_cmd' => Environment.minifier_command(site, @type) }
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
- @asset_source, @asset_destination = text.split(/\s+/, 3)[0, 2]
8
- if !@asset_source || @asset_source.empty?
9
- fail ArgumentError, "No asset source for ministamp tag; pass value such as '/_assets/site.css' as the first argument"
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
- if !@asset_destination || @asset_destination.empty?
12
- fail ArgumentError, "No asset destination for ministamp tag; pass value such as '/assets/site.css' as the second argument"
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 = AssetFileRegistry.stamp_file(site, @asset_source, @asset_destination)
19
- file.add_as_static_file_to(site)
20
- file.destination_path_for_markup
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