frails 0.1.0 → 0.3.0

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.
@@ -1,21 +1,88 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Frails::Helper
4
+ def render(options = {}, locals = {}, &block)
5
+ case options
6
+ when Hash
7
+ if Rails::VERSION::MAJOR >= 6
8
+ in_rendering_context(options) do |_renderer|
9
+ if block_given?
10
+ # MONKEY PATCH! simply renders the component with the given block.
11
+ return view_renderer.render_component(self, options, &block) if options.key?(:component)
12
+
13
+ view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
14
+ else
15
+ view_renderer.render(self, options)
16
+ end
17
+ end
18
+ else
19
+ if block_given?
20
+ # MONKEY PATCH! simply renders the component with the given block.
21
+ return view_renderer.render_component(self, options, &block) if options.key?(:component)
22
+
23
+ view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
24
+ else
25
+ view_renderer.render(self, options)
26
+ end
27
+ end
28
+ else
29
+ view_renderer.render_partial(self, partial: options, locals: locals, &block)
30
+ end
31
+ end
32
+
4
33
  def javascript_pack_tag(*names, **options)
5
- javascript_include_tag(*sources_from_manifest_entries(names, :javascript), **options)
34
+ return if Rails.env.test?
35
+
36
+ @included_javascripts ||= []
37
+
38
+ soft_lookup = options.delete(:soft_lookup) { false }
39
+ sources = sources_from_manifest_entries(names, :javascript, manifest: options.delete(:manifest),
40
+ soft_lookup: soft_lookup)
41
+
42
+ # Make sure that we don't include an asset that has already been included.
43
+ sources -= @included_javascripts
44
+ sources.compact!
45
+
46
+ # Concatenate the sources to be included to those already included.
47
+ @included_javascripts.concat sources
48
+
49
+ sources.empty? ? nil : javascript_include_tag(*sources, **options)
6
50
  end
7
51
 
8
52
  def stylesheet_pack_tag(*names, **options)
9
- stylesheet_link_tag(*sources_from_manifest_entries(names, :stylesheet), **options)
53
+ return if Rails.env.test?
54
+
55
+ soft_lookup = options.delete(:soft_lookup) { false }
56
+ sources = sources_from_manifest_entries(names, :stylesheet, manifest: options.delete(:manifest),
57
+ soft_lookup: soft_lookup)
58
+
59
+ sources.compact!
60
+ stylesheet_link_tag(*sources, **options)
61
+ end
62
+
63
+ def image_pack_tag(name, **options)
64
+ return if Rails.env.test?
65
+
66
+ image_tag(pack_path("images/#{name}", manifest: options.delete(:manifest)), **options)
67
+ end
68
+
69
+ def pack_path(name, type: nil, manifest: nil)
70
+ manifest_manager[manifest].lookup! name, type: type
10
71
  end
11
72
 
12
73
  private
13
74
 
14
- def sources_from_manifest_entries(names, type)
15
- names.map { |name| frails_instance.manifest.lookup!(name, type: type) }.flatten
75
+ def sources_from_manifest_entries(names, type, manifest: nil, soft_lookup: false)
76
+ names.map do |name|
77
+ if soft_lookup
78
+ manifest_manager[manifest].lookup name, type: type
79
+ else
80
+ manifest_manager[manifest].lookup! name, type: type
81
+ end
82
+ end.flatten
16
83
  end
17
84
 
18
- def frails_instance
19
- Frails.instance
85
+ def manifest_manager
86
+ Frails.manifest
20
87
  end
21
88
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/log_subscriber'
4
+
5
+ module Frails
6
+ class LogSubscriber < ActiveSupport::LogSubscriber
7
+ VIEWS_PATTERN = %r{^app/views/}.freeze
8
+
9
+ def render_side_loaded_assets(event)
10
+ return if (asset_types = event.payload[:asset_types]).empty?
11
+
12
+ identifier_from_root = from_rails_root(event.payload[:identifier])
13
+
14
+ info do
15
+ message = +" Side loaded #{asset_types.join(',')} for #{identifier_from_root}"
16
+ message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ EMPTY = ''
23
+ def from_rails_root(string)
24
+ string = string.sub(rails_root, EMPTY)
25
+ string.sub!(VIEWS_PATTERN, EMPTY)
26
+ string
27
+ end
28
+
29
+ def rails_root
30
+ @root ||= "#{Rails.root}/"
31
+ end
32
+ end
33
+ end
34
+
35
+ Frails::LogSubscriber.attach_to :action_view
@@ -1,8 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'open-uri'
4
+
3
5
  class Frails::Manifest
6
+ class MissingManifestError < StandardError; end
4
7
  class MissingEntryError < StandardError; end
5
8
 
9
+ attr_reader :manifest_path
10
+
11
+ def initialize(path)
12
+ @manifest_path = Rails.public_path.join(Frails.public_output_path, path)
13
+
14
+ return if @manifest_path.exist?
15
+
16
+ raise Frails::Manifest::MissingManifestError, "Frails can't find manifest #{manifest_path}"
17
+ end
18
+
6
19
  def refresh
7
20
  @data = load
8
21
  end
@@ -11,7 +24,7 @@ class Frails::Manifest
11
24
  # returns nil.
12
25
  #
13
26
  # Example:
14
- # Frails.manifest.lookup('calendar.js') # => "/packs/calendar-1016838bab065ae1e122.js"
27
+ # Frails.manifest.lookup('calendar.js') # => "/assets/calendar-1016838bab065ae1e122.js"
15
28
  def lookup(name, type: nil)
16
29
  # When using SplitChunks or RuntimeChunks the manifest hash will contain an extra object called
17
30
  # "entrypoints". When the entrypoints key is not present in the manifest, or the name is not
@@ -32,12 +45,36 @@ class Frails::Manifest
32
45
  lookup(name, type: type) || handle_missing_entry(name)
33
46
  end
34
47
 
35
- def manifest_path
36
- @manifest_path ||= Rails.root.join('public', 'packs', 'manifest.json')
48
+ def read!(name, type)
49
+ sources = *lookup!(name, type: type)
50
+ sources.map do |path|
51
+ yield path, read_source(path)
52
+ end
53
+ end
54
+
55
+ def read(name, type)
56
+ sources = *lookup(name, type: type)
57
+ sources.map do |path|
58
+ yield path, read_source(path)
59
+ end
37
60
  end
38
61
 
39
62
  private
40
63
 
64
+ def read_source(path)
65
+ unless Frails.dev_server.running?
66
+ host = ActionController::Base.helpers.compute_asset_host
67
+ new_path = host && path.start_with?(host) ? path.delete_prefix(host) : path
68
+ return Rails.public_path.join(new_path.gsub(%r{^\/}, '')).read
69
+ end
70
+
71
+ begin
72
+ open("http://#{Frails.dev_server.host_with_port}#{path}").read
73
+ rescue OpenURI::HTTPError => e
74
+ handle_missing_entry path
75
+ end
76
+ end
77
+
41
78
  def load
42
79
  manifest_path.exist? ? JSON.parse(manifest_path.read) : {}
43
80
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'frails/manifest'
4
+
5
+ class Frails::ManifestManager
6
+ delegate :lookup, :lookup!, :read, :read!, to: :default_instance
7
+
8
+ def initialize
9
+ @instances = {}
10
+ end
11
+
12
+ def [](path = nil)
13
+ path ||= Frails.manifest_path
14
+ @instances[path] ||= Frails::Manifest.new(path)
15
+ end
16
+
17
+ private
18
+
19
+ def default_instance
20
+ @instances[Frails.manifest_path] ||= Frails::Manifest.new(Frails.manifest_path)
21
+ end
22
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Frails
4
+ module Monkey
5
+ module ActionView
6
+ module AbstractRenderer
7
+ def side_load_assets(view, tpl)
8
+ path = tpl.short_identifier.delete_prefix('app/').delete_suffix('.html.erb')
9
+
10
+ instrument :side_loaded_assets, identifier: tpl.identifier, asset_types: [] do |payload|
11
+ side_load_javascript path, view, payload
12
+ side_load_stylesheet path, view, payload
13
+ end
14
+
15
+ path
16
+ end
17
+
18
+ def side_load_javascript(path, view, payload)
19
+ # Render the JS - if any.
20
+ view.content_for :side_loaded_js do
21
+ view.javascript_pack_tag(path, soft_lookup: true).tap do |tag|
22
+ !tag.nil? && (payload[:asset_types] << :js)
23
+ end
24
+ end
25
+ end
26
+
27
+ def side_load_stylesheet(path, view, payload)
28
+ loaded = side_loaded_stylesheets(view)
29
+
30
+ # Don't inline the styles if already inlined.
31
+ return if loaded.include?(path)
32
+
33
+ # Render the CSS inline - if any.
34
+ Frails.manifest.read(path, :stylesheet) do |href, src|
35
+ view.content_for :side_loaded_css do
36
+ view.content_tag :style, src, { data: { href: href } }, false
37
+ end
38
+
39
+ loaded << path
40
+ payload[:asset_types] << :css
41
+ end
42
+ end
43
+
44
+ def side_loaded_stylesheets(view)
45
+ if view.instance_variable_defined?(:@side_loaded_stylesheets)
46
+ view.instance_variable_get(:@side_loaded_stylesheets)
47
+ else
48
+ view.instance_variable_set :@side_loaded_stylesheets, []
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Frails
4
+ module Monkey
5
+ module ActionView
6
+ module PartialRenderer
7
+ def render_partial(view, template)
8
+ # Side load partial assets - if any.
9
+ @asset_path = side_load_assets(view, template)
10
+
11
+ super
12
+ end
13
+
14
+ def build_rendered_template(content, template, layout = nil)
15
+ content = transform_css_modules(content).html_safe
16
+ ::ActionView::AbstractRenderer::RenderedTemplate.new content, layout, template
17
+ end
18
+
19
+ def transform_css_modules(content)
20
+ doc = Nokogiri::HTML::DocumentFragment.parse(content)
21
+
22
+ return content if (modules = doc.css('[css_module]')).empty?
23
+
24
+ modules.each do |ele|
25
+ classes = class_name_for_style(ele.delete('css_module'))
26
+ ele['class'] = (ele['class'].nil? ? classes : classes << ele['class']).join(' ')
27
+ end
28
+
29
+ doc.to_html
30
+ end
31
+
32
+ def class_name_for_style(class_names)
33
+ class_names.to_s.split.map { |class_name| build_ident class_name }
34
+ end
35
+
36
+ def build_ident(local_name)
37
+ path = Rails.root.join('app', "#{@asset_path}.css").relative_path_from(Rails.root)
38
+ hash_digest = Digest::MD5.hexdigest("#{path}+#{local_name}")[0, 6]
39
+
40
+ return "#{local_name}-#{hash_digest}" unless Frails.dev_server.running?
41
+
42
+ name = path.basename.sub(path.extname, '').sub('.', '-')
43
+ ident = +"#{name}__#{local_name}___#{hash_digest}"
44
+ ident.prepend("#{path.dirname.to_s.tr('/', '-')}-")
45
+ ident
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Frails
4
+ module Monkey
5
+ module ActionView
6
+ module Renderer
7
+ def render_to_object(context, options) # :nodoc:
8
+ if options.key?(:partial)
9
+ render_partial_to_object(context, options)
10
+ elsif options.key?(:component)
11
+ render_component_to_object(context, options)
12
+ else
13
+ render_template_to_object(context, options)
14
+ end
15
+ end
16
+
17
+ # Direct access to partial rendering.
18
+ def render_component(context, options, &block) #:nodoc:
19
+ render_component_to_object(context, options, &block).body
20
+ end
21
+
22
+ def render_component_to_object(context, options, &block)
23
+ component = options[:component].to_s
24
+
25
+ result = if Rails.root.join('app', 'components', component, 'index.entry.jsx').exist?
26
+ Frails::Component::ReactComponentRenderer.new.render(context, options, &block)
27
+ else
28
+ options[:partial] = "#{component}/index"
29
+ Frails::Component::ComponentRenderer.new(@lookup_context)
30
+ .render(context, options, &block)
31
+ end
32
+
33
+ ::ActionView::AbstractRenderer::RenderedTemplate.new result, nil, nil
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Frails
4
+ module Monkey
5
+ module ActionView
6
+ module TemplateRenderer
7
+ def render_template(view, template, layout_name, locals)
8
+ return super if template.type != :html
9
+
10
+ # Side load layout assets - if any.
11
+ if layout_name
12
+ layout = find_layout(layout_name, locals.keys, [formats.first])
13
+ layout && side_load_assets(view, layout)
14
+ end
15
+
16
+ # Side load view assets - if any.
17
+ side_load_assets view, template
18
+
19
+ super
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,24 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rails/railtie'
4
-
5
4
  require 'frails/helper'
6
5
  require 'frails/dev_server_proxy'
7
6
 
8
7
  class Frails::Engine < ::Rails::Engine
9
- # Allows Webpacker config values to be set via Rails env config files
10
- config.frails = ActiveSupport::OrderedOptions.new
11
-
12
- initializer 'frails.default_config' do |app|
13
- assign_config app, :public_output_path, '/assets'
14
- assign_config app, :dev_server_port, 8080
15
- assign_config app, :dev_server_host, 'localhost'
16
- end
17
-
18
8
  initializer 'frails.proxy' do |app|
19
9
  app.middleware.insert_before 0, Frails::DevServerProxy, ssl_verify_none: true
20
10
  end
21
11
 
12
+ initializer 'frails.rendering' do |_conf|
13
+ ActiveSupport.on_load :action_controller do
14
+ ActionController::Base.prepend_view_path Rails.root.join('app', 'components')
15
+ end
16
+
17
+ ActiveSupport.on_load :action_view do
18
+ require 'frails/monkey/action_view/abstract_renderer'
19
+ require 'frails/monkey/action_view/template_renderer'
20
+ require 'frails/monkey/action_view/partial_renderer'
21
+ require 'frails/monkey/action_view/renderer'
22
+
23
+ ActionView::AbstractRenderer.send :prepend, Frails::Monkey::ActionView::AbstractRenderer
24
+ ActionView::TemplateRenderer.send :prepend, Frails::Monkey::ActionView::TemplateRenderer
25
+ ActionView::PartialRenderer.send :prepend, Frails::Monkey::ActionView::PartialRenderer
26
+ ActionView::Renderer.send :prepend, Frails::Monkey::ActionView::Renderer
27
+ end
28
+ end
29
+
22
30
  initializer 'frails.helper' do
23
31
  ActiveSupport.on_load :action_controller do
24
32
  ActionController::Base.helper Frails::Helper
@@ -29,7 +37,3 @@ class Frails::Engine < ::Rails::Engine
29
37
  end
30
38
  end
31
39
  end
32
-
33
- def assign_config(app, name, default_value)
34
- app.config.frails[name] = app.config.frails.fetch(name, default_value)
35
- end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Frails
4
- VERSION = '0.1.0'
4
+ VERSION = '0.3.0'
5
5
  end