inesita 0.2.3 → 0.3.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +9 -0
  3. data/CHANGELOG.md +9 -0
  4. data/Gemfile +7 -1
  5. data/LICENSE.md +21 -0
  6. data/README.md +5 -3
  7. data/Rakefile +17 -0
  8. data/inesita.gemspec +7 -5
  9. data/lib/inesita.rb +28 -1
  10. data/lib/inesita/app_files_listener.rb +42 -0
  11. data/lib/inesita/cli.rb +1 -0
  12. data/lib/inesita/cli/build.rb +33 -10
  13. data/lib/inesita/cli/new.rb +2 -5
  14. data/lib/inesita/cli/server.rb +1 -2
  15. data/lib/inesita/cli/template/Gemfile.tt +9 -6
  16. data/lib/inesita/cli/template/app/application.js.rb.tt +2 -4
  17. data/lib/inesita/cli/template/app/components/home.rb.tt +1 -1
  18. data/lib/inesita/cli/template/app/index.html.slim.tt +1 -9
  19. data/lib/inesita/cli/template/app/{components/layout.rb.tt → layout.rb.tt} +1 -1
  20. data/lib/inesita/cli/template/config.ru.tt +6 -1
  21. data/lib/inesita/cli/template/static/inesita-rb.png +0 -0
  22. data/lib/inesita/config.rb +8 -0
  23. data/lib/inesita/live_reload.rb +47 -0
  24. data/lib/inesita/minify.rb +34 -0
  25. data/lib/inesita/server.rb +56 -49
  26. data/lib/rubame.rb +156 -0
  27. data/opal/inesita.rb +12 -2
  28. data/opal/inesita/application.rb +37 -15
  29. data/opal/inesita/component.rb +37 -36
  30. data/opal/inesita/{component_withs.rb → component_properties.rb} +1 -1
  31. data/opal/inesita/component_virtual_dom_extension.rb +22 -0
  32. data/opal/inesita/error.rb +4 -0
  33. data/opal/inesita/layout.rb +1 -3
  34. data/opal/inesita/live_reload.rb +79 -0
  35. data/opal/inesita/router.rb +24 -20
  36. data/opal/inesita/routes.rb +16 -13
  37. data/opal/inesita/store.rb +6 -1
  38. data/spec/lib/nil_spec.rb +7 -0
  39. data/spec/lib/spec_helper.rb +1 -0
  40. data/spec/opal/application_spec.rb +48 -0
  41. data/spec/opal/component_spec.rb +56 -0
  42. data/spec/opal/layout_spec.rb +9 -0
  43. data/spec/opal/router_spec.rb +60 -0
  44. data/spec/opal/spec_helper.rb +5 -0
  45. data/spec/opal/store_spec.rb +17 -0
  46. metadata +69 -16
  47. data/TODO.md +0 -8
  48. data/opal/inesita/js_helpers.rb +0 -27
  49. data/test.rb +0 -27
@@ -1,5 +1,5 @@
1
1
  module Inesita
2
- module ComponentWiths
2
+ module ComponentProperties
3
3
  attr_reader :root_component
4
4
  def with_root_component(component)
5
5
  @root_component = component
@@ -0,0 +1,22 @@
1
+ module Inesita
2
+ module ComponentVirtualDomExtension
3
+ def self.included(base)
4
+ base.alias_method :__a, :a
5
+ base.define_method(:a) do |params, &block|
6
+ params = { onclick: -> { @router.handle_link(params[:href]) } }.merge(params) if params[:href] && @router
7
+ __a(params, &block)
8
+ end
9
+ end
10
+
11
+ def component(comp, opts = {})
12
+ fail Error, "Component is nil in #{self.class} class" if comp.nil?
13
+ @__virtual_nodes__ ||= []
14
+ @__virtual_nodes__ << cache_component(comp) do
15
+ (comp.is_a?(Class) ? comp.new : comp)
16
+ .with_root_component(@root_component)
17
+ .with_router(@router)
18
+ .with_store(@store)
19
+ end.with_props(opts[:props] || {}).render_virtual_dom
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ module Inesita
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -1,8 +1,6 @@
1
1
  module Inesita
2
2
  module Layout
3
- def self.included(base)
4
- base.include(Component)
5
- end
3
+ include Component
6
4
 
7
5
  def outlet
8
6
  @router
@@ -0,0 +1,79 @@
1
+ require 'inesita'
2
+
3
+ module Inesita
4
+ WebSocket = JS.global.JS[:WebSocket]
5
+ Document = JS.global.JS[:document]
6
+ Head = Document.JS[:head]
7
+ Window = JS.global
8
+
9
+ module Component
10
+ alias_method :old_mount_to, :mount_to
11
+ def mount_to(element)
12
+ Window.JS.addEventListener('inesita:refresh', -> { render! }, false)
13
+ old_mount_to(element)
14
+ end
15
+ end
16
+
17
+ class LiveReload
18
+ def initialize
19
+ connect
20
+ end
21
+
22
+ def connect
23
+ ws = `new WebSocket('ws://0.0.0.0:23654')`
24
+ ws.JS[:onmessage] = ->(e) { on_file_change(e.JS[:data]) }
25
+ ws.JS[:onclose] = -> { reconnect }
26
+ end
27
+
28
+ def reconnect
29
+ JS.setTimeout(-> { connect }, 1000)
30
+ end
31
+
32
+ def on_file_change(filename)
33
+ prefix, mod, ext = filename.split('|')
34
+ filename = "#{prefix}/#{mod}.self.#{ext}"
35
+ case ext
36
+ when 'js'
37
+ replace_js(filename, mod)
38
+ when 'css'
39
+ replace_css(filename)
40
+ else
41
+ fail Error, "Don't know how to reload #{ext} file!"
42
+ end
43
+ end
44
+
45
+ def replace_js(filename, mod)
46
+ s = create_element(:script, type: 'text/javascript', src: filename, onload: lambda do
47
+ Opal.load(mod)
48
+ Window.JS.dispatchEvent(`new Event('inesita:refresh')`)
49
+ end)
50
+ replace_or_append(s, 'script') { |t| t.JS[:src].match(filename) }
51
+ end
52
+
53
+ def replace_css(filename)
54
+ s = create_element(:link, rel: 'stylesheet', type: 'text/css', href: filename)
55
+ replace_or_append(s, 'link') { |t| t.JS[:href].match(filename) }
56
+ end
57
+
58
+ def create_element(name, attrs = {})
59
+ s = Document.JS.createElement(name)
60
+ s.JS[:onload] = attrs.delete(:onload)
61
+ attrs.each do |k, v|
62
+ s.JS.setAttribute(k, v)
63
+ end
64
+ s
65
+ end
66
+
67
+ def replace_or_append(tag, tags, &block)
68
+ tags = Document.JS.getElementsByTagName(tags)
69
+ tags.JS[:length].times do |i|
70
+ next unless block.call(tags.JS.item(i))
71
+ Head.JS.replaceChild(tag, tags.JS.item(i))
72
+ return
73
+ end
74
+ Head.JS.appendChild(tag)
75
+ end
76
+ end
77
+ end
78
+
79
+ Inesita::LiveReload.new
@@ -1,19 +1,22 @@
1
1
  module Inesita
2
2
  module Router
3
- include Inesita::JSHelpers
4
3
  include Inesita::Component
5
4
 
6
5
  attr_reader :params
7
6
 
8
7
  def initialize
9
- on_popstate method(:update_dom)
10
- on_hashchange method(:update_dom)
11
8
  @routes = Routes.new
12
9
  @url_params = parse_url_params
10
+ fail Error, 'Add #routes method to router!' unless respond_to?(:routes)
13
11
  routes
12
+ fail Error, 'Add #route to your #routes method!' if @routes.routes.empty?
13
+ add_js_listeners
14
14
  end
15
15
 
16
- def routes; end
16
+ def add_js_listeners
17
+ $window.on(:popstate) { render! }
18
+ $window.on(:hashchange) { render! }
19
+ end
17
20
 
18
21
  def route(*params, &block)
19
22
  @routes.route(*params, &block)
@@ -21,13 +24,13 @@ module Inesita
21
24
 
22
25
  def find_component
23
26
  @routes.routes.each do |route|
24
- if params = path.match(route[:regex])
25
- @params = @url_params.merge(Hash[route[:params].zip(params[1..-1])])
26
- @component_props = route[:component_props]
27
- return route[:component]
28
- end
27
+ params = path.match(route[:regex])
28
+ next unless params
29
+ @params = @url_params.merge(Hash[route[:params].zip(params[1..-1])])
30
+ @component_props = route[:component_props]
31
+ return route[:component]
29
32
  end
30
- fail 'not_found'
33
+ fail Error, "Can't find route for url"
31
34
  end
32
35
 
33
36
  def render
@@ -35,16 +38,17 @@ module Inesita
35
38
  end
36
39
 
37
40
  def handle_link(path)
38
- push_state path
39
- update_dom
41
+ $window.history.push(path)
42
+ render!
40
43
  false
41
44
  end
42
45
 
43
46
  def parse_url_params
44
47
  params = {}
48
+ url_query = $window.location.query.to_s
45
49
  url_query[1..-1].split('&').each do |param|
46
50
  key, value = param.split('=')
47
- params[decode_uri(key)] = decode_uri(value)
51
+ params[key.decode_uri_component] = value.decode_uri_component
48
52
  end unless url_query.length == 0
49
53
  params
50
54
  end
@@ -52,15 +56,15 @@ module Inesita
52
56
  def url_for(name)
53
57
  route = case name
54
58
  when String
55
- @routes.routes.find { |route| route[:name] == name }
59
+ @routes.routes.find { |r| r[:name] == name }
56
60
  when Object
57
- @routes.routes.find { |route| route[:component] == name }
61
+ @routes.routes.find { |r| r[:component] == name }
58
62
  end
59
- if route
60
- route[:path]
61
- else
62
- raise "Route '#{name}' not found."
63
- end
63
+ route ? route[:path] : fail(Error, "Route '#{name}' not found.")
64
+ end
65
+
66
+ def path
67
+ $window.location.path
64
68
  end
65
69
 
66
70
  def current_url?(name)
@@ -15,7 +15,13 @@ module Inesita
15
15
  component_props = params.last[:props]
16
16
 
17
17
  add_subroutes(path, &block) if block_given?
18
- add_route(name, path, component, component_props) if component
18
+ validate_component(component)
19
+ add_route(name, path, component, component_props)
20
+ end
21
+
22
+ def validate_component(component)
23
+ fail Error, 'Component not exists' unless component
24
+ fail Error, "Invalid #{component} class, should mixin Inesita::Component" unless component.include?(Inesita::Component)
19
25
  end
20
26
 
21
27
  def add_route(name, path, component, component_props)
@@ -23,7 +29,7 @@ module Inesita
23
29
  path: path,
24
30
  component: component,
25
31
  component_props: component_props,
26
- name: name || component.to_s.downcase
32
+ name: name || component.to_s.gsub(/(.)([A-Z])/, '\1_\2').downcase
27
33
  }.merge(params_and_regex(path))
28
34
  end
29
35
 
@@ -37,18 +43,15 @@ module Inesita
37
43
  regex = ['^']
38
44
  params = []
39
45
  parts = path.split('/')
40
- if parts.empty?
46
+ regex << '\/' if parts.empty?
47
+ parts.each do |part|
48
+ next if part.empty?
41
49
  regex << '\/'
42
- else
43
- parts.each do |part|
44
- next if part.empty?
45
- regex << '\/'
46
- if part[0] == ':'
47
- params << part[1..-1]
48
- regex << '([^\/]+)'
49
- else
50
- regex << part
51
- end
50
+ if part[0] == ':'
51
+ params << part[1..-1]
52
+ regex << '([^\/]+)'
53
+ else
54
+ regex << part
52
55
  end
53
56
  end
54
57
  regex << '$'
@@ -5,7 +5,12 @@ module Inesita
5
5
  end
6
6
 
7
7
  def update_dom
8
- root_component.update_dom
8
+ $console.warn "Use 'render!' instead of 'update_dom'"
9
+ render!
10
+ end
11
+
12
+ def render!
13
+ root_component.render!
9
14
  end
10
15
 
11
16
  attr_reader :root_component
@@ -0,0 +1,7 @@
1
+ require 'lib/spec_helper'
2
+
3
+ describe do
4
+ it 'nil is nil' do
5
+ expect(nil).to be_nil
6
+ end
7
+ end
@@ -0,0 +1 @@
1
+ include RSpec
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe Inesita::Application do
4
+ let(:application) { Inesita::Application }
5
+ let(:layout) { Class.new { include Inesita::Layout } }
6
+ let(:store) { Class.new { include Inesita::Store } }
7
+ let(:router) do
8
+ Class.new do
9
+ include Inesita::Router
10
+
11
+ class TestComponent
12
+ include Inesita::Component
13
+ end
14
+
15
+ def routes
16
+ route '/', to: TestComponent
17
+ end
18
+ end
19
+ end
20
+
21
+ it 'should respond to #render' do
22
+ expect(application.new(router: router)).to respond_to(:render)
23
+ end
24
+
25
+ it 'should fail with wrong :router class' do
26
+ expect { application.new(router: Class) }.to raise_error Inesita::Error
27
+ end
28
+
29
+ it 'should not fail with :router class' do
30
+ expect { application.new(router: router) }.not_to raise_error
31
+ end
32
+
33
+ it 'should fail with wrong :layout class' do
34
+ expect { application.new(layout: Class) }.to raise_error Inesita::Error
35
+ end
36
+
37
+ it 'should not fail with :layout class' do
38
+ expect { application.new(layout: layout) }.not_to raise_error
39
+ end
40
+
41
+ it 'should fail with wrong :store class' do
42
+ expect { application.new(store: Class) }.to raise_error Inesita::Error
43
+ end
44
+
45
+ it 'should not fail with :layout class' do
46
+ expect { application.new(router: router) }.not_to raise_error
47
+ end
48
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe Inesita::Router do
4
+ let(:empty_component) { Class.new { include Inesita::Component }}
5
+ let(:component) do
6
+ Class.new do
7
+ include Inesita::Component
8
+
9
+ def render
10
+ h1 class: 'test' do
11
+ 'Test'
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ let(:nested_component) do
18
+ Class.new do
19
+ include Inesita::Component
20
+
21
+ Inner = Class.new do
22
+ include Inesita::Component
23
+
24
+ def render
25
+ div do
26
+ 'Inner'
27
+ end
28
+ end
29
+ end
30
+
31
+ def render
32
+ h1 class: 'test' do
33
+ component Inner
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ let(:element) do
40
+ $document.create_element('div')
41
+ end
42
+
43
+ it 'should fail when no #render method' do
44
+ expect { empty_component.new.mount_to(element) }.to raise_error Inesita::Error
45
+ end
46
+
47
+ it 'should render html' do
48
+ component.new.mount_to(element)
49
+ expect(element.inner_html).to eq '<h1 class="test">Test</h1>'
50
+ end
51
+
52
+ it 'should render html with nested components' do
53
+ nested_component.new.mount_to(element)
54
+ expect(element.inner_html).to eq '<h1 class="test"><div>Inner</div></h1>'
55
+ end
56
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Inesita::Layout do
4
+ let(:layout) { Class.new { include Inesita::Layout } }
5
+
6
+ it 'should respond to #outlet' do
7
+ expect(layout.new).to respond_to(:outlet)
8
+ end
9
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe Inesita::Router do
4
+ let(:wrong_router) { Class.new { include Inesita::Router } }
5
+ let(:empty_router) { Class.new { include Inesita::Router; def routes; end } }
6
+ let(:router) do
7
+ Class.new do
8
+ include Inesita::Router
9
+
10
+ class TestComponent
11
+ include Inesita::Component
12
+ end
13
+
14
+ class OtherTestComponent
15
+ include Inesita::Component
16
+ end
17
+
18
+ def routes
19
+ route '/', to: TestComponent
20
+ route '/other', to: OtherTestComponent
21
+ end
22
+ end
23
+ end
24
+
25
+ it 'should fail without routes' do
26
+ expect { wrong_router.new }.to raise_error Inesita::Error
27
+ end
28
+
29
+ it 'should fail with empty routes' do
30
+ expect { empty_router.new }.to raise_error Inesita::Error
31
+ end
32
+
33
+ it 'should not fail with routes' do
34
+ expect { router.new }.not_to raise_error
35
+ end
36
+
37
+ describe '#url_for' do
38
+ it 'should return url for component name' do
39
+ expect(router.new.url_for(:test_component)).to eq '/'
40
+ end
41
+
42
+ it 'should return url for component name' do
43
+ expect(router.new.url_for(:other_test_component)).to eq '/other'
44
+ end
45
+
46
+ it 'should fail when no route for component' do
47
+ expect { router.new.url_for(:nothing) }.to raise_error Inesita::Error
48
+ end
49
+ end
50
+
51
+ describe '#current_url?' do
52
+ it 'should return true for current url' do
53
+ expect(router.new.current_url?(:test_component)).to eq true
54
+ end
55
+
56
+ it 'should return false for current url' do
57
+ expect(router.new.current_url?(:other_test_component)).to eq false
58
+ end
59
+ end
60
+ end