frails 0.4.0 → 0.8.0

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