porous 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 605209154f6a44bc2c7a84934bf661de4c721a2a5ed5f3b9c21556064ac1672e
4
- data.tar.gz: 27eb154e0be5f0b524e723355c1544541b26207f36e49eb372cea7d06e24cdd5
3
+ metadata.gz: 87cf02a737a26d181f89e3ee42f73574a30473e490b4ce045d76960e3dbc6fa5
4
+ data.tar.gz: 679a28921b7fc66630d2ec21b75543cd7dc4906d3f77230a052ff9fb08ab1281
5
5
  SHA512:
6
- metadata.gz: f4244bbc93b9c53b55e486d52420653ce8ffd5ee5e8d642b91e25aafc439ea2c48b5622c84167083b93c05ef424f5ea2b5964b7a4696665e92ffab68d13a43b9
7
- data.tar.gz: 58e28a76335c29b19f57c8b68e9c1c6bd00136b753025b230bcabf4a041a872e0efe8a8ebf233a15c7249de018662f4de9c240b27c10701989faeb71231bc866
6
+ metadata.gz: 7e25b7156a1e9d10c8d66ea198be3ddb3da0a87aa904519b6fbe0c71006e13d33f5840c051114c7a8e8506ce73f033492fb1bc4089506534759b8bc1a86d18e6
7
+ data.tar.gz: 8da9145c152a0d93d0a2d182f3ade0da334dc9bb898a75483675173f341a3ea42e5fcd1bc8e16b5e949e92b283fc1f845f7772c3fd19e193283d5338a8acd557
data/CHANGELOG.md CHANGED
@@ -1,7 +1,21 @@
1
1
  ## [Planned]
2
2
 
3
- - WebSockets support
4
3
  - Production mode
4
+ - Data Abstraction Layer / Object Relational Model
5
+ - Event Model
6
+ - Plugin / Extension system
7
+
8
+ - Frontend Extensions
9
+ - Tailwind CSS (tailwind-cli)
10
+
11
+ - Persistence Extensions
12
+ - Memory (default)
13
+ - Disk (file)
14
+ - Databases (SQLite, PostgreSQL)
15
+
16
+ ## [0.3.0] - 22 February 2024
17
+
18
+ - WebSockets support
5
19
 
6
20
  ## [0.2.0] - 18 February 2024
7
21
 
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # 🧽 Porous
2
2
 
3
- Porous is a web engine that uses isomorphic Ruby components to build a Progressive Web App. Its use is analogous to a web framework, but the approach is entirely different. You write only
3
+ Porous is a web engine that uses isomorphic Ruby components to build a Progressive Web App. Its use is analogous to a web framework, but the approach is entirely different. You write *only* the code that is *unique to your application* and the engine takes care of the rest!
4
4
 
5
- This project is a work-in-progress and is not yet even in the Proof of Concept phase. However, if you are interested in a full-stack, everything included solution, that only requires you to use one language (that is arguably easy and enjoyable to write) then feel free to follow this project.
5
+ This project is a **work-in-progress** and is not yet even in the Proof of Concept phase. However, if you are interested in a full-stack, everything included solution, that only requires you to use one language (that is arguably easy and enjoyable to write) then feel free to follow this project.
6
6
 
7
7
  The closest thing to this I could find was [Volt](https://github.com/voltrb/volt) or [Silica](https://github.com/youchan/silica), neither of which are active or match the overall development flow I'm looking for.
8
8
 
@@ -12,7 +12,7 @@ The closest thing to this I could find was [Volt](https://github.com/voltrb/volt
12
12
  - 🖥️ Server-side rendering (server responds with the entire initial page populated for SEO)
13
13
  - 💻 Client-side rendering (application bundle is served and interactions and subsequent pages are rendered client-side)
14
14
  - 🌄 Serves static files (from `static` folder)
15
- - 🔥 Hot reloading (via HTTP polling and browser refresh)
15
+ - 🔥 Hot reloading (via WebSocket push and browser refresh)
16
16
 
17
17
  ## Design
18
18
 
@@ -18,8 +18,7 @@ module Porous
18
18
  end
19
19
  meta name: 'description', content: props[:description] if props[:description]
20
20
 
21
- script src: '/static/dist/application.js'
22
- script src: '/static/dist/reload.js'
21
+ script src: '/static/dist/application.js', id: 'application'
23
22
  script src: 'https://cdn.tailwindcss.com'
24
23
  end
25
24
 
@@ -15,44 +15,7 @@ module Porous
15
15
  desc 'build', 'Build static assets'
16
16
  def build
17
17
  empty_directory 'static/dist', verbose: false, force: options[:force]
18
- transpile
19
- live_reload
18
+ Porous::Server::Builder.new.build
20
19
  end
21
-
22
- # rubocop:disable Metrics/BlockLength
23
- no_commands do
24
- def transpile
25
- components = Dir.glob(File.join('{components,pages}', '**', '*.rb')).map do |relative_path|
26
- "require '#{relative_path}'"
27
- end
28
- build_string = "require 'porous'; #{components.join ";"}".gsub '.rb', ''
29
- builder = Opal::Builder.new
30
- builder.build_str build_string, '(inline)'
31
- File.binwrite "#{Dir.pwd}/static/dist/application.js", builder.to_s
32
- end
33
-
34
- # rubocop:disable Metrics/MethodLength
35
- def live_reload
36
- timestamp = Time.now.to_i.to_s
37
- File.write "#{Dir.pwd}/static/dist/timestamp", timestamp
38
- builder = Opal::Builder.new
39
- script = <<-BROWSER
40
- $document.ready do
41
- every 0.1 do
42
- Browser::HTTP.get('/static/dist/timestamp').then do |response|
43
- return unless response.success?
44
- timestamp = response.text.to_i
45
- TIMESTAMP ||= timestamp
46
- $document.location.reload if TIMESTAMP < timestamp
47
- end
48
- end
49
- end
50
- BROWSER
51
- builder.build_str script, '(inline)'
52
- File.binwrite "#{Dir.pwd}/static/dist/reload.js", builder.to_s
53
- end
54
- # rubocop:enable Metrics/MethodLength
55
- end
56
- # rubocop:enable Metrics/BlockLength
57
20
  end
58
21
  end
@@ -19,29 +19,34 @@ module Porous
19
19
  default: 'localhost',
20
20
  desc: 'The host address Porous will bind to'
21
21
 
22
- MONITORING = %w[components pages].freeze
22
+ def server # rubocop:todo Metrics/MethodLength
23
+ Agoo::Log.configure(dir: '',
24
+ console: true,
25
+ classic: true,
26
+ colorize: true,
27
+ states: {
28
+ INFO: true,
29
+ DEBUG: false,
30
+ connect: false,
31
+ request: false,
32
+ response: false,
33
+ eval: true,
34
+ push: true
35
+ })
23
36
 
24
- def server
25
- MONITORING.each { |path| FileUtils.mkdir_p path }
26
- build
27
- start_live_reload
28
- Rackup::Server.start environment: 'none', app: Porous::Server.new
29
- end
37
+ Agoo::Server.init 9292, Dir.pwd, thread_count: 1
38
+ Agoo::Server.use Rack::ContentLength
39
+ Agoo::Server.use Rack::Static, urls: ['/static']
40
+ Agoo::Server.use Rack::ShowExceptions
30
41
 
31
- no_commands do
32
- def start_live_reload
33
- opts = { only: /\.rb$/, relative: true }
34
- @listener = Listen.to(*MONITORING, opts) do |modified, added, _removed|
35
- # Load for server
36
- (modified + added).each do |file|
37
- load File.expand_path("#{Dir.pwd}/#{file}")
38
- end
39
- # Rebuild for browser
40
- Thread.new { build }
41
- end
42
- @listener.start
43
- at_exit { @listener.stop }
44
- end
42
+ # Socket Communication
43
+ $socket ||= Porous::Server::Socket.new
44
+ Agoo::Server.handle nil, '/connect', Porous::Server::Connect.new
45
+ # Server-Side Rendering
46
+ Agoo::Server.handle nil, '**', Porous::Server::Application.new
47
+ Agoo::Server.start
48
+ # Live Reload Builder
49
+ Server::Builder.new.build.start
45
50
  end
46
51
  end
47
52
  end
data/lib/porous/cli.rb CHANGED
@@ -3,6 +3,10 @@
3
3
  require 'thor'
4
4
 
5
5
  require 'porous'
6
+ require 'porous/server/builder'
7
+ require 'porous/server/socket'
8
+ require 'porous/server/connect'
9
+ require 'porous/server/application'
6
10
 
7
11
  require 'rack'
8
12
  require 'rackup/server'
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porous
4
+ module Server
5
+ class Application
6
+ MONITORING = %w[components pages].freeze
7
+
8
+ def call(env)
9
+ router = Porous::Router.new path: env['PATH_INFO'], query: env['QUERY_STRING']
10
+ route = router.find_route
11
+ page = route[:component].new(route[:params])
12
+
13
+ [200, { 'content-type' => 'text/html' }, [
14
+ Porous::Application.new(
15
+ title: page.page_title,
16
+ description: page.page_description,
17
+ path: env['PATH_INFO'],
18
+ query: env['QUERY_STRING']
19
+ ).to_s
20
+ ]]
21
+ rescue Porous::InvalidRouteError => e
22
+ [404, { 'content-type' => 'text/plain' }, ["404 Page not found\n", e.message]]
23
+ rescue Porous::Error => e
24
+ [500, { 'content-type' => 'text/plain' }, ["500 Internal Server Error\n", e.message]]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'opal/builder_scheduler/sequential'
4
+
5
+ module Porous
6
+ module Server
7
+ class Builder
8
+ def initialize
9
+ @build_queue = Queue.new
10
+ @last_build = nil
11
+ @latest_change = Dir.glob(File.join('**', '*.rb')).map { |f| File.mtime f }.max
12
+ end
13
+
14
+ def build
15
+ components = Dir.glob(File.join('**', '*.rb')).map do |relative_path|
16
+ modified = File.mtime relative_path
17
+ @latest_change = modified if modified > @latest_change
18
+ "require '#{relative_path}'"
19
+ end
20
+ build_string = "require 'porous'; #{components.join ";"}".gsub '.rb', ''
21
+ builder = Opal::Builder.new scheduler: Opal::BuilderScheduler::Sequential, cache: false
22
+ builder.build_str build_string, '(inline)'
23
+ File.binwrite "#{Dir.pwd}/static/dist/application.js", builder.to_s
24
+ @last_build = Time.now
25
+ self
26
+ end
27
+
28
+ # rubocop:disable Metrics/AbcSize
29
+ def start
30
+ loop do
31
+ sleep 0.25
32
+ next unless @build_queue.empty?
33
+
34
+ modified = Dir.glob(File.join('**', '*.rb')).map { |f| File.mtime f }.max
35
+ next unless modified > @last_build
36
+
37
+ @build_queue.push modified
38
+ # Load for server
39
+ Dir.glob(File.join('**', '*.rb')).map { |f| load File.expand_path("#{Dir.pwd}/#{f}") }
40
+
41
+ # Rebuild for browser
42
+ Thread.new { build }.join
43
+
44
+ # Notify clients
45
+ $socket.public 'build', @last_build.inspect
46
+ @build_queue.clear
47
+ end
48
+ end
49
+ # rubocop:enable Metrics/AbcSize
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porous
4
+ module Server
5
+ class Connect
6
+ # Only used for WebSocket or SSE upgrades.
7
+ def call(env)
8
+ if env['rack.upgrade?'].nil?
9
+ [404, {}, []]
10
+ else
11
+ $socket ||= Socket.new
12
+ env['rack.upgrade'] = $socket
13
+ [200, {}, []]
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Porous
4
+ module Server
5
+ class Socket
6
+ def initialize
7
+ @clients = []
8
+ @mutex = Mutex.new
9
+ end
10
+
11
+ def on_open(client)
12
+ @mutex.synchronize do
13
+ @clients << client
14
+ end
15
+ end
16
+
17
+ def on_close(client)
18
+ @mutex.synchronize do
19
+ @clients.delete(client)
20
+ end
21
+ end
22
+
23
+ def on_drained(_client); end
24
+
25
+ def on_message(client, data)
26
+ client.write("Handler says #{data}")
27
+ end
28
+
29
+ def public(channel, message)
30
+ output = "#{channel}|#{message}"
31
+ @mutex.synchronize do
32
+ @clients.each do |c|
33
+ c.write output
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Porous
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/porous.rb CHANGED
@@ -7,7 +7,7 @@ require 'opal-virtual-dom'
7
7
  Opal.append_path File.expand_path('../opal', __dir__)
8
8
  Opal.append_path File.expand_path(Dir.pwd)
9
9
 
10
- require 'listen'
10
+ require 'agoo'
11
11
 
12
12
  require 'porous/version'
13
13
 
@@ -27,7 +27,6 @@ Dir.glob(File.join('{components,pages}', '**', '*.rb')).each do |relative_path|
27
27
  end
28
28
 
29
29
  require 'porous/application'
30
- require 'porous/server' unless RUBY_ENGINE == 'opal'
31
30
 
32
31
  module Porous
33
32
  class Error < StandardError; end
@@ -11,8 +11,9 @@ module VirtualDOM
11
11
  small source span strong style sub summary sup table tbody td textarea tfoot th
12
12
  thead time title tr track u ul var video wbr].freeze
13
13
 
14
- SVG_TAGS = %w[svg path].freeze
15
-
14
+ SVG_TAGS = %w[animate animateMotion animateTransform circle clipPath defs desc ellipse filter
15
+ foreignObject g image line linearGradient marker mask metadata mpath path pattern
16
+ polygon polyline radialGradient rect set stop svg switch symbol textPath tspan use view].freeze
16
17
  (HTML_TAGS + SVG_TAGS).each do |tag|
17
18
  define_method tag do |params = {}, &block|
18
19
  if params.is_a?(String)
@@ -37,7 +37,7 @@ module Porous
37
37
 
38
38
  def render!
39
39
  if Browser::AnimationFrame.supported?
40
- animation_frame do
40
+ Browser::AnimationFrame.new $window do
41
41
  @root_component.render_if_root
42
42
  end
43
43
  else
data/opal/porous.rb CHANGED
@@ -11,15 +11,6 @@ require 'console'
11
11
  require 'virtual_dom'
12
12
  require 'virtual_dom/support/browser'
13
13
 
14
- VirtualDOM::DOM::HTML_TAGS = %w[a abbr address area article aside audio b base bdi bdo big blockquote body br
15
- button canvas caption cite code col colgroup data datalist dd del details dfn
16
- dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5
17
- h6 head header hr html i iframe img input ins kbd keygen label legend li link
18
- main map mark menu menuitem meta meter nav noscript object ol optgroup option
19
- output p param picture pre progress q rp rt ruby s samp script section select
20
- small source span strong style sub summary sup table tbody td textarea tfoot th
21
- thead time title tr track u ul var video wbr svg path].freeze
22
-
23
14
  require 'porous/injection'
24
15
  require 'porous/component/class_methods'
25
16
  require 'porous/component/render'
@@ -38,4 +29,24 @@ end
38
29
 
39
30
  $document.ready do
40
31
  Porous::Application.mount_to($document.body)
32
+ Browser::Socket.new 'ws://localhost:9292/connect' do
33
+ on :open do |_e|
34
+ $console.info 'Connected to server!'
35
+ end
36
+
37
+ on :message do |e|
38
+ channel, content = e.data.split '|'
39
+ case channel
40
+ when 'build'
41
+ if content == 'started'
42
+ $console.log 'New build started…'
43
+ else
44
+ $console.log 'Reloading scripts…'
45
+ $document.location.reload
46
+ end
47
+ else
48
+ $console.log "Received #{e.data}"
49
+ end
50
+ end
51
+ end
41
52
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: porous
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Exa Stencil
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-18 00:00:00.000000000 Z
11
+ date: 2024-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: listen
14
+ name: agoo
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '3.0'
19
+ version: '2.15'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '3.0'
26
+ version: '2.15'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: opal-browser
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -117,7 +117,10 @@ files:
117
117
  - lib/porous/page.rb
118
118
  - lib/porous/router.rb
119
119
  - lib/porous/routes.rb
120
- - lib/porous/server.rb
120
+ - lib/porous/server/application.rb
121
+ - lib/porous/server/builder.rb
122
+ - lib/porous/server/connect.rb
123
+ - lib/porous/server/socket.rb
121
124
  - lib/porous/version.rb
122
125
  - lib/virtual_dom/dom.rb
123
126
  - lib/virtual_dom/virtual_node.rb
data/lib/porous/server.rb DELETED
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Porous
4
- class Server
5
- def initialize(*_args)
6
- setup_rack_app
7
- end
8
-
9
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
10
- def setup_rack_app
11
- @rack = Rack::Builder.new do
12
- use Rack::ContentLength
13
- use Rack::Static, urls: ['/static']
14
- use Rack::CommonLogger
15
- use Rack::ShowExceptions
16
- use Rack::Lint
17
- use Rack::TempfileReaper
18
-
19
- run do |env|
20
- router = Porous::Router.new path: env['PATH_INFO'], query: env['QUERY_STRING']
21
- route = router.find_route
22
- page = route[:component].new(route[:params])
23
-
24
- [200, { 'content-type' => 'text/html' }, [
25
- Porous::Application.new(
26
- title: page.page_title,
27
- description: page.page_description,
28
- path: env['PATH_INFO'],
29
- query: env['QUERY_STRING']
30
- ).to_s
31
- ]]
32
- rescue Porous::InvalidRouteError => e
33
- [404, { 'content-type' => 'text/plain' }, ["404 Page not found\n", e.message]]
34
- rescue Porous::Error => e
35
- [500, { 'content-type' => 'text/plain' }, ["500 Internal Server Error\n", e.message]]
36
- end
37
- end
38
- end
39
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
40
-
41
- def call(*args)
42
- @rack.call(*args)
43
- end
44
- end
45
- end