inesita 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +9 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +7 -1
- data/LICENSE.md +21 -0
- data/README.md +5 -3
- data/Rakefile +17 -0
- data/inesita.gemspec +7 -5
- data/lib/inesita.rb +28 -1
- data/lib/inesita/app_files_listener.rb +42 -0
- data/lib/inesita/cli.rb +1 -0
- data/lib/inesita/cli/build.rb +33 -10
- data/lib/inesita/cli/new.rb +2 -5
- data/lib/inesita/cli/server.rb +1 -2
- data/lib/inesita/cli/template/Gemfile.tt +9 -6
- data/lib/inesita/cli/template/app/application.js.rb.tt +2 -4
- data/lib/inesita/cli/template/app/components/home.rb.tt +1 -1
- data/lib/inesita/cli/template/app/index.html.slim.tt +1 -9
- data/lib/inesita/cli/template/app/{components/layout.rb.tt → layout.rb.tt} +1 -1
- data/lib/inesita/cli/template/config.ru.tt +6 -1
- data/lib/inesita/cli/template/static/inesita-rb.png +0 -0
- data/lib/inesita/config.rb +8 -0
- data/lib/inesita/live_reload.rb +47 -0
- data/lib/inesita/minify.rb +34 -0
- data/lib/inesita/server.rb +56 -49
- data/lib/rubame.rb +156 -0
- data/opal/inesita.rb +12 -2
- data/opal/inesita/application.rb +37 -15
- data/opal/inesita/component.rb +37 -36
- data/opal/inesita/{component_withs.rb → component_properties.rb} +1 -1
- data/opal/inesita/component_virtual_dom_extension.rb +22 -0
- data/opal/inesita/error.rb +4 -0
- data/opal/inesita/layout.rb +1 -3
- data/opal/inesita/live_reload.rb +79 -0
- data/opal/inesita/router.rb +24 -20
- data/opal/inesita/routes.rb +16 -13
- data/opal/inesita/store.rb +6 -1
- data/spec/lib/nil_spec.rb +7 -0
- data/spec/lib/spec_helper.rb +1 -0
- data/spec/opal/application_spec.rb +48 -0
- data/spec/opal/component_spec.rb +56 -0
- data/spec/opal/layout_spec.rb +9 -0
- data/spec/opal/router_spec.rb +60 -0
- data/spec/opal/spec_helper.rb +5 -0
- data/spec/opal/store_spec.rb +17 -0
- metadata +69 -16
- data/TODO.md +0 -8
- data/opal/inesita/js_helpers.rb +0 -27
- data/test.rb +0 -27
@@ -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
|
data/opal/inesita/layout.rb
CHANGED
@@ -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
|
data/opal/inesita/router.rb
CHANGED
@@ -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
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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 '
|
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
|
-
|
39
|
-
|
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[
|
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 { |
|
59
|
+
@routes.routes.find { |r| r[:name] == name }
|
56
60
|
when Object
|
57
|
-
@routes.routes.find { |
|
61
|
+
@routes.routes.find { |r| r[:component] == name }
|
58
62
|
end
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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)
|
data/opal/inesita/routes.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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 << '$'
|
data/opal/inesita/store.rb
CHANGED
@@ -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,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
|