frails 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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