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
@@ -1,34 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Frails::Helper
4
- def render(options = {}, locals = {}, &block)
4
+ # rubocop:disable Metrics/AbcSize
5
+ def render(options = {}, *locals, &block)
6
+ sload_assets = respond_to?(:side_load_assets?) ? side_load_assets? : false
7
+
5
8
  case options
6
9
  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)
10
+ in_rendering_context(options) do
11
+ return view_renderer.render_component(self, options, &block) if options.key?(:component)
22
12
 
13
+ options[:side_load_assets] = sload_assets
14
+ if block_given?
23
15
  view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
24
16
  else
25
17
  view_renderer.render(self, options)
26
18
  end
27
19
  end
28
20
  else
29
- view_renderer.render_partial(self, partial: options, locals: locals, &block)
21
+ if options.is_a?(Class) && options < Frails::Component::Base
22
+ return view_renderer.render_component(self, { component: options, locals: locals }, &block)
23
+ end
24
+
25
+ view_renderer.render_partial(self, side_load_assets: sload_assets, partial: options,
26
+ locals: locals.extract_options!, &block)
30
27
  end
31
28
  end
29
+ # rubocop:enable Metrics/AbcSize
32
30
 
33
31
  def javascript_pack_tag(*names, **options)
34
32
  return if Rails.env.test?
@@ -27,7 +27,7 @@ module Frails
27
27
  end
28
28
 
29
29
  def rails_root
30
- @root ||= "#{Rails.root}/"
30
+ @rails_root ||= "#{Rails.root}/"
31
31
  end
32
32
  end
33
33
  end
@@ -62,15 +62,11 @@ class Frails::Manifest
62
62
  private
63
63
 
64
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
65
+ return Rails.public_path.join(path.gsub(%r{^\/}, '')).read unless Frails.dev_server.running?
70
66
 
71
67
  begin
72
- open("http://#{Frails.dev_server.host_with_port}#{path}").read
73
- rescue OpenURI::HTTPError => e
68
+ URI.open("http://#{Frails.dev_server.host_with_port}#{path}").read
69
+ rescue OpenURI::HTTPError
74
70
  handle_missing_entry path
75
71
  end
76
72
  end
@@ -5,8 +5,6 @@ module Frails
5
5
  module ActionView
6
6
  module AbstractRenderer
7
7
  def side_load_assets(view, tpl)
8
- return unless view.controller._side_load_assets?
9
-
10
8
  path = tpl.short_identifier.delete_prefix('app/').delete_suffix('.html.erb')
11
9
 
12
10
  instrument :side_loaded_assets, identifier: tpl.identifier, asset_types: [] do |payload|
@@ -4,9 +4,22 @@ module Frails
4
4
  module Monkey
5
5
  module ActionView
6
6
  module PartialRenderer
7
+ def render_collection(view, template)
8
+ # Side load partial assets - if any.
9
+ @asset_path = @side_load_assets && side_load_assets(view, template)
10
+
11
+ super
12
+ end
13
+
7
14
  def render_partial(view, template)
8
15
  # Side load partial assets - if any.
9
- @asset_path = side_load_assets(view, template)
16
+ @asset_path = @side_load_assets && side_load_assets(view, template)
17
+
18
+ super
19
+ end
20
+
21
+ def setup(context, options, as, block)
22
+ @side_load_assets = options.key?(:side_load_assets) ? options[:side_load_assets] : false
10
23
 
11
24
  super
12
25
  end
@@ -20,21 +33,21 @@ module Frails
20
33
  doc = Nokogiri::HTML::DocumentFragment.parse(content)
21
34
 
22
35
  return content if (modules = doc.css('[css_module]')).empty?
36
+ return content unless (path = stylesheet_path_for_ident)
23
37
 
24
38
  modules.each do |ele|
25
- classes = class_name_for_style(ele.delete('css_module'))
39
+ classes = class_name_for_style(ele.delete('css_module'), path)
26
40
  ele['class'] = (ele['class'].nil? ? classes : classes << ele['class']).join(' ')
27
41
  end
28
42
 
29
43
  doc.to_html
30
44
  end
31
45
 
32
- def class_name_for_style(class_names)
33
- class_names.to_s.split.map { |class_name| build_ident class_name }
46
+ def class_name_for_style(class_names, path)
47
+ class_names.to_s.split.map { |class_name| build_ident class_name, path }
34
48
  end
35
49
 
36
- def build_ident(local_name)
37
- path = Rails.root.join('app', "#{@asset_path}.css").relative_path_from(Rails.root)
50
+ def build_ident(local_name, path)
38
51
  hash_digest = Digest::MD5.hexdigest("#{path}+#{local_name}")[0, 6]
39
52
 
40
53
  return "#{local_name}-#{hash_digest}" unless Frails.dev_server.running?
@@ -44,6 +57,12 @@ module Frails
44
57
  ident.prepend("#{path.dirname.to_s.tr('/', '-')}-")
45
58
  ident
46
59
  end
60
+
61
+ def stylesheet_path_for_ident
62
+ return if (globs = Rails.root.glob("app/#{@asset_path}.{css,scss,sass,less}")).empty?
63
+
64
+ globs.first.relative_path_from(Rails.root)
65
+ end
47
66
  end
48
67
  end
49
68
  end
@@ -4,7 +4,7 @@ module Frails
4
4
  module Monkey
5
5
  module ActionView
6
6
  module Renderer
7
- def render_to_object(context, options) # :nodoc:
7
+ def render_to_object(context, options)
8
8
  if options.key?(:partial)
9
9
  render_partial_to_object(context, options)
10
10
  elsif options.key?(:component)
@@ -15,19 +15,18 @@ module Frails
15
15
  end
16
16
 
17
17
  # Direct access to partial rendering.
18
- def render_component(context, options, &block) #:nodoc:
18
+ def render_component(context, options, &block)
19
19
  render_component_to_object(context, options, &block).body
20
20
  end
21
21
 
22
22
  def render_component_to_object(context, options, &block)
23
23
  component = options[:component].to_s
24
24
 
25
- result = if Rails.root.join('app', 'components', component, 'index.entry.jsx').exist?
26
- Frails::Component::ReactComponentRenderer.new.render(context, options, &block)
25
+ result = if Frails.components_path.join(component, 'index.entry.jsx').exist?
26
+ Frails::Component::ReactRenderer.new.render(context, options, &block)
27
27
  else
28
- options[:partial] = "#{component}/index"
29
- Frails::Component::ComponentRenderer.new(@lookup_context)
30
- .render(context, options, &block)
28
+ Frails::Component::Renderer.new(@lookup_context)
29
+ .render(context, options, &block)
31
30
  end
32
31
 
33
32
  ::ActionView::AbstractRenderer::RenderedTemplate.new result, nil, nil
@@ -4,8 +4,15 @@ module Frails
4
4
  module Monkey
5
5
  module ActionView
6
6
  module TemplateRenderer
7
+ def render(context, options)
8
+ # See Frails::SideLoadAssets
9
+ @side_load_assets = options.key?(:side_load_assets) ? options[:side_load_assets] : false
10
+
11
+ super
12
+ end
13
+
7
14
  def render_template(view, template, layout_name, locals)
8
- return super if template.type != :html
15
+ return super if !@side_load_assets || template.type != :html
9
16
 
10
17
  # Side load layout assets - if any.
11
18
  if layout_name
@@ -9,7 +9,7 @@ class Frails::Engine < ::Rails::Engine
9
9
  app.middleware.insert_before 0, Frails::DevServerProxy, ssl_verify_none: true
10
10
  end
11
11
 
12
- initializer 'frails.rendering' do
12
+ initializer 'frails' do
13
13
  ActiveSupport.on_load :action_controller do
14
14
  require 'frails/side_load_assets'
15
15
 
@@ -28,19 +28,11 @@ class Frails::Engine < ::Rails::Engine
28
28
  require 'frails/monkey/action_view/partial_renderer'
29
29
  require 'frails/monkey/action_view/renderer'
30
30
 
31
- ActionView::AbstractRenderer.send :prepend, Frails::Monkey::ActionView::AbstractRenderer
32
- ActionView::TemplateRenderer.send :prepend, Frails::Monkey::ActionView::TemplateRenderer
33
- ActionView::PartialRenderer.send :prepend, Frails::Monkey::ActionView::PartialRenderer
34
- ActionView::Renderer.send :prepend, Frails::Monkey::ActionView::Renderer
35
- end
36
- end
37
-
38
- initializer 'frails.helper' do
39
- ActiveSupport.on_load :action_controller do
40
- ActionController::Base.helper Frails::Helper
41
- end
31
+ ActionView::AbstractRenderer.prepend Frails::Monkey::ActionView::AbstractRenderer
32
+ ActionView::TemplateRenderer.prepend Frails::Monkey::ActionView::TemplateRenderer
33
+ ActionView::PartialRenderer.prepend Frails::Monkey::ActionView::PartialRenderer
34
+ ActionView::Renderer.prepend Frails::Monkey::ActionView::Renderer
42
35
 
43
- ActiveSupport.on_load :action_view do
44
36
  include Frails::Helper
45
37
  end
46
38
  end
@@ -4,12 +4,15 @@ module Frails::SideLoadAssets
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- class_attribute :_side_load_assets, default: false
7
+ class_attribute :side_load_assets, default: false
8
+ helper_method :side_load_assets?
8
9
  end
9
10
 
10
- class_methods do
11
- def side_load_assets
12
- self._side_load_assets = true
13
- end
11
+ # Add _side_load_assets flag to the options hash, which will be available in TemplateRenderer,
12
+ # allowing us control if views/layouts are side loaded.
13
+ def _normalize_options(options)
14
+ super
15
+ options[:side_load_assets] = side_load_assets
16
+ options
14
17
  end
15
18
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Frails
4
+ module Utils
5
+ def self.camelize(term, first_letter = :upper, convert_slashes: true)
6
+ # If we are not converting slashes, and `first_letter` is `:lower`, this ensures that the
7
+ # first letter after a slash is not capitalized.
8
+ string = if !convert_slashes && first_letter == :lower
9
+ term.split('/').map { |str| str.camelize :lower }.join('/')
10
+ else
11
+ term.camelize first_letter
12
+ end
13
+
14
+ # Reverses the effect of ActiveSupport::Inflector.camelize converting slashes into `::`. If
15
+ # the keyword argument `convert_slashes:` is false (default: true), we can avoid
16
+ # converting slashes to `::`.
17
+ string.gsub!('::', '/') unless convert_slashes
18
+
19
+ string
20
+ end
21
+ end
22
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Frails
4
- VERSION = '0.4.0'
4
+ VERSION = '0.8.0'
5
5
  end
@@ -16,12 +16,7 @@ namespace :frails do
16
16
  puts 'Compiling Webpack...'
17
17
 
18
18
  ENV['RAILS_ENV'] ||= 'development'
19
- env = {
20
- 'RAILS_ASSET_HOST' => ActionController::Base.helpers.compute_asset_host,
21
- 'RAILS_RELATIVE_URL_ROOT' => ActionController::Base.relative_url_root
22
- }
23
-
24
- stdout, sterr, status = Open3.capture3(env, "yarn webpack --env #{ENV['RAILS_ENV']}")
19
+ stdout, sterr, status = Open3.capture3({}, "yarn webpack --env #{ENV['RAILS_ENV']}")
25
20
 
26
21
  if sterr == '' && status.success?
27
22
  puts 'Frails successfully compiled 🎉'
@@ -3,14 +3,24 @@
3
3
  "version": "0.3.0",
4
4
  "description": "A Modern [F]ront End on [Rails] and Webpack",
5
5
  "main": "index.js",
6
- "files": [
7
- "package"
8
- ],
9
6
  "repository": "git@github.com:joelmoss/frails.git",
10
7
  "author": "Joel Moss <joel@developwithstyle.com>",
11
8
  "license": "MIT",
9
+ "scripts": {
10
+ "test": "jest"
11
+ },
12
+ "jest": {
13
+ "clearMocks": true,
14
+ "testEnvironment": "node"
15
+ },
12
16
  "dependencies": {
13
17
  "glob": "^7.1.4",
14
18
  "lodash.has": "^4.5.2"
19
+ },
20
+ "devDependencies": {
21
+ "@babel/core": "^7.10.2",
22
+ "@babel/preset-env": "^7.10.2",
23
+ "babel-jest": "^26.0.1",
24
+ "jest": "^26.2.2"
15
25
  }
16
26
  }
@@ -0,0 +1,10 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`config config 1`] = `
4
+ Object {
5
+ "absolutePublicPath": "/Users/joelmoss/dev/frails/test/dummy/public/frails",
6
+ "appPath": "/Users/joelmoss/dev/frails/test/dummy/app",
7
+ "publicOutputPath": "frails",
8
+ "rootPath": "/Users/joelmoss/dev/frails/test/dummy",
9
+ }
10
+ `;
@@ -1,47 +1,57 @@
1
- const glob = require('glob')
2
- const path = require('path')
3
- const fs = require('fs')
1
+ const glob = require("glob");
2
+ const path = require("path");
3
+ const fs = require("fs");
4
4
 
5
- const appPath = path.join(process.cwd(), 'app')
5
+ const appPath = path.join(process.cwd(), "app");
6
6
 
7
7
  // Build entry points for HTML based components (components with an index.html.erb and index.css).
8
8
  //
9
9
  // NOTE: Adding new files will require a rebuild!
10
10
  const buildComponents = () => {
11
- const result = {}
11
+ const result = {};
12
12
 
13
13
  // Find HTML partials and add its corresponding index.css as an entry point if it exists.
14
- glob.sync(path.join(appPath, 'components/**/_index.html.erb')).forEach(paf => {
15
- paf = paf.replace('_index.html.erb', 'index.css')
14
+ glob
15
+ .sync(path.join(appPath, "components/**/_index.html.erb"))
16
+ .forEach(paf => {
17
+ paf = paf.replace("_index.html.erb", "index.css");
16
18
 
17
- if (!fs.existsSync(paf)) return
19
+ if (!fs.existsSync(paf)) return;
18
20
 
19
- const namespace = path.relative(path.join(appPath), path.dirname(paf))
20
- const name = path.join(namespace, path.basename(paf, path.extname(paf)))
21
+ const namespace = path.relative(path.join(appPath), path.dirname(paf));
22
+ const name = path.join(namespace, path.basename(paf, path.extname(paf)));
21
23
 
22
- result[name] = paf
23
- })
24
+ result[name] = paf;
25
+ });
24
26
 
25
27
  // Find components with no JSX and HTML index files, and add its corresponding index.css as an
26
28
  // entry point if it exists. These will be components that render from within the Ruby class
27
29
  // itself.
28
- glob.sync(path.join(appPath, 'components/**/*_component.rb')).forEach(paf => {
29
- css_paf = paf.replace('_component.rb', '/index.css')
30
- html_paf = paf.replace('_component.rb', '/_index.html.erb')
31
- jsx_paf = paf.replace('_component.rb', '/index.entry.jsx')
30
+ glob.sync(path.join(appPath, "components/**/*_component.rb")).forEach(paf => {
31
+ css_paf = paf.replace("_component.rb", "/index.css");
32
+ html_paf = paf.replace("_component.rb", "/_index.html.erb");
33
+ jsx_paf = paf.replace("_component.rb", "/index.entry.jsx");
32
34
 
33
35
  // Ignore if we have a JSX or HTML file, or no CSS file.
34
- if (fs.existsSync(jsx_paf) || fs.existsSync(html_paf) || !fs.existsSync(css_paf)) return
36
+ if (
37
+ fs.existsSync(jsx_paf) ||
38
+ fs.existsSync(html_paf) ||
39
+ !fs.existsSync(css_paf)
40
+ )
41
+ return;
35
42
 
36
- const namespace = path.relative(path.join(appPath), path.dirname(css_paf))
37
- const name = path.join(namespace, path.basename(css_paf, path.extname(css_paf)))
43
+ const namespace = path.relative(path.join(appPath), path.dirname(css_paf));
44
+ const name = path.join(
45
+ namespace,
46
+ path.basename(css_paf, path.extname(css_paf))
47
+ );
38
48
 
39
- result[name] = css_paf
40
- })
49
+ result[name] = css_paf;
50
+ });
41
51
 
42
- return result
43
- }
52
+ return result;
53
+ };
44
54
 
45
55
  module.exports = {
46
56
  entry: { ...buildComponents() }
47
- }
57
+ };
@@ -0,0 +1,38 @@
1
+ const spawnSync = require("child_process").spawnSync;
2
+ const path = require("path");
3
+
4
+ const isTest = process.env.NODE_ENV === "test";
5
+ const configFromRails = railsRun("print Frails.config_as_json");
6
+ const rootPath = isTest
7
+ ? path.join(process.cwd(), "test/dummy")
8
+ : process.cwd();
9
+
10
+ module.exports = {
11
+ ...configFromRails,
12
+
13
+ rootPath,
14
+ appPath: path.join(rootPath, "app"),
15
+ absolutePublicPath: path.join(
16
+ rootPath,
17
+ "public",
18
+ configFromRails.publicOutputPath
19
+ ),
20
+ };
21
+
22
+ function railsRun(argument) {
23
+ if (isTest) {
24
+ return {
25
+ publicOutputPath: "frails",
26
+ };
27
+ }
28
+
29
+ const result = spawnSync("./bin/rails", ["runner", argument]);
30
+
31
+ if (result.status === 0) {
32
+ return JSON.parse(result.stdout.toString());
33
+ } else if (result.signal !== null) {
34
+ throw `Rails runner was terminated with signal: ${result.signal}`;
35
+ } else {
36
+ throw `Rails runner failed with '${result.error.toString()}'.`;
37
+ }
38
+ }