Skip to content

Instantly share code, notes, and snippets.

@amkisko
Last active October 16, 2023 15:38
Show Gist options
  • Save amkisko/bfca789c699362a8e5b7cf92b3421a20 to your computer and use it in GitHub Desktop.
Save amkisko/bfca789c699362a8e5b7cf92b3421a20 to your computer and use it in GitHub Desktop.
ViewAsset Rails assets autoloading for views by applying single convention for assets paths
<html>
<head>
<%= vite_client_tag %>
<%= view_asset_render_head %>
</head>
<body>
<%= yield %>
<%= view_asset_render_body %>
<% if Rails.env.development? %>
<%= view_asset_render_debug %>
<% end %>
</body>
</html>
# AUTHOR: Andrei Makarov (github.com/amkisko)
class ApplicationController < ActionController::Base
before_action do
ViewAsset.setup do |config|
config.path_filter { |path| Rails.root.join("app/assets", path).exist? }
config.path_prefix "views/"
config.head_tag_method "_controller.scss", :vite_stylesheet_tag
config.head_tag_method "_controller.css", :vite_stylesheet_tag
config.body_tag_method "_controller.ts", :vite_typescript_tag
config.body_tag_method "_controller.js", :vite_javascript_tag
end
end
end
# AUTHOR: Andrei Makarov (github.com/amkisko)
class CustomController < ApplicationController
before_action { ViewAsset << "custom/_additional" }
end
# AUTHOR: Andrei Makarov (github.com/amkisko)
class ViewAsset
def self.setup
Rails.logger.debug { "#{name}.#{__method__}" }
yield self
reset
self
end
def self.head_tag_method(*argv)
tag_method(:head, *argv)
end
def self.body_tag_method(*argv)
tag_method(:body, *argv)
end
def self.tag_method(location, suffix, method_name)
location = location.to_s
@tag_methods ||= {}
@tag_methods[location] ||= {}
@tag_methods[location][suffix] = method_name
end
def self.path_filter(&block)
@path_filter = block
end
path_filter { |path| Rails.root.join("app/assets", path).exist? }
def self.path_prefix(prefix)
@path_prefix = prefix
end
path_prefix "views/"
def self.add_template(template_instance)
templates << template_instance.virtual_path
Rails.logger.debug do
"#{name}.#{__method__} #{template_instance.virtual_path}"
end
end
def self.<<(path)
Rails.logger.debug { "#{name}.#{__method__} #{path}" }
templates << path
end
def self.reset
Rails.logger.debug { "#{name}.#{__method__}" }
@templates = []
@assets = {}
self
end
def self.templates
@templates ||= []
end
def self.build_assets
result = {}
templates.each do |template|
@tag_methods&.each do |location, suffixes|
suffixes.each do |suffix, method_name|
path = "#{@path_prefix}#{template}#{suffix}"
result[location] ||= {}
result[location][path] = method_name
end
end
end
result
end
def self.assets
@assets = build_assets
end
def self.render_debug(_view)
return unless Rails.env.development?
Rails.logger.debug { "#{name}.#{__method__}" }
<<-HTML.html_safe # rubocop:disable Rails/OutputSafety
<script>window.viewAssets = #{assets.to_json}; console.log('ViewAsset.assets', window.viewAssets);</script>
HTML
end
def self.render_head(view)
render(view, :head)
end
def self.render_body(view)
render(view, :body)
end
def self.render(view, location)
view_assets = assets[location.to_s]
return unless view_assets
result = ["<!-- ViewAsset #{location} begin -->"]
result +=
view_assets.filter_map do |path, method_def|
next if !@path_filter.call(path)
Rails.logger.debug do
"#{name}.#{__method__} #{location} #{path} #{method_def}"
end
if method_def.is_a?(Proc)
method_def.call(view, path)
elsif method_def.is_a?(Symbol)
view.send(method_def, path)
end
rescue StandardError
nil
end
result += ["<!-- ViewAsset #{location} end -->"]
result.join("\n").html_safe # rubocop:disable Rails/OutputSafety
end
end
module ActionView
class Template
alias render_without_view_asset render
def render(*args, **kwargs, &block)
ViewAsset.add_template(self)
render_without_view_asset(*args, **kwargs, &block)
end
end
end
module ViewAssetHelpers
def view_asset_include(*args)
args.each { |arg| ViewAsset << arg }
end
def view_asset_render_head
ViewAsset.render_head(self)
end
def view_asset_render_body
ViewAsset.render_body(self)
end
def view_asset_render_debug
ViewAsset.render_debug(self)
end
end
ActiveSupport.on_load(:action_controller) do
ActionController::Base.helper(ViewAssetHelpers)
end
ActiveSupport.on_load(:action_view) { include ViewAssetHelpers }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment