quince 0.5.1 โ 0.6.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/Gemfile.lock +25 -1
- data/README.md +13 -38
- data/lib/quince/attributes_by_element.rb +2 -2
- data/lib/quince/callback.rb +2 -1
- data/lib/quince/component.rb +20 -13
- data/lib/quince/sinatra.rb +70 -0
- data/lib/quince/singleton_methods.rb +0 -23
- data/lib/quince/version.rb +1 -1
- data/lib/quince.rb +1 -0
- data/quince.gemspec +3 -0
- data/scripts.js +44 -38
- metadata +45 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f42655bc57603a51bd5231388dc7be387a51a8e912549f6a504fbaeb0e1dbb3e
|
4
|
+
data.tar.gz: ed87a57062ce7343b08409fd8126034a7dbf46e094b1741829fc63fdb9ab1f7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c5358907ee716710a7c0f53c277ede1301ee16c31ab2304a63d33cdd91dff9f80a282f2832637d7b5ad14d4103d65a5f9ce16c4ed02cda14f9a148145263ee5
|
7
|
+
data.tar.gz: 0dc9bad86700382b48810bf70a2c0b0258c7e3383a4b73a37ea84fc46bf8ca174f2e20404c5070723512ffd2c1f0b1a61447bd9ce3da9eec6275e2dc8a003309
|
data/Gemfile.lock
CHANGED
@@ -1,15 +1,26 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
quince (0.
|
4
|
+
quince (0.6.0)
|
5
5
|
oj (~> 3.13)
|
6
|
+
rack-contrib (~> 2.3)
|
7
|
+
sinatra (~> 2.1)
|
8
|
+
sinatra-contrib (~> 2.1)
|
6
9
|
typed_struct (>= 0.1.4)
|
7
10
|
|
8
11
|
GEM
|
9
12
|
remote: https://rubygems.org/
|
10
13
|
specs:
|
11
14
|
diff-lcs (1.4.4)
|
15
|
+
multi_json (1.15.0)
|
16
|
+
mustermann (1.1.1)
|
17
|
+
ruby2_keywords (~> 0.0.1)
|
12
18
|
oj (3.13.9)
|
19
|
+
rack (2.2.3)
|
20
|
+
rack-contrib (2.3.0)
|
21
|
+
rack (~> 2.0)
|
22
|
+
rack-protection (2.1.0)
|
23
|
+
rack
|
13
24
|
rake (13.0.6)
|
14
25
|
rbs (1.6.2)
|
15
26
|
rspec (3.10.0)
|
@@ -25,6 +36,19 @@ GEM
|
|
25
36
|
diff-lcs (>= 1.2.0, < 2.0)
|
26
37
|
rspec-support (~> 3.10.0)
|
27
38
|
rspec-support (3.10.2)
|
39
|
+
ruby2_keywords (0.0.5)
|
40
|
+
sinatra (2.1.0)
|
41
|
+
mustermann (~> 1.0)
|
42
|
+
rack (~> 2.2)
|
43
|
+
rack-protection (= 2.1.0)
|
44
|
+
tilt (~> 2.0)
|
45
|
+
sinatra-contrib (2.1.0)
|
46
|
+
multi_json
|
47
|
+
mustermann (~> 1.0)
|
48
|
+
rack-protection (= 2.1.0)
|
49
|
+
sinatra (= 2.1.0)
|
50
|
+
tilt (~> 2.0)
|
51
|
+
tilt (2.0.10)
|
28
52
|
typed_struct (0.1.4)
|
29
53
|
rbs (~> 1.0)
|
30
54
|
|
data/README.md
CHANGED
@@ -10,7 +10,8 @@ React, Turbo, Hotwire amongst others
|
|
10
10
|
|
11
11
|
### Current status
|
12
12
|
|
13
|
-
|
13
|
+
Early, but [working in production](https://quince-rb.herokuapp.com/). Expect more features and
|
14
|
+
optimisations to come, but also potential for big changes between versions in the early stages.
|
14
15
|
|
15
16
|
### How it works
|
16
17
|
|
@@ -22,7 +23,7 @@ Proof of concept, but [working in production](https://quince-rb.herokuapp.com/),
|
|
22
23
|
|
23
24
|
```ruby
|
24
25
|
# app.rb
|
25
|
-
require "
|
26
|
+
require "quince"
|
26
27
|
|
27
28
|
class App < Quince::Component
|
28
29
|
def render
|
@@ -46,7 +47,7 @@ ruby app.rb
|
|
46
47
|
## More complex example
|
47
48
|
|
48
49
|
```ruby
|
49
|
-
require '
|
50
|
+
require 'quince'
|
50
51
|
|
51
52
|
class App < Quince::Component
|
52
53
|
def render
|
@@ -114,49 +115,23 @@ expose App, at: "/"
|
|
114
115
|
|
115
116
|
### Why?
|
116
117
|
|
117
|
-
- Lightweight ๐ชถ
|
118
|
-
- Very few dependencies
|
119
|
-
- Just a couple hundred lines of core logic
|
120
|
-
- Fewer than 30 lines (unminified) of JavaScript in the front end
|
121
|
-
- Plug and play into multiple back ends ๐
|
122
118
|
- Components > templates ๐งฉ
|
123
|
-
|
124
|
-
- no special syntax or compilation required
|
119
|
+
- Lightweight ๐ชถ
|
125
120
|
- Shallow learning curve if you are already familiar with React ๐
|
126
|
-
-
|
127
|
-
|
128
|
-
- routes
|
129
|
-
- controllers
|
130
|
-
- front end -> back end communication/APIs/Data transfer
|
131
|
-
- front end -> back end code sharing
|
132
|
-
- Quince handles these for you
|
133
|
-
- No node_modules ๐ฆ
|
134
|
-
- No yarn/npm
|
135
|
-
- Minimise bundle size concerns
|
136
|
-
- Manage your dependencies just using bundler & rubygems
|
137
|
-
- Make use of other pre-built Quince components via rubygems
|
121
|
+
- Focus on your core business logic, not routes/APIs/data transfer/code sharing ๐งช
|
122
|
+
- No compilation/node_modules/yarn/js bundle size concerns - just bundler ๐ฆ
|
138
123
|
- Get full use of Ruby's rich and comprehensive standard library ๐
|
139
124
|
- Take advantage of Ruby's ability to wrap native libraries (eg. gems using C) โก๏ธ
|
140
|
-
- Fully server-rendered responses ๐ก
|
141
|
-
|
142
|
-
|
143
|
-
- Know exactly what your user is seeing
|
144
|
-
- Tracking a user's activity on the front end has become a big deal, especially in heavily front-end driven apps/SPAs, in order to be able to see how a user is actually using the app (ie. to track how the state has been changing on the front end)
|
145
|
-
- This normally requires cookies and a premium third party service
|
146
|
-
- But if everything a user sees is generated server-side, it would be easy to reconstruct a user's journey and their state changes
|
125
|
+
- Fully server-rendered responses - single source of truth ๐ก
|
126
|
+
- Easy to recreate/rehydrate a pages state (almost nothing is stored in memory from JavaScript - all
|
127
|
+
the state is stored with the HTML document's markup)
|
147
128
|
|
148
129
|
## Installation
|
149
130
|
|
150
|
-
|
151
|
-
|
152
|
-
### Install it via adapters
|
153
|
-
|
154
|
-
- [Sinatra](https://github.com/johansenja/quince_sinatra)
|
155
|
-
|
156
|
-
Pick one, and add it to your application's Gemfile, eg:
|
131
|
+
Add this to your application's Gemfile:
|
157
132
|
|
158
133
|
```ruby
|
159
|
-
gem '
|
134
|
+
gem 'quince'
|
160
135
|
```
|
161
136
|
|
162
137
|
And then execute:
|
@@ -165,7 +140,7 @@ And then execute:
|
|
165
140
|
|
166
141
|
Or install it yourself as:
|
167
142
|
|
168
|
-
$ gem install
|
143
|
+
$ gem install quince
|
169
144
|
|
170
145
|
|
171
146
|
## Usage notes
|
@@ -16,7 +16,7 @@ module Quince
|
|
16
16
|
|
17
17
|
ATTRIBUTES_BY_ELEMENT = {
|
18
18
|
"A" => {
|
19
|
-
|
19
|
+
download: opt_string_sym,
|
20
20
|
href: opt_string_sym,
|
21
21
|
hreflang: opt_string_sym,
|
22
22
|
media: opt_string_sym,
|
@@ -337,7 +337,7 @@ module Quince
|
|
337
337
|
"Area" => {
|
338
338
|
alt: opt_string_sym,
|
339
339
|
coords: opt_string_sym,
|
340
|
-
|
340
|
+
download: opt_string_sym,
|
341
341
|
href: opt_string_sym,
|
342
342
|
hreflang: opt_string_sym,
|
343
343
|
media: opt_string_sym,
|
data/lib/quince/callback.rb
CHANGED
@@ -12,6 +12,7 @@ module Quince
|
|
12
12
|
rerender: nil,
|
13
13
|
push_params_state: nil,
|
14
14
|
handle_errors: true,
|
15
|
+
download: false,
|
15
16
|
}.freeze
|
16
17
|
|
17
18
|
def callback(method_name, **opts)
|
@@ -29,7 +30,7 @@ module Quince
|
|
29
30
|
attr_reader(
|
30
31
|
:receiver,
|
31
32
|
:method_name,
|
32
|
-
|
33
|
+
*ComponentHelpers::DEFAULT_CALLBACK_OPTIONS.keys,
|
33
34
|
)
|
34
35
|
end
|
35
36
|
|
data/lib/quince/component.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "forwardable"
|
1
2
|
require_relative "callback"
|
2
3
|
|
3
4
|
module Quince
|
@@ -24,18 +25,20 @@ module Quince
|
|
24
25
|
@exposed_actions ||= Set.new
|
25
26
|
@exposed_actions.add action
|
26
27
|
route = "/api/#{self.name}/#{action}"
|
27
|
-
Quince.
|
28
|
+
Quince::SinatraApp.create_route_handler(
|
28
29
|
verb: method,
|
29
30
|
route: route,
|
30
|
-
) do |
|
31
|
+
) do |bind|
|
32
|
+
Thread.current[:request_binding] = bind
|
33
|
+
params = bind.receiver.params
|
34
|
+
Thread.current[:params] = params[:params] || {}
|
31
35
|
instance = Quince::Serialiser.deserialise(CGI.unescapeHTML(params[:component]))
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
36
|
+
if params[:rerender]
|
37
|
+
instance.instance_variable_set :@state_container, params[:stateContainer]
|
38
|
+
render_with = params[:rerender][:method].to_sym
|
39
|
+
else
|
40
|
+
render_with = :render
|
41
|
+
end
|
39
42
|
instance.instance_variable_set :@render_with, render_with
|
40
43
|
instance.instance_variable_set :@callback_event, params[:event]
|
41
44
|
if @exposed_actions.member? action
|
@@ -73,11 +76,9 @@ module Quince
|
|
73
76
|
end
|
74
77
|
end
|
75
78
|
|
79
|
+
extend Forwardable
|
76
80
|
include Callback::ComponentHelpers
|
77
81
|
|
78
|
-
# set default
|
79
|
-
@@params = {}
|
80
|
-
|
81
82
|
attr_reader :props, :state, :children
|
82
83
|
|
83
84
|
def render
|
@@ -91,9 +92,15 @@ module Quince
|
|
91
92
|
end
|
92
93
|
|
93
94
|
def params
|
94
|
-
|
95
|
+
Thread.current[:params]
|
95
96
|
end
|
96
97
|
|
98
|
+
def request_context
|
99
|
+
Thread.current[:request_binding].receiver
|
100
|
+
end
|
101
|
+
|
102
|
+
def_delegators :request_context, :attachment, :request, :response, :redirect, :halt
|
103
|
+
|
97
104
|
private
|
98
105
|
|
99
106
|
attr_reader :__id
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ENV["RACK_ENV"] ||= "development"
|
4
|
+
|
5
|
+
require "sinatra/base"
|
6
|
+
require "sinatra/reloader" if ENV["RACK_ENV"] == "development"
|
7
|
+
require "rack/contrib"
|
8
|
+
|
9
|
+
module Quince
|
10
|
+
class SinatraApp < Sinatra::Base
|
11
|
+
configure :development do
|
12
|
+
if Object.const_defined? "Sinatra::Reloader"
|
13
|
+
register Sinatra::Reloader
|
14
|
+
dont_reload __FILE__
|
15
|
+
also_reload $0
|
16
|
+
end
|
17
|
+
end
|
18
|
+
use Rack::JSONBodyParser
|
19
|
+
use Rack::Deflater
|
20
|
+
set :public_folder, File.join(File.dirname(File.expand_path($0)), "public")
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def create_route_handler(verb:, route:, &blck)
|
24
|
+
meth = case verb
|
25
|
+
when :POST, :post
|
26
|
+
:post
|
27
|
+
when :GET, :get
|
28
|
+
:get
|
29
|
+
else
|
30
|
+
raise "invalid verb"
|
31
|
+
end
|
32
|
+
|
33
|
+
public_send meth, route do
|
34
|
+
Quince.to_html(blck.call(binding))
|
35
|
+
ensure
|
36
|
+
Thread.current[:request_binding] = nil
|
37
|
+
Thread.current[:params] = nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def expose(component, at:)
|
45
|
+
Quince::SinatraApp.get(at) do
|
46
|
+
Thread.current[:request_binding] = binding
|
47
|
+
Thread.current[:params] = binding.receiver.params
|
48
|
+
comp = component.instance_of?(Class) ? component.create : component
|
49
|
+
comp.instance_variable_set :@render_with, :render
|
50
|
+
Quince.to_html(comp)
|
51
|
+
ensure
|
52
|
+
Thread.current[:request_binding] = nil
|
53
|
+
Thread.current[:params] = nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
at_exit do
|
58
|
+
if $!.nil? || ($!.is_a?(SystemExit) && $!.success?)
|
59
|
+
if Object.const_defined? "Sinatra::Reloader"
|
60
|
+
app_dir = Pathname(File.expand_path($0)).dirname.to_s
|
61
|
+
$LOADED_FEATURES.each do |f|
|
62
|
+
next unless f.start_with? app_dir
|
63
|
+
|
64
|
+
Quince::SinatraApp.also_reload f
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
Quince::SinatraApp.run!
|
69
|
+
end
|
70
|
+
end
|
@@ -1,28 +1,5 @@
|
|
1
1
|
module Quince
|
2
2
|
class << self
|
3
|
-
attr_reader :middleware
|
4
|
-
attr_accessor :underlying_app
|
5
|
-
|
6
|
-
def optional_string
|
7
|
-
@optional_string ||= Rbs("String?")
|
8
|
-
end
|
9
|
-
|
10
|
-
def middleware=(middleware)
|
11
|
-
@middleware = middleware
|
12
|
-
Object.define_method(:expose) do |component, at:|
|
13
|
-
Quince.middleware.create_route_handler(
|
14
|
-
verb: :GET,
|
15
|
-
route: at,
|
16
|
-
) do |params|
|
17
|
-
component = component.create if component.instance_of? Class
|
18
|
-
Quince::Component.class_variable_set :@@params, params
|
19
|
-
component.instance_variable_set :@render_with, :render
|
20
|
-
component
|
21
|
-
end
|
22
|
-
end
|
23
|
-
Object.send :private, :expose
|
24
|
-
end
|
25
|
-
|
26
3
|
def define_constructor(const, constructor_name = nil)
|
27
4
|
if const.name
|
28
5
|
parts = const.name.split("::")
|
data/lib/quince/version.rb
CHANGED
data/lib/quince.rb
CHANGED
data/quince.gemspec
CHANGED
@@ -29,6 +29,9 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.require_paths = ["lib"]
|
30
30
|
|
31
31
|
# Uncomment to register a new dependency of your gem
|
32
|
+
spec.add_dependency "sinatra", "~> 2.1"
|
33
|
+
spec.add_dependency "sinatra-contrib", "~> 2.1"
|
34
|
+
spec.add_dependency "rack-contrib", "~> 2.3"
|
32
35
|
spec.add_dependency "typed_struct", ">= 0.1.4"
|
33
36
|
spec.add_dependency "oj", "~> 3.13"
|
34
37
|
|
data/scripts.js
CHANGED
@@ -11,7 +11,12 @@ const Q = {
|
|
11
11
|
}
|
12
12
|
).then(resp => {
|
13
13
|
if (resp.status <= 299) {
|
14
|
-
|
14
|
+
const cd = resp.headers.get("Content-Disposition");
|
15
|
+
if (cd && cd.trim().startsWith("attachment")) {
|
16
|
+
return resp.blob();
|
17
|
+
} else {
|
18
|
+
return resp.text();
|
19
|
+
}
|
15
20
|
} else if (resp.status >= 500) {
|
16
21
|
throw Q.em["500"];
|
17
22
|
} else {
|
@@ -21,45 +26,46 @@ const Q = {
|
|
21
26
|
|
22
27
|
throw msg;
|
23
28
|
}
|
24
|
-
}).then(
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
const
|
37
|
-
|
38
|
-
|
39
|
-
// be overkill
|
40
|
-
// let c = 0;
|
41
|
-
// for (; c < existingChildren.length; c++) {
|
42
|
-
// if (existingChildren[c].isEqualNode(newNodes[c]))
|
43
|
-
// continue;
|
44
|
-
// else
|
45
|
-
// break;
|
46
|
-
// }
|
47
|
-
// for the time being, we can just assume that we can just take the extra items
|
48
|
-
let c = existingChildren.length;
|
49
|
-
for (const node of newNodes.slice(c)) {
|
50
|
-
element.appendChild(node);
|
29
|
+
}).then(data => {
|
30
|
+
switch (true) {
|
31
|
+
case data instanceof Blob:
|
32
|
+
const url = URL.createObjectURL(data);
|
33
|
+
const a = document.createElement('a');
|
34
|
+
a.href = url;
|
35
|
+
a.download = "download";
|
36
|
+
document.body.appendChild(a);
|
37
|
+
a.click();
|
38
|
+
a.remove();
|
39
|
+
break;
|
40
|
+
default: // html
|
41
|
+
const element = document.querySelector(selector);
|
42
|
+
if (!element) {
|
43
|
+
throw `element not found for ${selector}`;
|
51
44
|
}
|
52
45
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
46
|
+
switch (mode) {
|
47
|
+
case "append_diff":
|
48
|
+
const tmpElem = document.createElement(element.nodeName);
|
49
|
+
tmpElem.innerHTML = data;
|
50
|
+
const newNodes = Array.from(tmpElem.childNodes);
|
51
|
+
const script = newNodes.pop();
|
52
|
+
const existingChildren = element.childNodes;
|
53
|
+
let c = existingChildren.length;
|
54
|
+
for (const node of newNodes.slice(c)) {
|
55
|
+
element.appendChild(node);
|
56
|
+
}
|
57
|
+
|
58
|
+
const newScript = document.createElement("script");
|
59
|
+
newScript.dataset.quid = script.dataset.quid;
|
60
|
+
newScript.innerHTML = script.innerHTML;
|
61
|
+
document.head.appendChild(newScript);
|
62
|
+
break;
|
63
|
+
case "replace":
|
64
|
+
element.outerHTML = data;
|
65
|
+
break;
|
66
|
+
default:
|
67
|
+
throw `mode ${mode} is not valid`;
|
68
|
+
}
|
63
69
|
}
|
64
70
|
}).catch(err => {
|
65
71
|
if (!handleErrors) throw err;
|
metadata
CHANGED
@@ -1,15 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quince
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joseph Johansen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-10-
|
11
|
+
date: 2021-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sinatra
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sinatra-contrib
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rack-contrib
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.3'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.3'
|
13
55
|
- !ruby/object:Gem::Dependency
|
14
56
|
name: typed_struct
|
15
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -62,6 +104,7 @@ files:
|
|
62
104
|
- lib/quince/component.rb
|
63
105
|
- lib/quince/html_tag_components.rb
|
64
106
|
- lib/quince/serialiser.rb
|
107
|
+
- lib/quince/sinatra.rb
|
65
108
|
- lib/quince/singleton_methods.rb
|
66
109
|
- lib/quince/types.rb
|
67
110
|
- lib/quince/version.rb
|