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