porous 0.2.0 → 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 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