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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.autotest +6 -0
  3. data/.gitignore +8 -1
  4. data/.rubocop.yml +10 -1
  5. data/Gemfile +6 -8
  6. data/Gemfile.lock +64 -109
  7. data/README.md +21 -29
  8. data/Rakefile +1 -1
  9. data/babel.config.js +12 -0
  10. data/bin/test +5 -3
  11. data/config.ru +9 -0
  12. data/frails.gemspec +5 -13
  13. data/index.js +10 -31
  14. data/lib/frails.rb +33 -9
  15. data/lib/frails/component.rb +16 -5
  16. data/lib/frails/component/{abstract_component.rb → abstract.rb} +11 -9
  17. data/lib/frails/component/{plain_component.rb → base.rb} +7 -3
  18. data/lib/frails/component/{react_component.rb → react.rb} +3 -5
  19. data/lib/frails/component/{react_component_renderer.rb → react_renderer.rb} +22 -16
  20. data/lib/frails/component/{component_renderer.rb → renderer.rb} +34 -5
  21. data/lib/frails/component/renderer_concerns.rb +10 -4
  22. data/lib/frails/component/test_helpers.rb +19 -0
  23. data/lib/frails/dev_server.rb +2 -2
  24. data/lib/frails/helper.rb +15 -17
  25. data/lib/frails/log_subscriber.rb +1 -1
  26. data/lib/frails/manifest.rb +3 -7
  27. data/lib/frails/monkey/action_view/abstract_renderer.rb +0 -2
  28. data/lib/frails/monkey/action_view/partial_renderer.rb +25 -6
  29. data/lib/frails/monkey/action_view/renderer.rb +6 -7
  30. data/lib/frails/monkey/action_view/template_renderer.rb +8 -1
  31. data/lib/frails/railtie.rb +5 -13
  32. data/lib/frails/side_load_assets.rb +8 -5
  33. data/lib/frails/utils.rb +22 -0
  34. data/lib/frails/version.rb +1 -1
  35. data/lib/tasks/frails.rake +1 -6
  36. data/package.json +13 -3
  37. data/package/__snapshots__/config.test.js.snap +10 -0
  38. data/package/components.js +34 -24
  39. data/package/config.js +38 -0
  40. data/package/config.test.js +7 -0
  41. data/package/side_load.js +7 -5
  42. data/yarn.lock +4283 -51
  43. metadata +28 -21
  44. data/.travis.yml +0 -7
data/index.js CHANGED
@@ -1,35 +1,14 @@
1
- const path = require('path')
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
- // Configuration
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: process.env.NODE_ENV == 'development' ? '[path][name]__[local]___[md5:hash:hex:6]' : '[local]-[md5:hash:hex:6]',
31
-
32
- getPublicPathWithAssetHost,
33
- sideLoadEntry: require('./package/side_load'),
34
- components: require('./package/components')
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
+ };
@@ -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
- def public_output_path
24
- ENV['FRAILS_PUBLIC_OUTPUT_PATH'] || 'assets'
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 manifest_path
28
- ENV['FRAILS_MANIFEST_PATH'] || 'manifest.json'
51
+ def components_path
52
+ @components_path ||= Rails.root.join('app', 'components')
29
53
  end
30
54
  end
31
55
 
@@ -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/component_renderer'
8
- require 'frails/component/react_component_renderer'
9
- require 'frails/component/abstract_component'
10
- require 'frails/component/plain_component'
11
- require 'frails/component/react_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::AbstractComponent
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, @options = view, options
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 => e1
29
+ rescue NoMethodError, NameError => e
28
30
  # the error is not mine, so just releases it as is.
29
- raise e1 if e1.name != method
31
+ raise e if e.name != method
30
32
 
31
33
  begin
32
34
  @view.send method, *args, &block
33
- rescue NoMethodError => e2
34
- raise e2 if e2.name != method
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 => e2
39
- raise e2 if e2.name != method
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::PlainComponent < Frails::Component::AbstractComponent
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::ReactComponent < Frails::Component::AbstractComponent
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
- @view, @options = view, options
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
- class Frails::Component::ReactComponentRenderer
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).to_s
9
- @presenter = presenter_class.new(@view, options)
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 @children
27
+ @props[:children] = @children if instance_variable_defined?(:@children)
25
28
 
26
29
  @prerender && render_inline_styles
27
30
 
28
- rendered_tag = content_tag do
29
- @prerender ? React::Renderer.new.render(@component, @props).html_safe : loader
30
- end
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::ReactComponent
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
- classes = "js__reactComponent #{@presenter.class_name}"
53
- @view.content_tag @presenter.tag, class: classes, data: data_for_content_tag, &block
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 { |key| key.to_s.camelize :lower }
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 "shared/content_loaders/#{@content_loader == true ? 'code' : @content_loader}"
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 Rails/OutputSafety, Style/RegexpLiteral
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 Rails/OutputSafety, Style/RegexpLiteral
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::ComponentRenderer < ActionView::PartialRenderer
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).to_s
9
- @presenter = presenter_class.new(@view, options)
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::PlainComponent
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
- Rails.root.join('app', 'components', style_file).relative_path_from(Rails.root)
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
- klass_file = Rails.root.join('app', 'components', "#{@component}_component.rb")
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!(stylesheet_entry_file, :stylesheet) do |path, src|
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
- inlined_stylesheets << @component
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
@@ -15,10 +15,10 @@ class Frails::DevServer
15
15
  end
16
16
 
17
17
  def host
18
- ENV['FRAILS_DEV_SERVER_HOST'] || 'localhost'
18
+ Frails.dev_server_host
19
19
  end
20
20
 
21
21
  def port
22
- ENV['FRAILS_DEV_SERVER_PORT'] || 8080
22
+ Frails.dev_server_port
23
23
  end
24
24
  end