frails 0.4.0 → 0.8.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.
- 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 +21 -29
- 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 +15 -17
- data/lib/frails/log_subscriber.rb +1 -1
- data/lib/frails/manifest.rb +3 -7
- data/lib/frails/monkey/action_view/abstract_renderer.rb +0 -2
- data/lib/frails/monkey/action_view/partial_renderer.rb +25 -6
- data/lib/frails/monkey/action_view/renderer.rb +6 -7
- data/lib/frails/monkey/action_view/template_renderer.rb +8 -1
- data/lib/frails/railtie.rb +5 -13
- data/lib/frails/side_load_assets.rb +8 -5
- data/lib/frails/utils.rb +22 -0
- data/lib/frails/version.rb +1 -1
- data/lib/tasks/frails.rake +1 -6
- data/package.json +13 -3
- data/package/__snapshots__/config.test.js.snap +10 -0
- data/package/components.js +34 -24
- data/package/config.js +38 -0
- data/package/config.test.js +7 -0
- data/package/side_load.js +7 -5
- data/yarn.lock +4283 -51
- metadata +28 -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