frails 0.5.0 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.autotest +6 -0
- data/.gitignore +8 -1
- data/.rubocop.yml +10 -1
- data/Gemfile +6 -8
- data/Gemfile.lock +64 -109
- data/README.md +18 -27
- data/Rakefile +1 -1
- data/babel.config.js +12 -0
- data/bin/test +5 -3
- data/config.ru +9 -0
- data/frails.gemspec +5 -13
- data/index.js +10 -31
- data/lib/frails.rb +33 -9
- data/lib/frails/component.rb +16 -5
- data/lib/frails/component/{abstract_component.rb → abstract.rb} +11 -9
- data/lib/frails/component/{plain_component.rb → base.rb} +7 -3
- data/lib/frails/component/{react_component.rb → react.rb} +3 -5
- data/lib/frails/component/{react_component_renderer.rb → react_renderer.rb} +22 -16
- data/lib/frails/component/{component_renderer.rb → renderer.rb} +34 -5
- data/lib/frails/component/renderer_concerns.rb +10 -4
- data/lib/frails/component/test_helpers.rb +19 -0
- data/lib/frails/dev_server.rb +2 -2
- data/lib/frails/helper.rb +9 -4
- data/lib/frails/log_subscriber.rb +1 -1
- data/lib/frails/manifest.rb +3 -7
- data/lib/frails/monkey/action_view/partial_renderer.rb +18 -5
- data/lib/frails/monkey/action_view/renderer.rb +6 -7
- data/lib/frails/railtie.rb +4 -4
- data/lib/frails/utils.rb +22 -0
- data/lib/frails/version.rb +1 -1
- data/lib/tasks/frails.rake +1 -6
- data/package.json +18 -4
- data/package/__snapshots__/config.test.js.snap +10 -0
- data/package/components.js +34 -24
- data/package/config.js +29 -0
- data/package/side_load.js +7 -5
- data/yarn.lock +4283 -51
- metadata +27 -21
- data/.travis.yml +0 -7
data/index.js
CHANGED
@@ -1,35 +1,14 @@
|
|
1
|
-
const
|
2
|
-
|
3
|
-
const rootPath = path.resolve(__dirname, '../../../')
|
4
|
-
const publicOutputPath = process.env.FRAILS_PUBLIC_OUTPUT_PATH || 'assets'
|
5
|
-
|
6
|
-
// Ensure that the publicPath includes our asset host so dynamic imports
|
7
|
-
// (code-splitting chunks and static assets) load from the CDN instead of a relative path.
|
8
|
-
const getPublicPathWithAssetHost = () => {
|
9
|
-
const rootUrl = process.env.RAILS_ASSET_HOST || '/'
|
10
|
-
let packPath = `${publicOutputPath}/`
|
11
|
-
|
12
|
-
// Add relative root prefix to pack path.
|
13
|
-
if (process.env.RAILS_RELATIVE_URL_ROOT) {
|
14
|
-
let relativeRoot = process.env.RAILS_RELATIVE_URL_ROOT
|
15
|
-
relativeRoot = relativeRoot.startsWith('/') ? relativeRoot.substr(1) : relativeRoot
|
16
|
-
packPath = `${ensureTrailingSlash(relativeRoot)}${packPath}`
|
17
|
-
}
|
18
|
-
|
19
|
-
return (rootUrl.endsWith('/') ? rootUrl : `${rootUrl}/`) + packPath
|
20
|
-
}
|
1
|
+
const config = require("./package/config");
|
21
2
|
|
22
3
|
module.exports = {
|
23
|
-
|
24
|
-
publicOutputPath,
|
25
|
-
manifestPath: process.env.FRAILS_MANIFEST_PATH || 'manifest.json',
|
26
|
-
devServerPort: process.env.FRAILS_DEV_SERVER_PORT || '8080',
|
27
|
-
devServerHost: process.env.FRAILS_DEV_SERVER_HOST || 'localhost',
|
4
|
+
config,
|
28
5
|
|
29
6
|
// The local ident name required for loading component styles.
|
30
|
-
cssLocalIdentName:
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
7
|
+
cssLocalIdentName:
|
8
|
+
config.railsEnv == "development"
|
9
|
+
? "[path][name]__[local]___[md5:hash:hex:6]"
|
10
|
+
: "[local]-[md5:hash:hex:6]",
|
11
|
+
|
12
|
+
sideLoadEntry: require("./package/side_load"),
|
13
|
+
components: require("./package/components"),
|
14
|
+
};
|
data/lib/frails.rb
CHANGED
@@ -4,11 +4,6 @@ require 'frails/version'
|
|
4
4
|
require 'active_support/core_ext/module'
|
5
5
|
require 'active_support/core_ext/module/attribute_accessors'
|
6
6
|
|
7
|
-
# ENV['FRAILS_DEV_SERVER_PORT'] ||= '8080'
|
8
|
-
# ENV['FRAILS_DEV_SERVER_HOST'] ||= 'localhost'
|
9
|
-
# ENV['FRAILS_PUBLIC_OUTPUT_PATH'] ||= 'assets'
|
10
|
-
# ENV['FRAILS_MANIFEST_PATH'] ||= 'manifest.json'
|
11
|
-
|
12
7
|
module Frails
|
13
8
|
extend self
|
14
9
|
|
@@ -20,12 +15,41 @@ module Frails
|
|
20
15
|
@manifest ||= Frails::ManifestManager.new
|
21
16
|
end
|
22
17
|
|
23
|
-
|
24
|
-
|
18
|
+
# Path of where webpack build output will be emitted, relative to the Rails public directory.
|
19
|
+
mattr_accessor :public_output_path
|
20
|
+
@@public_output_path = 'assets'
|
21
|
+
|
22
|
+
# Path and name of manifest file.
|
23
|
+
mattr_accessor :manifest_path
|
24
|
+
@@manifest_path = 'manifest.json'
|
25
|
+
|
26
|
+
# Hostname where the Webpack dev webpack server should run.
|
27
|
+
mattr_accessor :dev_server_host
|
28
|
+
@@dev_server_host = 'localhost'
|
29
|
+
|
30
|
+
# Post number where the Webpack dev webpack server should run.
|
31
|
+
mattr_accessor :dev_server_port
|
32
|
+
@@dev_server_port = 8080
|
33
|
+
|
34
|
+
def self.setup
|
35
|
+
yield self
|
36
|
+
end
|
37
|
+
|
38
|
+
def config
|
39
|
+
{
|
40
|
+
public_output_path: @@public_output_path,
|
41
|
+
dev_server_host: @@dev_server_host,
|
42
|
+
dev_server_port: @@dev_server_port,
|
43
|
+
manifest_path: @@manifest_path
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def config_as_json
|
48
|
+
config.merge({ rails_env: Rails.env }).transform_keys { |key| key.to_s.camelize :lower }.to_json
|
25
49
|
end
|
26
50
|
|
27
|
-
def
|
28
|
-
|
51
|
+
def components_path
|
52
|
+
@components_path ||= Rails.root.join('app', 'components')
|
29
53
|
end
|
30
54
|
end
|
31
55
|
|
data/lib/frails/component.rb
CHANGED
@@ -3,9 +3,20 @@
|
|
3
3
|
module Frails::Component
|
4
4
|
end
|
5
5
|
|
6
|
+
module ActionView
|
7
|
+
extend ActiveSupport::Autoload
|
8
|
+
|
9
|
+
# Make sure ActionView::SyntaxErrorInTemplate is loaded.
|
10
|
+
eager_autoload do
|
11
|
+
autoload_at 'action_view/template/error' do
|
12
|
+
autoload :SyntaxErrorInTemplate
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
6
17
|
require 'frails/component/renderer_concerns'
|
7
|
-
require 'frails/component/
|
8
|
-
require 'frails/component/
|
9
|
-
require 'frails/component/
|
10
|
-
require 'frails/component/
|
11
|
-
require 'frails/component/
|
18
|
+
require 'frails/component/renderer'
|
19
|
+
require 'frails/component/react_renderer'
|
20
|
+
require 'frails/component/abstract'
|
21
|
+
require 'frails/component/base'
|
22
|
+
require 'frails/component/react'
|
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class Frails::Component::
|
3
|
+
class Frails::Component::Abstract
|
4
4
|
include ActiveSupport::Callbacks
|
5
5
|
|
6
6
|
define_callbacks :render
|
7
7
|
|
8
|
-
def initialize(view, options)
|
9
|
-
@view
|
8
|
+
def initialize(view, path, options)
|
9
|
+
@view = view
|
10
|
+
@path = path
|
11
|
+
@options = options
|
10
12
|
|
11
13
|
expand_instance_vars
|
12
14
|
end
|
@@ -24,19 +26,19 @@ class Frails::Component::AbstractComponent
|
|
24
26
|
# rubocop:disable Lint/ShadowedException, Style/MissingRespondToMissing
|
25
27
|
def method_missing(method, *args, &block)
|
26
28
|
super
|
27
|
-
rescue NoMethodError, NameError =>
|
29
|
+
rescue NoMethodError, NameError => e
|
28
30
|
# the error is not mine, so just releases it as is.
|
29
|
-
raise
|
31
|
+
raise e if e.name != method
|
30
32
|
|
31
33
|
begin
|
32
34
|
@view.send method, *args, &block
|
33
|
-
rescue NoMethodError =>
|
34
|
-
raise
|
35
|
+
rescue NoMethodError => e
|
36
|
+
raise e if e.name != method
|
35
37
|
|
36
38
|
raise NoMethodError.new("undefined method `#{method}' for either #{self} or #{@view}",
|
37
39
|
method)
|
38
|
-
rescue NameError =>
|
39
|
-
raise
|
40
|
+
rescue NameError => e
|
41
|
+
raise e if e.name != method
|
40
42
|
|
41
43
|
raise NameError.new("undefined local variable `#{method}' for either #{self} or #{@view}",
|
42
44
|
method)
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class Frails::Component::
|
4
|
-
PRIVATE_METHODS = %i[render method_missing locals].freeze
|
3
|
+
class Frails::Component::Base < Frails::Component::Abstract
|
4
|
+
PRIVATE_METHODS = %i[render method_missing locals to_partial_path].freeze
|
5
5
|
|
6
|
-
def initialize(view, options)
|
6
|
+
def initialize(view, path, options)
|
7
7
|
super
|
8
8
|
|
9
9
|
@locals = @options.fetch(:locals, @options)
|
@@ -16,4 +16,8 @@ class Frails::Component::PlainComponent < Frails::Component::AbstractComponent
|
|
16
16
|
end
|
17
17
|
hash.merge @locals
|
18
18
|
end
|
19
|
+
|
20
|
+
def to_partial_path
|
21
|
+
"#{@path}/index"
|
22
|
+
end
|
19
23
|
end
|
@@ -1,17 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class Frails::Component::
|
3
|
+
class Frails::Component::React < Frails::Component::Abstract
|
4
4
|
attr_accessor :class_name, :props, :tag, :prerender, :content_loader
|
5
5
|
|
6
|
-
def initialize(view, options)
|
7
|
-
|
6
|
+
def initialize(view, path, options)
|
7
|
+
super
|
8
8
|
|
9
9
|
@class_name = @options.fetch(:class, nil)
|
10
10
|
@props = @options.fetch(:props, {})
|
11
11
|
@tag = @options.fetch(:tag, :div)
|
12
12
|
@prerender = @options.fetch(:prerender, false)
|
13
13
|
@content_loader = @options.fetch(:content_loader, false)
|
14
|
-
|
15
|
-
expand_instance_vars
|
16
14
|
end
|
17
15
|
end
|
@@ -1,12 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'frails/utils'
|
4
|
+
|
5
|
+
class Frails::Component::ReactRenderer
|
4
6
|
include Frails::Component::RendererConcerns
|
5
7
|
|
6
8
|
def render(context, options, &block)
|
7
9
|
@view = context
|
8
|
-
@component = options.delete(:component)
|
9
|
-
|
10
|
+
@component = options.delete(:component)
|
11
|
+
|
12
|
+
klass = presenter_class
|
13
|
+
@presenter = klass.new(@view, @component, options)
|
10
14
|
|
11
15
|
@children = @view.capture(&block) if block_given?
|
12
16
|
|
@@ -15,27 +19,27 @@ class Frails::Component::ReactComponentRenderer
|
|
15
19
|
|
16
20
|
private
|
17
21
|
|
18
|
-
# rubocop:disable Rails/OutputSafety
|
19
22
|
def render_with_callbacks
|
20
23
|
@presenter.run_callbacks :render do
|
21
24
|
@prerender = @presenter.prerender
|
22
25
|
@content_loader = @presenter.content_loader
|
23
26
|
@props = camelize_keys(@presenter.props)
|
24
|
-
@props[:children] = @children if
|
27
|
+
@props[:children] = @children if instance_variable_defined?(:@children)
|
25
28
|
|
26
29
|
@prerender && render_inline_styles
|
27
30
|
|
28
|
-
rendered_tag =
|
29
|
-
|
30
|
-
|
31
|
+
rendered_tag = if @prerender
|
32
|
+
content_tag { React::Renderer.new.render(@component, @props).html_safe }
|
33
|
+
else
|
34
|
+
loader || content_tag { nil }
|
35
|
+
end
|
31
36
|
|
32
37
|
@prerender ? move_console_replay_script(rendered_tag) : rendered_tag
|
33
38
|
end
|
34
39
|
end
|
35
|
-
# rubocop:enable Rails/OutputSafety
|
36
40
|
|
37
41
|
def presenter_class
|
38
|
-
super || Frails::Component::
|
42
|
+
super || Frails::Component::React
|
39
43
|
end
|
40
44
|
|
41
45
|
def data_for_content_tag
|
@@ -49,28 +53,30 @@ class Frails::Component::ReactComponentRenderer
|
|
49
53
|
end
|
50
54
|
|
51
55
|
def content_tag(&block)
|
52
|
-
|
53
|
-
@view.content_tag @presenter.tag, class:
|
56
|
+
class_names = "js__reactComponent #{@presenter.class_name}"
|
57
|
+
@view.content_tag @presenter.tag, class: class_names, data: data_for_content_tag, &block
|
54
58
|
end
|
55
59
|
|
56
60
|
def camelize_keys(data)
|
57
|
-
data.deep_transform_keys
|
61
|
+
data.deep_transform_keys do |key|
|
62
|
+
Frails::Utils.camelize key.to_s, :lower, convert_slashes: false
|
63
|
+
end
|
58
64
|
end
|
59
65
|
|
60
66
|
def loader
|
61
67
|
return unless @content_loader
|
62
68
|
|
63
|
-
@view.render
|
69
|
+
@view.render @content_loader, class_name: 'js__reactComponent', data: data_for_content_tag
|
64
70
|
end
|
65
71
|
|
66
72
|
# Grab the server-rendered console replay script and move it outside the container div.
|
67
73
|
#
|
68
|
-
# rubocop:disable
|
74
|
+
# rubocop:disable Style/RegexpLiteral
|
69
75
|
def move_console_replay_script(rendered_tag)
|
70
76
|
regex = /\n(<script class="js__reactServerConsoleReplay">.*<\/script>)<\/(\w+)>$/m
|
71
77
|
rendered_tag.sub(regex, '</\2>\1').html_safe
|
72
78
|
end
|
73
|
-
# rubocop:enable
|
79
|
+
# rubocop:enable Style/RegexpLiteral
|
74
80
|
|
75
81
|
def stylesheet_entry_file
|
76
82
|
"#{@component.tr('/', '-')}-index-entry-jsx"
|
@@ -1,29 +1,56 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class Frails::Component::
|
3
|
+
class Frails::Component::Renderer < ActionView::PartialRenderer
|
4
4
|
include Frails::Component::RendererConcerns
|
5
5
|
|
6
|
+
# Overwritten to make sure we don't lookup partials. Even though this inherits from the
|
7
|
+
# PartialRenderer, component templates do not have the underscore prefix that partials have.
|
8
|
+
#
|
9
|
+
# Additionally, this will ensure that ONLY the app/components directory is used as the only view
|
10
|
+
# path to search within when looking up the component template.
|
11
|
+
def find_template(path, locals)
|
12
|
+
path_count = @lookup_context.view_paths.size
|
13
|
+
@lookup_context.view_paths.unshift Frails.components_path
|
14
|
+
old_paths = @lookup_context.view_paths.pop(path_count)
|
15
|
+
|
16
|
+
prefixes = path.include?('/') ? [] : @lookup_context.prefixes
|
17
|
+
result = @lookup_context.find_template(path, prefixes, false, locals, @details)
|
18
|
+
|
19
|
+
@lookup_context.view_paths.unshift(*old_paths)
|
20
|
+
@lookup_context.view_paths.pop
|
21
|
+
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
# rubocop:disable Metrics/AbcSize
|
6
26
|
def render(context, options, &block)
|
7
27
|
@view = context
|
8
|
-
@component = options.delete(:component)
|
9
|
-
|
28
|
+
@component = options.delete(:component)
|
29
|
+
|
30
|
+
klass = presenter_class
|
31
|
+
@presenter = klass.new(@view, @component, options)
|
32
|
+
|
33
|
+
@children = block_given? ? @view.capture(&block) : nil
|
34
|
+
options[:partial] = @presenter
|
10
35
|
|
11
36
|
result = @presenter.run_callbacks :render do
|
12
37
|
if @presenter.respond_to?(:render)
|
13
38
|
@presenter.render(&block)
|
14
39
|
else
|
15
40
|
options[:locals] = @presenter.locals
|
41
|
+
options[:locals][:children] = @children
|
16
42
|
super context, options, block
|
17
43
|
end
|
18
44
|
end
|
19
45
|
|
20
46
|
apply_styles((result.respond_to?(:body) ? result.body : result) || nil)
|
21
47
|
end
|
48
|
+
# rubocop:enable Metrics/AbcSize
|
22
49
|
|
23
50
|
private
|
24
51
|
|
25
52
|
def presenter_class
|
26
|
-
super || Frails::Component::
|
53
|
+
super || Frails::Component::Base
|
27
54
|
end
|
28
55
|
|
29
56
|
def apply_styles(content)
|
@@ -54,6 +81,7 @@ class Frails::Component::ComponentRenderer < ActionView::PartialRenderer
|
|
54
81
|
class_names.to_s.split.map { |class_name| build_ident class_name }
|
55
82
|
end
|
56
83
|
|
84
|
+
# rubocop:disable Metrics/AbcSize
|
57
85
|
def build_ident(local_name)
|
58
86
|
hash_digest = Digest::MD5.hexdigest("#{stylesheet_path}+#{local_name}")[0, 6]
|
59
87
|
|
@@ -64,11 +92,12 @@ class Frails::Component::ComponentRenderer < ActionView::PartialRenderer
|
|
64
92
|
ident.prepend("#{stylesheet_path.dirname.to_s.tr('/', '-')}-")
|
65
93
|
ident
|
66
94
|
end
|
95
|
+
# rubocop:enable Metrics/AbcSize
|
67
96
|
|
68
97
|
def stylesheet_path
|
69
98
|
@stylesheet_path ||= begin
|
70
99
|
style_file = "#{@component}/index.css"
|
71
|
-
|
100
|
+
Frails.components_path.join(style_file).relative_path_from(Rails.root)
|
72
101
|
end
|
73
102
|
end
|
74
103
|
end
|
@@ -6,7 +6,13 @@ module Frails::Component::RendererConcerns
|
|
6
6
|
private
|
7
7
|
|
8
8
|
def presenter_class
|
9
|
-
|
9
|
+
if @component.is_a?(Class) && @component < Frails::Component::Base
|
10
|
+
klass = @component
|
11
|
+
@component = @component.to_s.underscore
|
12
|
+
return klass
|
13
|
+
end
|
14
|
+
|
15
|
+
klass_file = Frails.components_path.join("#{@component}_component.rb")
|
10
16
|
klass_file.exist? && "#{@component.to_s.camelcase}Component".constantize
|
11
17
|
end
|
12
18
|
|
@@ -17,13 +23,13 @@ module Frails::Component::RendererConcerns
|
|
17
23
|
# Don't inline the styles if already inlined.
|
18
24
|
return if inlined_stylesheets.include?(@component)
|
19
25
|
|
20
|
-
Frails.manifest.read
|
26
|
+
Frails.manifest.read(stylesheet_entry_file, :stylesheet) do |path, src|
|
21
27
|
@view.content_for :component_styles do
|
22
28
|
@view.content_tag(:style, src, { data: { href: path } }, false)
|
23
29
|
end
|
24
|
-
end
|
25
30
|
|
26
|
-
|
31
|
+
inlined_stylesheets << @component
|
32
|
+
end
|
27
33
|
end
|
28
34
|
|
29
35
|
def inlined_stylesheets
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Frails
|
4
|
+
module Component
|
5
|
+
module TestHelpers
|
6
|
+
def render_inline(options = {}, locals = {}, &block)
|
7
|
+
Nokogiri::HTML(controller.view_context.render(options, locals, &block))
|
8
|
+
end
|
9
|
+
|
10
|
+
def controller
|
11
|
+
@controller ||= ApplicationController.new.tap { |c| c.request = request }
|
12
|
+
end
|
13
|
+
|
14
|
+
def request
|
15
|
+
@request ||= ActionDispatch::TestRequest.create
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/frails/dev_server.rb
CHANGED