bridgetown-core 1.0.0.alpha10 → 1.0.0.alpha11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b529c24ff76bbed44607ecbef4d5dd66414bded0cb8d1640cfb6945d1ae7e9c
4
- data.tar.gz: 2522b693b676608de3546183310970d7def3e05c6bbdf5561699fc434e4b3f6d
3
+ metadata.gz: '09c13a3ec4bb37595ede90e5536eca3159812d06c482397d971d1130eab3ceab'
4
+ data.tar.gz: 48cd4b6488ba57a185e510f00e6986b8514ad33887b7710431c40d9efcae4f45
5
5
  SHA512:
6
- metadata.gz: a628d623d424930d62deebff57273a72899b6399fc6ce2114410d0b1fa31293d427df9066238646d208c2878d20686a69bce6c80db5e548bee30e1b7c0ed45e3
7
- data.tar.gz: 53b3202f0f8858de9ae4ac123750ebe29c873c989f73a1e1a63defd96ebc20c2e7e935c3f88e2c00685fff7e4eec33190b892788331181bf5e2780869b1684dc
6
+ metadata.gz: '008f32b43e009a8349bc69fe01277c9a7ebdef2c7062d8c0dc331826d0eac394491a75b48892629d5a835eeef12e435c573d4c811ec33441555ec820870157d6'
7
+ data.tar.gz: b6cf7933d50093c1332b1b39f5764768a3d8cdc3ff91b115e3d31790afa350826815f30e7d3e8c3891ddffea236f40b8e5b52268ec8eb019b4e20f319f1171ef
@@ -297,12 +297,6 @@ module Bridgetown
297
297
  resources << resource if site.config.unpublished || resource.published?
298
298
  end
299
299
 
300
- private
301
-
302
- def container
303
- @container ||= site.config["collections_dir"]
304
- end
305
-
306
300
  def sort_resources!
307
301
  if metadata["sort_by"].is_a?(String)
308
302
  sort_resources_by_key!
@@ -312,6 +306,12 @@ module Bridgetown
312
306
  resources.reverse! if metadata.sort_direction == "descending"
313
307
  end
314
308
 
309
+ private
310
+
311
+ def container
312
+ @container ||= site.config["collections_dir"]
313
+ end
314
+
315
315
  # A custom sort function based on Schwartzian transform
316
316
  # Refer https://byparker.com/blog/2017/schwartzian-transform-faster-sorting/ for details
317
317
  def sort_resources_by_key!
@@ -18,6 +18,7 @@ module Bridgetown
18
18
  Bridgetown::Hooks.clear_reloadable_hooks
19
19
  site.plugin_manager.reload_plugin_files
20
20
  site.loaders_manager.reload_loaders
21
+ Bridgetown::Hooks.trigger :site, :post_reload, site
21
22
 
22
23
  ConsoleMethods.site_reset(site)
23
24
  end
@@ -56,5 +56,13 @@ class Bridgetown::Site
56
56
  c.new(config)
57
57
  end
58
58
  end
59
+
60
+ # Shorthand for registering a site hook via {Bridgetown::Hooks}
61
+ # @param event [Symbol] name of the event (`:pre_read`, `:post_render`, etc.)
62
+ # @yield the block will be called when the event is triggered
63
+ # @yieldparam site the site which triggered the event hook
64
+ def on(event, reloadable: false, &block)
65
+ Bridgetown::Hooks.register_one :site, event, reloadable: reloadable, &block
66
+ end
59
67
  end
60
68
  end
@@ -23,9 +23,9 @@ class Bridgetown::Site
23
23
 
24
24
  # Reset all in-memory data and content.
25
25
  #
26
-
26
+ # @param soft [Boolean] if true, persist some state and do a light refresh of layouts and data
27
27
  # @return [void]
28
- def reset(soft: false)
28
+ def reset(soft: false) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
29
29
  self.time = Time.now
30
30
  if config["time"]
31
31
  self.time = Bridgetown::Utils.parse_date(
@@ -41,12 +41,30 @@ class Bridgetown::Site
41
41
  @documents = nil
42
42
  @docs_to_write = nil
43
43
  @liquid_renderer.reset
44
- frontmatter_defaults.reset unless soft
45
44
 
46
- Bridgetown::Cache.clear_if_config_changed config unless soft
45
+ if soft
46
+ refresh_layouts_and_data
47
+ else
48
+ frontmatter_defaults.reset
49
+ Bridgetown::Cache.clear_if_config_changed config
50
+ end
51
+
47
52
  Bridgetown::Hooks.trigger :site, (soft ? :after_soft_reset : :after_reset), self
48
53
  end
49
54
 
55
+ # Read layouts and merge any new data collection contents into the site data
56
+ def refresh_layouts_and_data
57
+ reader.read_layouts
58
+
59
+ collections.data.tap do |coll|
60
+ coll.resources.clear
61
+ coll.read
62
+ coll.merge_data_resources.each do |k, v|
63
+ data[k] = v # refresh site data
64
+ end
65
+ end
66
+ end
67
+
50
68
  # Read data from disk and load it into internal memory.
51
69
  # @return [void]
52
70
  def read
@@ -32,7 +32,7 @@ class Bridgetown::Site
32
32
  @ssr_enabled = true
33
33
  end
34
34
 
35
- def ssr_setup(&block) # rubocop:disable Metrics/AbcSize
35
+ def ssr_setup
36
36
  config.serving = true
37
37
  Bridgetown::Hooks.trigger :site, :pre_read, self
38
38
  defaults_reader.tap do |d|
@@ -46,27 +46,12 @@ class Bridgetown::Site
46
46
  end
47
47
  Bridgetown::Hooks.trigger :site, :post_read, self
48
48
 
49
- hook = block&.(self) # provide additional setup hook
49
+ yield self if block_given? # provide additional setup hook
50
50
  return if Bridgetown.env.production?
51
51
 
52
- @ssr_reload_hook = hook if hook.is_a?(Proc) && hook.lambda?
53
52
  Bridgetown::Watcher.watch(self, config)
54
53
  end
55
54
 
56
- def ssr_reload
57
- reset soft: true
58
- reader.read_layouts
59
-
60
- collections.data.tap do |coll|
61
- coll.resources.clear
62
- coll.read
63
- coll.merge_data_resources.each do |k, v|
64
- data[k] = v
65
- end
66
- end
67
- @ssr_reload_hook.() if @ssr_reload_hook.is_a?(Proc)
68
- end
69
-
70
55
  def disable_ssr
71
56
  Bridgetown.logger.info "SSR:", "now disabled."
72
57
  @ssr_enabled = false
@@ -82,7 +82,7 @@ create_file "plugins/test_output.rb" do
82
82
  <<~RUBY
83
83
  module TestOutput
84
84
  unless Bridgetown.env.development?
85
- Bridgetown::Hooks.register :site, :post_write do
85
+ Bridgetown::Hooks.register_one :site, :post_write do
86
86
  # Load test suite to run on exit
87
87
  require "nokogiri"
88
88
  Dir["test/**/*.rb"].each { |file| require_relative("../\#{file}") }
@@ -5,6 +5,9 @@ services:
5
5
  buildCommand: bin/bridgetown deploy
6
6
  staticPublishPath: ./output
7
7
  pullRequestPreviewsEnabled: true
8
+ envVars:
9
+ - key: BRIDGETOWN_ENV
10
+ value: production
8
11
  headers:
9
12
  - path: /*
10
13
  name: X-Frame-Options
@@ -110,7 +110,7 @@ module Bridgetown
110
110
  # underlying data hashes and performs a set union to ensure a list
111
111
  # of unique keys for the Drop.
112
112
  #
113
- # Returns an Array of unique keys for content for the Drop.
113
+ # @return [Array<String>]
114
114
  def keys
115
115
  (content_methods |
116
116
  mutations.keys |
@@ -6,7 +6,7 @@ module Bridgetown
6
6
  extend Forwardable
7
7
 
8
8
  NESTED_OBJECT_FIELD_BLACKLIST = %w(
9
- content output excerpt next previous
9
+ content output excerpt next previous next_resource previous_resource
10
10
  ).freeze
11
11
 
12
12
  mutable false
@@ -44,13 +44,15 @@ module Bridgetown
44
44
  cmp
45
45
  end
46
46
 
47
- def previous
48
- @previous ||= @obj.previous_resource.to_liquid
47
+ def next_resource
48
+ @next ||= @obj.next_resource.to_liquid
49
49
  end
50
+ alias_method :next, :next_resource
50
51
 
51
- def next
52
- @next ||= @obj.next_resource.to_liquid
52
+ def previous_resource
53
+ @previous ||= @obj.previous_resource.to_liquid
53
54
  end
55
+ alias_method :previous, :previous_resource
54
56
 
55
57
  # Generate a Hash for use in generating JSON.
56
58
  # This is useful if fields need to be cleared before the JSON can generate.
@@ -79,6 +81,27 @@ module Bridgetown
79
81
  result[key] = doc[key] unless NESTED_OBJECT_FIELD_BLACKLIST.include?(key)
80
82
  end
81
83
  end
84
+
85
+ # Generates a list of keys with user content as their values.
86
+ # This gathers up the Drop methods and keys of the mutations and
87
+ # underlying data hashes and performs a set union to ensure a list
88
+ # of unique keys for the Drop.
89
+ #
90
+ # @return [Array<String>]
91
+ def keys
92
+ keys_to_remove = %w[next_resource previous_resource]
93
+ (content_methods |
94
+ mutations.keys |
95
+ fallback_data.keys).flatten.reject do |key|
96
+ keys_to_remove.include?(key)
97
+ end
98
+ end
99
+
100
+ # Inspect the drop's keys and values through a JSON representation
101
+ # of its keys and values.
102
+ def inspect
103
+ JSON.pretty_generate hash_for_json
104
+ end
82
105
  end
83
106
  end
84
107
  end
@@ -1,21 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Handles Generated Pages
4
- Bridgetown::Hooks.register :generated_pages, :post_init, reloadable: false do |page|
4
+ Bridgetown::Hooks.register_one :generated_pages, :post_init, reloadable: false do |page|
5
5
  if page.class != Bridgetown::PrototypePage && page.data["prototype"].is_a?(Hash)
6
6
  Bridgetown::PrototypeGenerator.add_matching_template(page)
7
7
  end
8
8
  end
9
9
 
10
10
  # Handles Resources
11
- Bridgetown::Hooks.register :resources, :post_read, reloadable: false do |resource|
11
+ Bridgetown::Hooks.register_one :resources, :post_read, reloadable: false do |resource|
12
12
  if resource.data["prototype"].is_a?(Hash)
13
13
  Bridgetown::PrototypeGenerator.add_matching_template(resource)
14
14
  end
15
15
  end
16
16
 
17
17
  # Ensure sites clear out templates before rebuild
18
- Bridgetown::Hooks.register :site, :after_reset, reloadable: false do |_site|
18
+ Bridgetown::Hooks.register_one :site, :after_reset, reloadable: false do |_site|
19
19
  Bridgetown::PrototypeGenerator.matching_templates.clear
20
20
  end
21
21
 
@@ -17,34 +17,59 @@ module Bridgetown
17
17
 
18
18
  DEFAULT_PRIORITY = 20
19
19
 
20
- # compatibility layer for octopress-hooks users
21
20
  PRIORITY_MAP = {
22
21
  low: 10,
23
22
  normal: 20,
24
23
  high: 30,
25
24
  }.freeze
26
25
 
27
- # initial empty hooks
28
26
  @registry = {}
29
27
 
30
28
  NotAvailable = Class.new(RuntimeError)
31
29
  Uncallable = Class.new(RuntimeError)
32
30
 
33
- # Ensure the priority is a Fixnum
34
31
  def self.priority_value(priority)
35
32
  return priority if priority.is_a?(Integer)
36
33
 
37
34
  PRIORITY_MAP[priority] || DEFAULT_PRIORITY
38
35
  end
39
36
 
40
- # register hook(s) to be called later
37
+ # Sort registered hooks according to priority and load order
38
+ #
39
+ # @param hooks [Array<HookRegistration>]
40
+ def self.prioritized_hooks(hooks)
41
+ grouped_hooks = hooks.group_by(&:priority)
42
+ grouped_hooks.keys.sort.reverse.map { |priority| grouped_hooks[priority] }.flatten
43
+ end
44
+
45
+ # Register one or more hooks which may be triggered later for a particular event
46
+ #
47
+ # @param owners [Symbol, Array<Symbol>] name of the owner (`:site`, `:resource`, etc.)
48
+ # @param event [Symbol] name of the event (`:pre_read`, `:post_render`, etc.)
49
+ # @param priority [Integer, Symbol] either `:low`, `:normal`, or `:high`, or an integer.
50
+ # Default is normal (20)
51
+ # @param reloadable [Boolean] whether the hook should be removed prior to a site reload.
52
+ # Default is true.
53
+ # @yield the block will be called when the event is triggered. Typically it receives at
54
+ # least one argument.
55
+ # @yieldparam obj the object which triggered the event hook
41
56
  def self.register(owners, event, priority: DEFAULT_PRIORITY, reloadable: true, &block)
42
57
  Array(owners).each do |owner|
43
58
  register_one(owner, event, priority: priority, reloadable: reloadable, &block)
44
59
  end
45
60
  end
46
61
 
47
- # register a single hook to be called later
62
+ # Register a hook which may be triggered later for a particular event
63
+ #
64
+ # @param owner [Symbol] name of the owner (`:site`, `:resource`, etc.)
65
+ # @param event [Symbol] name of the event (`:pre_read`, `:post_render`, etc.)
66
+ # @param priority [Integer, Symbol] either `:low`, `:normal`, or `:high`, or an integer.
67
+ # Default is normal (20)
68
+ # @param reloadable [Boolean] whether the hook should be removed prior to a site reload.
69
+ # Default is true.
70
+ # @yield the block will be called when the event is triggered. Typically it receives at
71
+ # least one argument.
72
+ # @yieldparam obj the object which triggered the event hook
48
73
  def self.register_one(owner, event, priority: DEFAULT_PRIORITY, reloadable: true, &block)
49
74
  @registry[owner] ||= []
50
75
 
@@ -68,10 +93,28 @@ module Bridgetown
68
93
  block
69
94
  end
70
95
 
71
- def self.remove_hook(owner, _event, block)
96
+ # Delete a previously-registered hook
97
+ #
98
+ # @param owners [Symbol] name of the owner (`:site`, `:resource`, etc.)
99
+ # @param block [Proc] the exact block used originally to register the hook
100
+ def self.remove_hook(owner, block)
72
101
  @registry[owner].delete_if { |item| item.block == block }
73
102
  end
74
103
 
104
+ # Clear all hooks marked as reloadable from the registry
105
+ def self.clear_reloadable_hooks
106
+ Bridgetown.logger.debug("Clearing reloadable hooks")
107
+
108
+ @registry.each_value do |hooks|
109
+ hooks.delete_if(&:reloadable)
110
+ end
111
+ end
112
+
113
+ # Trigger all registered hooks for a particular owner and event.
114
+ # Any arguments after the initial two will be directly passed along to the hooks.
115
+ #
116
+ # @param owner [Symbol] name of the owner (`:site`, `:resource`, etc.)
117
+ # @param event [Symbol] name of the event (`:pre_read`, `:post_render`, etc.)
75
118
  def self.trigger(owner, event, *args) # rubocop:disable Metrics/CyclomaticComplexity
76
119
  # proceed only if there are hooks to call
77
120
  hooks = @registry[owner]&.select { |item| item.event == event }
@@ -79,25 +122,13 @@ module Bridgetown
79
122
 
80
123
  prioritized_hooks(hooks).each do |hook|
81
124
  if ENV["BRIDGETOWN_LOG_LEVEL"] == "debug"
82
- hook_info = args[0].respond_to?(:url) ? args[0].relative_path : hook.block
125
+ hook_info = args[0].respond_to?(:relative_path) ? args[0].relative_path : hook.block
83
126
  Bridgetown.logger.debug("Triggering hook:", "#{owner}:#{event} for #{hook_info}")
84
127
  end
85
128
  hook.block.call(*args)
86
129
  end
87
- end
88
-
89
- def self.prioritized_hooks(hooks)
90
- # sort hooks according to priority and load order
91
- grouped_hooks = hooks.group_by(&:priority)
92
- grouped_hooks.keys.sort.reverse.map { |priority| grouped_hooks[priority] }.flatten
93
- end
94
130
 
95
- def self.clear_reloadable_hooks
96
- Bridgetown.logger.debug("Clearing reloadable hooks")
97
-
98
- @registry.each_value do |hooks|
99
- hooks.delete_if(&:reloadable)
100
- end
131
+ true
101
132
  end
102
133
  end
103
134
  end
@@ -69,8 +69,23 @@ module Bridgetown
69
69
  attributes[:_origin_]
70
70
  end
71
71
 
72
+ def origin=(new_origin)
73
+ attributes[:_id_] = new_origin.id
74
+ attributes[:_origin_] = new_origin
75
+ end
76
+
72
77
  def persisted?
73
- id && origin.exists?
78
+ (id && origin&.exists?) == true
79
+ end
80
+
81
+ def save
82
+ unless origin.respond_to?(:write)
83
+ raise "`#{origin.class}' doesn't allow writing of model objects"
84
+ end
85
+
86
+ run_callbacks :save do
87
+ origin.write(self)
88
+ end
74
89
  end
75
90
 
76
91
  # @return [Bridgetown::Resource::Base]
@@ -100,11 +115,19 @@ module Bridgetown
100
115
  attributes[:_collection_]
101
116
  end
102
117
 
118
+ def collection=(new_collection)
119
+ attributes[:_collection_] = new_collection
120
+ end
121
+
103
122
  # @return [String]
104
123
  def content
105
124
  attributes[:_content_]
106
125
  end
107
126
 
127
+ def content=(new_content)
128
+ attributes[:_content_] = new_content
129
+ end
130
+
108
131
  def attributes
109
132
  @attributes ||= HashWithDotAccess::Hash.new
110
133
  end
@@ -25,6 +25,18 @@ module Bridgetown
25
25
  def data_file_extensions
26
26
  %w(.yaml .yml .json .csv .tsv .rb).freeze
27
27
  end
28
+
29
+ # Initializes a new repo object using a collection and a relative source path.
30
+ # You'll need to use this when you want to create and save a model to the source.
31
+ #
32
+ # @param collection [Bridgetown::Collection, String, Symbol] either a collection
33
+ # label or Collection object
34
+ # @param relative_path [Pathname, String] the source path of the file to save
35
+ def new_with_collection_path(collection, relative_path)
36
+ collection = collection.label if collection.is_a?(Bridgetown::Collection)
37
+
38
+ new("repo://#{collection}.collection/#{relative_path}")
39
+ end
28
40
  end
29
41
 
30
42
  def read
@@ -46,6 +58,23 @@ module Bridgetown
46
58
  @data
47
59
  end
48
60
 
61
+ def write(model)
62
+ if File.exist?(original_path) && !Bridgetown::Utils.has_yaml_header?(original_path)
63
+ raise Bridgetown::Errors::InvalidYAMLFrontMatterError,
64
+ "Only existing files containing YAML front matter can be overwritten by the model"
65
+ end
66
+
67
+ contents = "#{front_matter_to_yaml(model)}---\n\n#{model.content}"
68
+
69
+ # Create folders if necessary
70
+ dir = File.dirname(original_path)
71
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
72
+
73
+ File.write(original_path, contents)
74
+
75
+ true
76
+ end
77
+
49
78
  def url
50
79
  @url ||= URI.parse(id)
51
80
  end
@@ -121,6 +150,25 @@ module Bridgetown
121
150
  raise error
122
151
  end
123
152
  end
153
+
154
+ def front_matter_to_yaml(model)
155
+ data = model.data_attributes.to_h
156
+ data = data.deep_merge(data) do |_, _, v|
157
+ case v
158
+ when DateTime
159
+ v.to_time
160
+ when Symbol
161
+ v.to_s
162
+ else
163
+ v
164
+ end
165
+ end
166
+ data.each do |k, v|
167
+ data.delete(k) if v.nil?
168
+ end
169
+
170
+ data.to_yaml
171
+ end
124
172
  end
125
173
  end
126
174
  end
@@ -23,6 +23,7 @@ module Bridgetown
23
23
  plugin :json
24
24
  plugin :json_parser
25
25
  plugin :cookies
26
+ plugin :streaming
26
27
  plugin :public, root: Bridgetown::Current.preloaded_configuration.destination
27
28
  plugin :not_found do
28
29
  output_folder = Bridgetown::Current.preloaded_configuration.destination
@@ -39,7 +40,7 @@ module Bridgetown
39
40
  "500 Internal Server Error"
40
41
  end
41
42
 
42
- def _roda_run_main_route(r) # rubocop:disable Naming/MethodParameterName
43
+ before do
43
44
  if self.class.opts[:bridgetown_site]
44
45
  # The site had previously been initialized via the bridgetown_ssr plugin
45
46
  Bridgetown::Current.site ||= self.class.opts[:bridgetown_site]
@@ -47,14 +48,12 @@ module Bridgetown
47
48
  Bridgetown::Current.preloaded_configuration ||=
48
49
  self.class.opts[:bridgetown_preloaded_config]
49
50
 
50
- r.public
51
+ request.public
51
52
 
52
- r.root do
53
+ request.root do
53
54
  output_folder = Bridgetown::Current.preloaded_configuration.destination
54
55
  File.read(File.join(output_folder, "index.html"))
55
56
  end
56
-
57
- super
58
57
  end
59
58
 
60
59
  # Helper shorthand for Bridgetown::Current.site
@@ -2,6 +2,12 @@
2
2
 
3
3
  module Bridgetown
4
4
  module Rack
5
+ @interrupted = false
6
+
7
+ class << self
8
+ attr_accessor :interrupted
9
+ end
10
+
5
11
  class Routes
6
12
  class << self
7
13
  attr_accessor :tracked_subclasses, :router_block
@@ -37,7 +43,7 @@ module Bridgetown
37
43
  def start!(roda_app)
38
44
  if Bridgetown.env.development? &&
39
45
  !Bridgetown::Current.preloaded_configuration.skip_live_reload
40
- setup_live_reload roda_app.request
46
+ setup_live_reload roda_app
41
47
  end
42
48
 
43
49
  Bridgetown::Rack::Routes.tracked_subclasses&.each_value do |klass|
@@ -51,15 +57,31 @@ module Bridgetown
51
57
  nil
52
58
  end
53
59
 
54
- def setup_live_reload(request)
55
- request.get "_bridgetown/live_reload" do
56
- {
57
- last_mod: File.stat(
58
- File.join(Bridgetown::Current.preloaded_configuration.destination, "index.html")
59
- ).mtime.to_i,
60
- }
61
- rescue StandardError => e
62
- { last_mod: 0, error: e.message }
60
+ def setup_live_reload(app) # rubocop:disable Metrics/AbcSize
61
+ sleep_interval = 0.2
62
+ file_to_check = File.join(app.class.opts[:bridgetown_preloaded_config].destination,
63
+ "index.html")
64
+
65
+ app.request.get "_bridgetown/live_reload" do
66
+ app.response["Content-Type"] = "text/event-stream"
67
+
68
+ @_mod = File.exist?(file_to_check) ? File.stat(file_to_check).mtime.to_i : 0
69
+ app.stream async: true do |out|
70
+ # 5 second intervals so Puma's threads aren't all exausted
71
+ (5 / sleep_interval).to_i.times do
72
+ break if Bridgetown::Rack.interrupted
73
+
74
+ new_mod = File.exist?(file_to_check) ? File.stat(file_to_check).mtime.to_i : 0
75
+ if @_mod < new_mod
76
+ out << "data: reloaded!\n\n"
77
+ break
78
+ else
79
+ out << "data: #{new_mod}\n\n"
80
+ end
81
+
82
+ sleep sleep_interval
83
+ end
84
+ end
63
85
  end
64
86
  end
65
87
  end
@@ -86,3 +108,15 @@ module Bridgetown
86
108
  end
87
109
  end
88
110
  end
111
+
112
+ if Bridgetown.env.development? &&
113
+ !Bridgetown::Current.preloaded_configuration.skip_live_reload
114
+ Puma::Launcher.class_eval do
115
+ alias_method :_old_stop, :stop
116
+ def stop
117
+ Bridgetown::Rack.interrupted = true
118
+
119
+ _old_stop
120
+ end
121
+ end
122
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "roda/plugins/public"
4
4
 
5
+ # NOTE: once this upstreamed PR is merged in we can remove this file.
6
+ # https://github.com/jeremyevans/roda/pull/215
5
7
  Roda::RodaPlugins::Public::RequestMethods.module_eval do
6
8
  SPLIT = Regexp.union(*[File::SEPARATOR, File::ALT_SEPARATOR].compact) # rubocop:disable Lint/ConstantDefinitionInBlock
7
9
  def public_path_segments(path) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
@@ -284,12 +284,14 @@ module Bridgetown
284
284
  collection.resources[pos + 1] if pos && pos < collection.resources.length - 1
285
285
  end
286
286
  alias_method :next_doc, :next_resource
287
+ alias_method :next, :next_resource
287
288
 
288
289
  def previous_resource
289
290
  pos = collection.resources.index { |item| item.equal?(self) }
290
291
  collection.resources[pos - 1] if pos&.positive?
291
292
  end
292
293
  alias_method :previous_doc, :previous_resource
294
+ alias_method :previous, :previous_resource
293
295
 
294
296
  private
295
297
 
@@ -401,30 +401,34 @@ module Bridgetown
401
401
  return "" unless Bridgetown.env.development? && !site.config.skip_live_reload
402
402
 
403
403
  code = <<~JAVASCRIPT
404
- let first_mod = 0
405
- let connection_errors = 0
406
- const checkForReload = () => {
407
- fetch("/_bridgetown/live_reload").then(response => {
408
- if (response.ok) {
409
- response.json().then(data => {
410
- const last_mod = data.last_mod
411
- if (first_mod === 0) {
412
- first_mod = last_mod
413
- } else if (last_mod > first_mod) {
414
- location.reload()
415
- }
416
- setTimeout(() => checkForReload(), 700)
417
- })
404
+ let lastmod = 0
405
+ function startReloadConnection() {
406
+ const evtSource = new EventSource("/_bridgetown/live_reload")
407
+ evtSource.onmessage = event => {
408
+ if (event.data == "reloaded!") {
409
+ location.reload()
418
410
  } else {
419
- if (connection_errors < 20) setTimeout(() => checkForReload(), 6000)
420
- connection_errors++
411
+ const newmod = Number(event.data)
412
+ if (lastmod > 0 && newmod > 0 && lastmod < newmod) {
413
+ location.reload()
414
+ } else {
415
+ lastmod = newmod
416
+ }
421
417
  }
422
- }).catch((err) => {
423
- if (connection_errors < 20) setTimeout(() => checkForReload(), 6000)
424
- connection_errors++
425
- })
418
+ }
419
+ evtSource.onerror = event => {
420
+ if (evtSource.readyState === 2) {
421
+ // reconnect with new object
422
+ evtSource.close()
423
+ console.warn("Live reload: attempting to reconnect in 3 seconds...")
424
+
425
+ setTimeout(() => startReloadConnection(), 3000)
426
+ }
427
+ }
426
428
  }
427
- checkForReload()
429
+ setTimeout(() => {
430
+ startReloadConnection()
431
+ }, 500)
428
432
  JAVASCRIPT
429
433
 
430
434
  %(<script type="module">#{code}</script>).html_safe
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bridgetown
4
- VERSION = "1.0.0.alpha10"
4
+ VERSION = "1.0.0.alpha11"
5
5
  CODE_NAME = "Pearl"
6
6
  end
@@ -4,71 +4,102 @@ module Bridgetown
4
4
  module Watcher
5
5
  extend self
6
6
 
7
- # Public: Continuously watch for file changes and rebuild the site
8
- # whenever a change is detected.
7
+ # Continuously watch for file changes and rebuild the site whenever a change is detected.
9
8
  #
10
- # site - The current site instance
11
- # options - A Hash containing the site configuration
12
- #
13
- # Returns nothing.
9
+ # @param site [Bridgetown::Site] the current site instance
10
+ # @param options [Bridgetown::Configuration] the site configuration
14
11
  def watch(site, options)
15
12
  ENV["LISTEN_GEM_DEBUGGING"] ||= "1" if options["verbose"]
16
13
 
17
- listener = build_listener(site, options)
18
- listener.start
14
+ listen(site, options)
19
15
 
20
- Bridgetown.logger.info "Auto-regeneration:", "enabled." unless options[:using_puma]
16
+ Bridgetown.logger.info "Watcher:", "enabled." unless options[:using_puma]
21
17
 
22
18
  unless options[:serving]
23
19
  trap("INT") do
24
- listener.stop
25
- Bridgetown.logger.info "", "Halting auto-regeneration."
26
20
  exit 0
27
21
  end
28
22
 
29
23
  sleep_forever
30
24
  end
31
25
  rescue ThreadError
32
- # You pressed Ctrl-C, oh my!
26
+ # NOTE: not sure if this is needed any longer
33
27
  end
34
28
 
35
- private
29
+ # Return a list of load paths which should be watched for changes
30
+ #
31
+ # @param (see #watch)
32
+ def load_paths_to_watch(site, options)
33
+ site.plugin_manager.plugins_path.select { |path| Dir.exist?(path) }
34
+ .then do |paths|
35
+ (paths + options.autoload_paths).uniq
36
+ end
37
+ end
36
38
 
37
- def build_listener(site, options)
39
+ # Start a listener to watch for changes and call {#reload_site}
40
+ #
41
+ # @param (see #watch)
42
+ def listen(site, options)
38
43
  webpack_path = site.in_root_dir(".bridgetown-webpack")
39
44
  FileUtils.mkdir(webpack_path) unless Dir.exist?(webpack_path)
40
- plugin_paths_to_watch = site.plugin_manager.plugins_path.select do |path|
41
- Dir.exist?(path)
42
- end
43
-
44
- paths_to_watch = (plugin_paths_to_watch + options.autoload_paths).uniq
45
-
46
45
  Listen.to(
47
46
  options["source"],
48
47
  webpack_path,
49
- *paths_to_watch,
48
+ *load_paths_to_watch(site, options),
50
49
  ignore: listen_ignore_paths(options),
51
- force_polling: options["force_polling"],
52
- &listen_handler(site, options)
53
- )
54
- end
55
-
56
- def listen_handler(site, options)
57
- proc do |modified, added, removed|
58
- t = Time.now
50
+ force_polling: options["force_polling"]
51
+ ) do |modified, added, removed|
59
52
  c = modified + added + removed
60
53
  n = c.length
61
54
 
62
55
  unless site.ssr?
63
- Bridgetown.logger.info "Regenerating…"
64
- Bridgetown.logger.info "", "#{n} file(s) changed at #{t.strftime("%Y-%m-%d %H:%M:%S")}"
65
- c.each { |path| Bridgetown.logger.info "", path["#{site.root_dir}/".length..] }
56
+ Bridgetown.logger.info(
57
+ "Reloading…",
58
+ "#{n} file#{"s" if c.length > 1} changed at #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}"
59
+ )
60
+ c.each { |path| Bridgetown.logger.info "", "- #{path["#{site.root_dir}/".length..]}" }
66
61
  end
67
62
 
68
- process(site, t, options)
63
+ reload_site(site, options)
64
+ end.start
65
+ end
66
+
67
+ # Reload the site including plugins and Zeitwerk autoloaders and process it (unless SSR)
68
+ #
69
+ # @param (see #watch)
70
+ def reload_site(site, options) # rubocop:todo Metrics/MethodLength
71
+ begin
72
+ time = Time.now
73
+ I18n.reload! # make sure any locale files get read again
74
+ Bridgetown::Current.site = site # needed in SSR mode apparently
75
+ Bridgetown::Hooks.trigger :site, :pre_reload, site
76
+ Bridgetown::Hooks.clear_reloadable_hooks
77
+ site.plugin_manager.reload_plugin_files
78
+ site.loaders_manager.reload_loaders
79
+ Bridgetown::Hooks.trigger :site, :post_reload, site
80
+
81
+ if site.ssr?
82
+ site.reset(soft: true)
83
+ return
84
+ end
85
+
86
+ site.process
87
+ Bridgetown.logger.info "Done! 🎉", "#{"Completed".green} in less than" \
88
+ " #{(Time.now - time).ceil(2)} seconds."
89
+ rescue Exception => e
90
+ Bridgetown.logger.error "Error:", e.message
91
+
92
+ if options[:trace]
93
+ Bridgetown.logger.info e.backtrace.join("\n")
94
+ else
95
+ Bridgetown.logger.warn "Backtrace:", "Use the --trace option for more information."
96
+ end
69
97
  end
98
+ Bridgetown.logger.info ""
70
99
  end
71
100
 
101
+ private
102
+
72
103
  def normalize_encoding(obj, desired_encoding)
73
104
  case obj
74
105
  when Array
@@ -126,32 +157,5 @@ module Bridgetown
126
157
  def sleep_forever
127
158
  loop { sleep 1000 }
128
159
  end
129
-
130
- # @param site [Bridgetown::Site]
131
- def process(site, time, options)
132
- begin
133
- I18n.reload! # make sure any locale files get read again
134
- Bridgetown::Current.site = site # needed in SSR mode apparently
135
- Bridgetown::Hooks.trigger :site, :pre_reload, site
136
- Bridgetown::Hooks.clear_reloadable_hooks
137
- site.plugin_manager.reload_plugin_files
138
- site.loaders_manager.reload_loaders
139
-
140
- return site.ssr_reload if site.ssr?
141
-
142
- site.process
143
- Bridgetown.logger.info "Done! 🎉", "#{"Completed".green} in less than" \
144
- " #{(Time.now - time).ceil(2)} seconds."
145
- rescue Exception => e
146
- Bridgetown.logger.error "Error:", e.message
147
-
148
- if options[:trace]
149
- Bridgetown.logger.info e.backtrace.join("\n")
150
- else
151
- Bridgetown.logger.warn "Backtrace:", "Use the --trace option for more information."
152
- end
153
- end
154
- Bridgetown.logger.info ""
155
- end
156
160
  end
157
161
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bridgetown-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.alpha10
4
+ version: 1.0.0.alpha11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bridgetown Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-16 00:00:00.000000000 Z
11
+ date: 2021-11-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel