frails 0.5.0 → 0.8.1

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