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
@@ -0,0 +1,34 @@
1
+ module Inesita
2
+ module Minify
3
+ module_function
4
+
5
+ def html(source)
6
+ if defined?(HtmlCompressor) && defined?(HtmlCompressor::Compressor)
7
+ HtmlCompressor::Compressor.new.compress(source)
8
+ else
9
+ source
10
+ end
11
+ end
12
+
13
+ def js(source)
14
+ if defined?(Uglifier)
15
+ Uglifier.compile(source)
16
+ else
17
+ source
18
+ end
19
+ end
20
+
21
+ def css(source)
22
+ if defined?(Sass) && defined?(Sass::Engine)
23
+ Sass::Engine.new(source,
24
+ syntax: :scss,
25
+ cache: false,
26
+ read_cache: false,
27
+ style: :compressed
28
+ ).render
29
+ else
30
+ source
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,74 +1,81 @@
1
1
  require 'rack/rewrite'
2
2
 
3
3
  module Inesita
4
- module Server
5
- SOURCE_MAP_PREFIX = '/__OPAL_MAPS__'
6
- ASSETS_PREFIX = '/__ASSETS__'
4
+ module SprocketsContext
5
+ def asset_path(path, _options = {})
6
+ path
7
+ end
8
+ end
9
+
10
+ class Server
11
+ attr_reader :assets_app
12
+
13
+ def initialize
14
+ @assets_app = create_assets_app
15
+ @source_maps_app = create_source_maps_app
16
+ @app = create_app
17
+ Inesita.assets_code = assets_code
18
+ end
7
19
 
8
- module_function
20
+ def assets_code
21
+ assets_prefix = Inesita.env == :development ? Config::ASSETS_PREFIX : nil
22
+ %(
23
+ <link rel="stylesheet" type="text/css" href="#{assets_prefix}/stylesheet.css">
24
+ #{Opal::Sprockets.javascript_include_tag('application', sprockets: @assets_app, prefix: assets_prefix, debug: Inesita.env == :development)}
25
+ )
26
+ end
27
+
28
+ def create_app
29
+ assets_app = @assets_app
30
+ source_maps_app = @source_maps_app
31
+
32
+ Rack::Builder.new do
33
+ use Rack::Static, :urls => [Config::STATIC_DIR]
9
34
 
10
- def assets
11
- Sprockets::Environment.new.tap do |s|
12
- # register engines
13
- s.register_engine '.slim', Slim::Template
14
- s.register_engine '.rb', Opal::Processor
35
+ use Rack::Rewrite do
36
+ rewrite(/^(?!#{Config::ASSETS_PREFIX}|#{Config::SOURCE_MAP_PREFIX}).*/, Config::ASSETS_PREFIX)
37
+ end
38
+
39
+ map Config::ASSETS_PREFIX do
40
+ run assets_app
41
+ end
42
+
43
+ map Config::SOURCE_MAP_PREFIX do
44
+ run source_maps_app
45
+ end
46
+ end
47
+ end
15
48
 
16
- # add folders
17
- s.append_path 'app'
49
+ def create_assets_app
50
+ Opal::Server.new do |s|
51
+ s.append_path Config::APP_DIR
18
52
 
19
- # add paths from opal
20
53
  Opal.paths.each do |p|
21
54
  s.append_path p
22
55
  end
23
56
 
24
- # add paths from rails-assets
25
57
  RailsAssets.load_paths.each do |p|
26
58
  s.append_path p
27
59
  end if defined?(RailsAssets)
28
60
 
29
- s.context_class.class_eval do
30
- def asset_path(path, options = {})
31
- $DEVELOPMENT_MODE ? "#{ASSETS_PREFIX}/#{path}" : path
32
- end
33
- end
34
- end
61
+ configure_sprockets(s.sprockets)
62
+ end.sprockets
35
63
  end
36
64
 
37
- def set_global_vars(assets, debug = false)
38
- $LOAD_ASSETS_CODE = Opal::Processor.load_asset_code(assets, 'application.js')
39
- if $DEVELOPMENT_MODE
40
- $SCRIPT_FILES = (assets['application.js'].dependencies + [assets['application.self.js']]).map(&:logical_path)
65
+ def configure_sprockets(sprockets)
66
+ sprockets.register_engine '.slim', Slim::Template
67
+ sprockets.context_class.class_eval do
68
+ include SprocketsContext
41
69
  end
42
70
  end
43
71
 
44
- def source_maps(sprockets)
45
- ::Opal::Sprockets::SourceMapHeaderPatch.inject!(SOURCE_MAP_PREFIX)
46
- Opal::SourceMapServer.new(sprockets, SOURCE_MAP_PREFIX)
72
+ def create_source_maps_app
73
+ ::Opal::Sprockets::SourceMapHeaderPatch.inject!(Config::SOURCE_MAP_PREFIX)
74
+ Opal::SourceMapServer.new(@assets_app, Config::SOURCE_MAP_PREFIX)
47
75
  end
48
76
 
49
- def development_mode
50
- $DEVELOPMENT_MODE = ENV['DEVELOPMENT_MODE'] || true
51
- end
52
-
53
- def create
54
- development_mode
55
- assets_app = assets
56
- source_maps_app = source_maps(assets_app)
57
- set_global_vars(assets_app, true)
58
-
59
- Rack::Builder.new do
60
- use Rack::Rewrite do
61
- rewrite %r[^(?!#{ASSETS_PREFIX}|#{SOURCE_MAP_PREFIX}).*], ASSETS_PREFIX
62
- end
63
-
64
- map ASSETS_PREFIX do
65
- run assets_app
66
- end
67
-
68
- map SOURCE_MAP_PREFIX do
69
- run source_maps_app
70
- end
71
- end
77
+ def call(env)
78
+ @app.call(env)
72
79
  end
73
80
  end
74
81
  end
data/lib/rubame.rb ADDED
@@ -0,0 +1,156 @@
1
+ require 'websocket'
2
+ require 'socket'
3
+
4
+ module Rubame
5
+ class Server
6
+ def initialize(host, port)
7
+ Socket.do_not_reverse_lookup
8
+ @hostname = host
9
+ @port = port
10
+
11
+ @reading = []
12
+ @writing = []
13
+
14
+ @clients = {} # Socket as key, and Client as value
15
+
16
+ @socket = TCPServer.new(@hostname, @port)
17
+ @reading.push @socket
18
+ end
19
+
20
+ def accept
21
+ socket = @socket.accept_nonblock
22
+ @reading.push socket
23
+ handshake = WebSocket::Handshake::Server.new
24
+ client = Rubame::Client.new(socket, handshake, self)
25
+
26
+ while line = socket.gets
27
+ client.handshake << line
28
+ break if client.handshake.finished?
29
+ end
30
+ if client.handshake.valid?
31
+ @clients[socket] = client
32
+ client.write handshake.to_s
33
+ client.opened = true
34
+ return client
35
+ else
36
+ close(client)
37
+ end
38
+ return nil
39
+ end
40
+
41
+ def read(client)
42
+
43
+ pairs = client.socket.recvfrom(2000)
44
+ messages = []
45
+
46
+ if pairs[0].length == 0
47
+ close(client)
48
+ else
49
+ client.frame << pairs[0]
50
+
51
+ while f = client.frame.next
52
+ if (f.type == :close)
53
+ close(client)
54
+ return messages
55
+ else
56
+ messages.push f
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ return messages
63
+
64
+ end
65
+
66
+ def close(client)
67
+ @reading.delete client.socket
68
+ @clients.delete client.socket
69
+ begin
70
+ client.socket.close
71
+ ensure
72
+ client.closed = true
73
+ end
74
+ end
75
+
76
+ def run(&blk)
77
+ readable, _writable = IO.select(@reading, @writing)
78
+
79
+ if readable
80
+ readable.each do |socket|
81
+ client = @clients[socket]
82
+ if socket == @socket
83
+ client = accept
84
+ else
85
+ msg = read(client)
86
+ client.messaged = msg
87
+ end
88
+
89
+ blk.call(client) if client and blk
90
+ end
91
+ end
92
+ end
93
+
94
+ def stop
95
+ @socket.close
96
+ end
97
+ end
98
+
99
+ class Client
100
+ attr_accessor :socket, :handshake, :frame, :opened, :messaged, :closed
101
+
102
+ def initialize(socket, handshake, server)
103
+ @socket = socket
104
+ @handshake = handshake
105
+ @frame = WebSocket::Frame::Incoming::Server.new(:version => @handshake.version)
106
+ @opened = false
107
+ @messaged = []
108
+ @closed = false
109
+ @server = server
110
+ end
111
+
112
+ def write(data)
113
+ @socket.write data
114
+ end
115
+
116
+ def send(data)
117
+ frame = WebSocket::Frame::Outgoing::Server.new(:version => @handshake.version, :data => data, :type => :text)
118
+ begin
119
+ @socket.write frame
120
+ @socket.flush
121
+ rescue
122
+ @server.close(self) unless @closed
123
+ end
124
+ end
125
+
126
+ def onopen(&blk)
127
+ if @opened
128
+ begin
129
+ blk.call
130
+ ensure
131
+ @opened = false
132
+ end
133
+ end
134
+ end
135
+
136
+ def onmessage(&blk)
137
+ if @messaged.size > 0
138
+ begin
139
+ @messaged.each do |x|
140
+ blk.call(x.to_s)
141
+ end
142
+ ensure
143
+ @messaged = []
144
+ end
145
+ end
146
+ end
147
+
148
+ def onclose(&blk)
149
+ if @closed
150
+ begin
151
+ blk.call
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
data/opal/inesita.rb CHANGED
@@ -1,6 +1,16 @@
1
+ require 'opal'
2
+ require 'js'
3
+ require 'console'
4
+
5
+ require 'browser'
6
+ require 'browser/history'
7
+ require 'browser/animation_frame'
8
+
1
9
  require 'virtual_dom'
2
- require 'inesita/js_helpers'
3
- require 'inesita/component_withs'
10
+
11
+ require 'inesita/error'
12
+ require 'inesita/component_virtual_dom_extension'
13
+ require 'inesita/component_properties'
4
14
  require 'inesita/component'
5
15
  require 'inesita/routes'
6
16
  require 'inesita/router'
@@ -2,27 +2,49 @@ module Inesita
2
2
  class Application
3
3
  include Inesita::Component
4
4
 
5
- attr_reader :root
5
+ def initialize(options = {})
6
+ setup_router(options[:router])
7
+ setup_layout(options[:layout])
8
+ setup_root
9
+ setup_store(options[:store])
10
+ end
6
11
 
7
- def initialize(options)
8
- router = options[:router]
9
- layout = options[:layout]
10
- store = options[:store]
12
+ def render
13
+ component @root
14
+ end
11
15
 
12
- raise 'Router missing' unless router
13
- raise "Invalid #{router} class, should mixin Inesita::Router" unless router.include?(Inesita::Router)
14
- raise "Invalid #{layout} class, should mixin Inesita::Layout" unless layout.include?(Inesita::Layout)
15
- raise "Invalid #{store} class, should mixin Inesita::Store" unless store.include?(Inesita::Store)
16
+ private
16
17
 
17
- @router = router.new
18
- @layout = layout.new if layout
18
+ def setup_router(router)
19
+ if router
20
+ fail Error, "Invalid #{router} class, should mixin Inesita::Router" unless router.include?(Inesita::Router)
21
+ @router = router.new
22
+ else
23
+ @router = Class.new { define_method(:method_missing) { fail 'Router missing' } }.new
24
+ end
25
+ end
19
26
 
20
- @root = @layout ? @layout : @router
21
- @store = store.new.with_root_component(@root).store if store
27
+ def setup_layout(layout)
28
+ if layout
29
+ fail Error, "Invalid #{layout} class, should mixin Inesita::Layout" unless layout.include?(Inesita::Layout)
30
+ @layout = layout.new
31
+ end
22
32
  end
23
33
 
24
- def render
25
- component root
34
+ def setup_root
35
+ if @layout
36
+ @root = @layout
37
+ elsif @router
38
+ @root = @router
39
+ else
40
+ fail Error, 'Router or Layout not found!'
41
+ end
42
+ end
43
+
44
+ def setup_store(store)
45
+ return unless store
46
+ fail Error, "Invalid #{store} class, should mixin Inesita::Store" unless store.include?(Inesita::Store)
47
+ @store = store.new.with_root_component(@root).store
26
48
  end
27
49
  end
28
50
  end
@@ -1,36 +1,64 @@
1
1
  module Inesita
2
2
  module Component
3
3
  include VirtualDOM::DOM
4
- include ComponentWiths
4
+ include ComponentProperties
5
+ include ComponentVirtualDomExtension
6
+
7
+ def render
8
+ fail Error, "Implement #render in #{self.class} component"
9
+ end
5
10
 
6
11
  def mount_to(element)
12
+ fail Error, "Can't mount #{self.class}, target element not found!" unless element
7
13
  @root_component = self
8
14
  @virtual_dom = render_virtual_dom
9
15
  @root_node = VirtualDOM.create(@virtual_dom)
10
16
  element.inner_dom = @root_node
17
+ @root_component.call_after_render
18
+ self
11
19
  end
12
20
 
13
21
  def render_if_root
14
- if @virtual_dom && @root_node
15
- new_virtual_dom = render_virtual_dom
16
- diff = VirtualDOM.diff(@virtual_dom, new_virtual_dom)
17
- VirtualDOM.patch(@root_node, diff)
18
- @virtual_dom = new_virtual_dom
19
- end
22
+ return unless @virtual_dom && @root_node
23
+ new_virtual_dom = render_virtual_dom
24
+ diff = VirtualDOM.diff(@virtual_dom, new_virtual_dom)
25
+ VirtualDOM.patch(@root_node, diff)
26
+ @virtual_dom = new_virtual_dom
20
27
  end
21
28
 
22
29
  def render_virtual_dom
30
+ @after_render_callbacks = []
31
+ @root_component.add_after_render(method(:after_render)) if respond_to?(:after_render)
32
+ @cache_component_counter = 0
23
33
  @__virtual_nodes__ = []
24
34
  render
25
35
  if @__virtual_nodes__.length == 1
26
36
  @__virtual_nodes__.first
27
37
  else
28
- VirtualDOM::VirtualNode.new('div', {}, @__virtual_nodes__).vnode
38
+ VirtualDOM::VirtualNode.new('div', {}, @__virtual_nodes__).to_n
29
39
  end
30
40
  end
31
41
 
42
+ def add_after_render(block)
43
+ @after_render_callbacks << block
44
+ end
45
+
46
+ def call_after_render
47
+ @after_render_callbacks.reverse_each(&:call)
48
+ end
49
+
32
50
  def update_dom
33
- @root_component.render_if_root
51
+ $console.warn "Use 'render!' instead of 'update_dom'"
52
+ render!
53
+ end
54
+
55
+ def render!
56
+ animation_frame do
57
+ if @root_component
58
+ @root_component.render_if_root
59
+ @root_component.call_after_render
60
+ end
61
+ end
34
62
  end
35
63
 
36
64
  def cache_component(component, &block)
@@ -39,32 +67,5 @@ module Inesita
39
67
  @cache_component_counter += 1
40
68
  @cache_component["#{component}-#{@cache_component_counter}"] || @cache_component["#{component}-#{@cache_component_counter}"] = block.call
41
69
  end
42
-
43
- def a(params, &block)
44
- params = { onclick: -> { @router.handle_link(params[:href]) } }.merge(params) if params[:href] && @router
45
- @__virtual_nodes__ ||= []
46
- if block
47
- current = @__virtual_nodes__
48
- @__virtual_nodes__ = []
49
- result = block.call
50
- vnode = VirtualDOM::VirtualNode.new('a', process_params(params), @__virtual_nodes__.count == 0 ? result : @__virtual_nodes__).vnode
51
- @__virtual_nodes__ = current
52
- else
53
- vnode = VirtualDOM::VirtualNode.new('a', process_params(params), []).vnode
54
- end
55
- @__virtual_nodes__ << vnode
56
- vnode
57
- end
58
-
59
- def component(comp, opts = {})
60
- fail "Component is nil in #{self.class} class" if comp.nil?
61
- @__virtual_nodes__ ||= []
62
- @__virtual_nodes__ << cache_component(comp) do
63
- (comp.is_a?(Class) ? comp.new : comp)
64
- .with_root_component(@root_component)
65
- .with_router(@router)
66
- .with_store(@store)
67
- end.with_props(opts[:props] || {}).render_virtual_dom
68
- end
69
70
  end
70
71
  end