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.
- 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
|