inesita 0.2.3 → 0.3.0

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