diff --git a/lib/volt/page/tasks.rb b/lib/volt/page/tasks.rb index c8c4fad7..e4fcdb53 100644 --- a/lib/volt/page/tasks.rb +++ b/lib/volt/page/tasks.rb @@ -1,4 +1,5 @@ require 'volt/utils/ejson' +require 'securerandom' module Volt # The tasks class provides an interface to call tasks on @@ -36,6 +37,8 @@ def received_message(name, promise_id, *args) response(promise_id, *args) when 'reload' reload + when 'refresh_css' + refresh_css(*args) end end @@ -82,5 +85,40 @@ def reload Volt.current_app.page._reloading = true `window.location.reload(false);` end + + # refresh changed css + def refresh_css(changed_files) + changed_files[:removed].each do |path| + + # Remove link to css from head + ` + var el = window.document.querySelector("link[href^='" + path + "']"); + el.parentElement.removeChild(el); + ` + end + changed_files[:modified].each do |path| + + # We fetch the link + # We then invalidate the cached css by appending a random query to the href which forces the CSS to be reloaded + ` + var el = window.document.querySelector("link[href^='" + path + "']") + el.setAttribute('href', el.getAttribute('href') + '?v=' + #{SecureRandom.uuid[0..7]}) + ` + end + + changed_files[:added].each do |path| + + # Inject a new link to the css into the head + ` + link=document.createElement('link'); + link.href=path; + link.rel='stylesheet'; + link.type='text/css'; + link.media='all'; + + document.getElementsByTagName('head')[0].appendChild(link); + ` + end + end end end diff --git a/lib/volt/server/forking_server.rb b/lib/volt/server/forking_server.rb index 838adf6a..f28683e8 100644 --- a/lib/volt/server/forking_server.rb +++ b/lib/volt/server/forking_server.rb @@ -208,38 +208,58 @@ def call(env) end end + def reload(args) + changed_files = args[:modified] + args[:added] + args[:removed] - def reload(changed_files) - # only reload the server code if a non-view file was changed - server_code_changed = changed_files.any? { |path| File.extname(path) == '.rb' } + # if all changed files are just css files then we will just refresh the css links by invalidating the cache + if changed_files.all? { |path| File.extname(path).match(/css|scss|sass/) } + msg = 'CSS updated, refreshing client...' - msg = 'file changed, reloading' - msg << ' server and' if server_code_changed - msg << ' client...' + Volt.logger.log_with_color(msg, :light_blue) - Volt.logger.log_with_color(msg, :light_blue) + # sub out the Volt root and any preprocessor extensions from the file paths + css_file_paths = args.update(args) do |key,value| + value.map { |path| path.gsub(Volt.root, '').gsub(/.scss|.sass/,'') } + end + begin + SocketConnectionHandler.send_message_all(nil, 'refresh_css', nil, css_file_paths) + rescue => e + Volt.logger.error('CSS refresh dispatch error: ') + Volt.logger.error(e) + end + else + # only reload the server code if a non-view file was changed + server_code_changed = changed_files.any? { |path| File.extname(path) == '.rb' } - # Figure out if any views or routes were changed: - # TODO: Might want to only check for /config/ under the CWD - if changed_files.any? {|path| path =~ /\/config\// } - update_mod_time - sync_mod_time - end + msg = 'file changed, reloading' + msg << ' server and' if server_code_changed + msg << ' client...' - begin - SocketConnectionHandler.send_message_all(nil, 'reload') - rescue => e - Volt.logger.error('Reload dispatch error: ') - Volt.logger.error(e) - end + Volt.logger.log_with_color(msg, :light_blue) - if server_code_changed - @child_lock.with_write_lock do - stop_child - start_child + + # Figure out if any views or routes were changed: + # TODO: Might want to only check for /config/ under the CWD + if changed_files.any? {|path| path =~ /\/config\// } + update_mod_time sync_mod_time end + + begin + SocketConnectionHandler.send_message_all(nil, 'reload') + rescue => e + Volt.logger.error('Reload dispatch error: ') + Volt.logger.error(e) + end + + if server_code_changed + @child_lock.with_write_lock do + stop_child + start_child + sync_mod_time + end + end end end @@ -267,7 +287,7 @@ def start_change_listener @listener = Listen.to("#{@server.app_path}/", options) do |modified, added, removed| Thread.new do # Run the reload in a new thread - reload(modified + added + removed) + reload(modified: modified, added: added, removed: removed) end end @listener.start diff --git a/lib/volt/server/socket_connection_handler.rb b/lib/volt/server/socket_connection_handler.rb index 63e49016..1bf83615 100644 --- a/lib/volt/server/socket_connection_handler.rb +++ b/lib/volt/server/socket_connection_handler.rb @@ -53,6 +53,7 @@ def self.channels end # Sends a message to all, optionally skipping a users channel + # Expects promise_id after message name, can be nil def self.send_message_all(skip_channel = nil, *args) return unless defined?(@@channels) @@channels.each do |channel|