frails 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +1 -3
- data/Gemfile +12 -1
- data/Gemfile.lock +101 -33
- data/README.md +166 -16
- data/bin/test +5 -0
- data/frails.gemspec +5 -8
- data/index.js +35 -0
- data/lib/frails.rb +17 -13
- data/lib/frails/component.rb +11 -0
- data/lib/frails/component/abstract_component.rb +58 -0
- data/lib/frails/component/component_renderer.rb +74 -0
- data/lib/frails/component/plain_component.rb +19 -0
- data/lib/frails/component/react_component.rb +17 -0
- data/lib/frails/component/react_component_renderer.rb +78 -0
- data/lib/frails/component/renderer_concerns.rb +36 -0
- data/lib/frails/dev_server.rb +2 -2
- data/lib/frails/dev_server_proxy.rb +3 -10
- data/lib/frails/helper.rb +73 -6
- data/lib/frails/log_subscriber.rb +35 -0
- data/lib/frails/manifest.rb +40 -3
- data/lib/frails/manifest_manager.rb +22 -0
- data/lib/frails/monkey/action_view/abstract_renderer.rb +54 -0
- data/lib/frails/monkey/action_view/partial_renderer.rb +50 -0
- data/lib/frails/monkey/action_view/renderer.rb +38 -0
- data/lib/frails/monkey/action_view/template_renderer.rb +24 -0
- data/lib/frails/railtie.rb +18 -14
- data/lib/frails/version.rb +1 -1
- data/lib/tasks/frails.rake +31 -0
- data/package.json +16 -0
- data/package/components.js +47 -0
- data/package/side_load.js +25 -0
- data/yarn.lock +80 -0
- metadata +34 -45
- data/lib/frails/instance.rb +0 -11
- data/lib/frails/server_manifest.rb +0 -7
data/lib/frails/helper.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
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
|
19
|
-
Frails.
|
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
|
data/lib/frails/manifest.rb
CHANGED
@@ -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') # => "/
|
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
|
36
|
-
|
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
|
data/lib/frails/railtie.rb
CHANGED
@@ -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
|
data/lib/frails/version.rb
CHANGED