skybolt 3.2.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +287 -0
- data/lib/skybolt/renderer.rb +172 -0
- data/lib/skybolt/version.rb +5 -0
- data/lib/skybolt.rb +8 -0
- metadata +52 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 730d6c870bd053421958e83321177a1f55f6d394eab14243c9db126d1177eb8b
|
|
4
|
+
data.tar.gz: '0883ef34760b719f36ec6e9221518f1034f3b9e3853f1bd54fb2432148adf446'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ee08f62ab0bebbca2522022a22c52c1f3c00a8b30f27104c3e54487e963d4bf47ff03d23efac0ca34f5b825fba121090ebd2e1a8627be62305de5c400e586b49
|
|
7
|
+
data.tar.gz: af5ab159945553fbbf348e1e81ab706f4ee6d6d5c2a2a4c73d54ae843afec1aa4365bea3b36d1eaae8e7590f5e318e0597d721494b6f55c1d086758adf307ae6
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jens Roland
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# Skybolt Ruby
|
|
2
|
+
|
|
3
|
+
Ruby adapter for [Skybolt](https://github.com/JensRoland/skybolt) - High-performance asset caching for multi-page applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add to your Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "skybolt"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then run:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
1. Install and configure the Vite plugin: `npm install @skybolt/vite-plugin`
|
|
22
|
+
2. Build your project: `npm run build`
|
|
23
|
+
3. Ensure `render-map.json` is generated in your build output
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
require "skybolt"
|
|
29
|
+
|
|
30
|
+
sb = Skybolt::Renderer.new(
|
|
31
|
+
"public/dist/.skybolt/render-map.json",
|
|
32
|
+
cookies: request.cookies
|
|
33
|
+
)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```erb
|
|
37
|
+
<!DOCTYPE html>
|
|
38
|
+
<html>
|
|
39
|
+
<head>
|
|
40
|
+
<%= raw sb.css("src/css/critical.css") %>
|
|
41
|
+
<%= raw sb.launch_script %>
|
|
42
|
+
<%= raw sb.css("src/css/main.css") %>
|
|
43
|
+
</head>
|
|
44
|
+
<body>
|
|
45
|
+
<h1>Hello Skybolt!</h1>
|
|
46
|
+
<%= raw sb.script("src/js/app.js") %>
|
|
47
|
+
</body>
|
|
48
|
+
</html>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API
|
|
52
|
+
|
|
53
|
+
### `Skybolt::Renderer.new(render_map_path, cookies: nil, cdn_url: nil)`
|
|
54
|
+
|
|
55
|
+
Create a new Skybolt renderer.
|
|
56
|
+
|
|
57
|
+
- `render_map_path` - Path to `render-map.json` generated by Vite plugin
|
|
58
|
+
- `cookies:` - Cookie hash (defaults to `nil`)
|
|
59
|
+
- `cdn_url:` - Optional CDN URL prefix (e.g., `'https://cdn.example.com'`)
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
# Basic usage
|
|
63
|
+
sb = Skybolt::Renderer.new(
|
|
64
|
+
"public/dist/.skybolt/render-map.json",
|
|
65
|
+
cookies: request.cookies
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# With CDN
|
|
69
|
+
sb = Skybolt::Renderer.new(
|
|
70
|
+
"public/dist/.skybolt/render-map.json",
|
|
71
|
+
cookies: request.cookies,
|
|
72
|
+
cdn_url: "https://cdn.example.com"
|
|
73
|
+
)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `css(entry) -> String`
|
|
77
|
+
|
|
78
|
+
Render CSS asset.
|
|
79
|
+
|
|
80
|
+
- First visit: Inlines CSS with caching attributes
|
|
81
|
+
- Repeat visit: Outputs `<link>` tag (Service Worker serves from cache)
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
sb.css("src/css/main.css")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `script(entry, is_module: true) -> String`
|
|
88
|
+
|
|
89
|
+
Render JavaScript asset.
|
|
90
|
+
|
|
91
|
+
- First visit: Inlines JS with caching attributes
|
|
92
|
+
- Repeat visit: Outputs `<script>` tag (Service Worker serves from cache)
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
# ES module (default)
|
|
96
|
+
sb.script("src/js/app.js")
|
|
97
|
+
|
|
98
|
+
# Classic script
|
|
99
|
+
sb.script("src/js/legacy.js", is_module: false)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `launch_script -> String`
|
|
103
|
+
|
|
104
|
+
Render the Skybolt client launcher. Call once in `<head>` before other assets.
|
|
105
|
+
|
|
106
|
+
```erb
|
|
107
|
+
<head>
|
|
108
|
+
<%= raw sb.launch_script %>
|
|
109
|
+
</head>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `asset_url(entry) -> String?`
|
|
113
|
+
|
|
114
|
+
Get the URL for an asset (for manual use cases).
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
url = sb.asset_url("src/css/main.css")
|
|
118
|
+
# "/assets/main-Pw3rT8vL.css"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `asset_hash(entry) -> String?`
|
|
122
|
+
|
|
123
|
+
Get the content hash for an asset.
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
hash = sb.asset_hash("src/css/main.css")
|
|
127
|
+
# "Pw3rT8vL"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### `preload(entry, as_type:, type: nil, crossorigin: nil, fetchpriority: nil) -> String`
|
|
131
|
+
|
|
132
|
+
Render preload link for critical resources like fonts and images.
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
# Preload hero image with high priority
|
|
136
|
+
sb.preload("images/hero.jpg", as_type: "image", fetchpriority: "high")
|
|
137
|
+
|
|
138
|
+
# Preload font
|
|
139
|
+
sb.preload("fonts/inter.woff2", as_type: "font", type: "font/woff2", crossorigin: "anonymous")
|
|
140
|
+
|
|
141
|
+
# Preload a Vite-built asset (resolved from render-map)
|
|
142
|
+
sb.preload("src/css/main.css", as_type: "style")
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Parameters:
|
|
146
|
+
|
|
147
|
+
- `entry` - Source file path or direct URL
|
|
148
|
+
- `as_type:` - Resource type (`'image'`, `'font'`, `'style'`, `'script'`, `'fetch'`)
|
|
149
|
+
- `type:` - MIME type (e.g., `'font/woff2'`, `'image/webp'`)
|
|
150
|
+
- `crossorigin:` - Crossorigin attribute (`'anonymous'`, `'use-credentials'`)
|
|
151
|
+
- `fetchpriority:` - Fetch priority (`'high'`, `'low'`, `'auto'`)
|
|
152
|
+
|
|
153
|
+
## Service Worker Setup
|
|
154
|
+
|
|
155
|
+
The Service Worker must be served from your domain root. Configure your web server:
|
|
156
|
+
|
|
157
|
+
**Nginx:**
|
|
158
|
+
|
|
159
|
+
```nginx
|
|
160
|
+
location = /skybolt-sw.js {
|
|
161
|
+
alias /path/to/public/dist/skybolt-sw.js;
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Apache (.htaccess):**
|
|
166
|
+
|
|
167
|
+
```apache
|
|
168
|
+
RewriteRule ^skybolt-sw\.js$ public/dist/skybolt-sw.js [L]
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Framework Integration
|
|
172
|
+
|
|
173
|
+
### Rails
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
# app/helpers/skybolt_helper.rb
|
|
177
|
+
module SkyboltHelper
|
|
178
|
+
def skybolt
|
|
179
|
+
@skybolt ||= Skybolt::Renderer.new(
|
|
180
|
+
Rails.root.join("public/dist/.skybolt/render-map.json").to_s,
|
|
181
|
+
cookies: cookies.to_h
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```erb
|
|
188
|
+
<%# app/views/layouts/application.html.erb %>
|
|
189
|
+
<!DOCTYPE html>
|
|
190
|
+
<html>
|
|
191
|
+
<head>
|
|
192
|
+
<%= raw skybolt.css("app/assets/stylesheets/critical.css") %>
|
|
193
|
+
<%= raw skybolt.launch_script %>
|
|
194
|
+
<%= raw skybolt.css("app/assets/stylesheets/application.css") %>
|
|
195
|
+
</head>
|
|
196
|
+
<body>
|
|
197
|
+
<%= yield %>
|
|
198
|
+
<%= raw skybolt.script("app/javascript/application.js") %>
|
|
199
|
+
</body>
|
|
200
|
+
</html>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Sinatra
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
require "sinatra"
|
|
207
|
+
require "skybolt"
|
|
208
|
+
|
|
209
|
+
helpers do
|
|
210
|
+
def skybolt
|
|
211
|
+
@skybolt ||= Skybolt::Renderer.new(
|
|
212
|
+
"public/dist/.skybolt/render-map.json",
|
|
213
|
+
cookies: request.cookies
|
|
214
|
+
)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
get "/" do
|
|
219
|
+
erb :index
|
|
220
|
+
end
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
```erb
|
|
224
|
+
<%# views/index.erb %>
|
|
225
|
+
<!DOCTYPE html>
|
|
226
|
+
<html>
|
|
227
|
+
<head>
|
|
228
|
+
<%= raw skybolt.css("src/css/critical.css") %>
|
|
229
|
+
<%= raw skybolt.launch_script %>
|
|
230
|
+
<%= raw skybolt.css("src/css/main.css") %>
|
|
231
|
+
</head>
|
|
232
|
+
<body>
|
|
233
|
+
<h1>Hello Skybolt!</h1>
|
|
234
|
+
<%= raw skybolt.script("src/js/app.js") %>
|
|
235
|
+
</body>
|
|
236
|
+
</html>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Hanami
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
# app/actions/home/index.rb
|
|
243
|
+
module MyApp
|
|
244
|
+
module Actions
|
|
245
|
+
module Home
|
|
246
|
+
class Index < MyApp::Action
|
|
247
|
+
def handle(request, response)
|
|
248
|
+
sb = Skybolt::Renderer.new(
|
|
249
|
+
"public/dist/.skybolt/render-map.json",
|
|
250
|
+
cookies: request.cookies
|
|
251
|
+
)
|
|
252
|
+
response.render(view, sb: sb)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Requirements
|
|
261
|
+
|
|
262
|
+
- Ruby 3.0+
|
|
263
|
+
- Vite with `@skybolt/vite-plugin`
|
|
264
|
+
|
|
265
|
+
## Publishing
|
|
266
|
+
|
|
267
|
+
This package is maintained in the [Skybolt monorepo](https://github.com/JensRoland/skybolt) and automatically synced to [skybolt-ruby](https://github.com/JensRoland/skybolt-ruby).
|
|
268
|
+
|
|
269
|
+
To publish a new version, run one command from the `packages/ruby` directory:
|
|
270
|
+
|
|
271
|
+
```sh
|
|
272
|
+
./scripts/release.sh patch # 3.1.0 → 3.1.1
|
|
273
|
+
./scripts/release.sh minor # 3.1.0 → 3.2.0
|
|
274
|
+
./scripts/release.sh major # 3.1.0 → 4.0.0
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
This automatically:
|
|
278
|
+
|
|
279
|
+
1. Bumps the version in `VERSION`, `skybolt.gemspec`, and `lib/skybolt/version.rb`
|
|
280
|
+
2. Commits and pushes to the monorepo
|
|
281
|
+
3. Sync workflow pushes changes to the split repo
|
|
282
|
+
4. `tag-version.yml` in the split repo creates the `v*` tag
|
|
283
|
+
5. `publish.yml` builds and publishes to RubyGems using trusted publishing (OIDC)
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "cgi"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module Skybolt
|
|
8
|
+
# Skybolt asset renderer.
|
|
9
|
+
#
|
|
10
|
+
# Reads the render-map.json generated by @skybolt/vite-plugin and outputs
|
|
11
|
+
# optimized HTML tags with intelligent caching via Service Workers.
|
|
12
|
+
class Renderer
|
|
13
|
+
# @param render_map_path [String] Path to render-map.json generated by Vite plugin
|
|
14
|
+
# @param cookies [Hash, nil] Cookie hash (defaults to empty hash)
|
|
15
|
+
# @param cdn_url [String, nil] Optional CDN URL prefix
|
|
16
|
+
def initialize(render_map_path, cookies: nil, cdn_url: nil)
|
|
17
|
+
json = File.read(render_map_path)
|
|
18
|
+
@map = JSON.parse(json)
|
|
19
|
+
@client_cache = parse_cookie((cookies || {})["sb_assets"] || "")
|
|
20
|
+
@cdn_url = cdn_url&.chomp("/")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Render CSS asset.
|
|
24
|
+
#
|
|
25
|
+
# On first visit: inlines CSS with sb-* attributes for caching
|
|
26
|
+
# On repeat visit: outputs <link> tag (Service Worker serves from cache)
|
|
27
|
+
#
|
|
28
|
+
# @param entry [String] Source file path (e.g., 'src/css/main.css')
|
|
29
|
+
# @return [String] HTML string
|
|
30
|
+
def css(entry)
|
|
31
|
+
asset = @map.dig("assets", entry)
|
|
32
|
+
return comment("Skybolt: asset not found: #{entry}") if asset.nil?
|
|
33
|
+
|
|
34
|
+
url = resolve_url(asset["url"])
|
|
35
|
+
|
|
36
|
+
# Client has current version - external link (SW serves from cache)
|
|
37
|
+
if cached?(entry, asset["hash"])
|
|
38
|
+
return %(<link rel="stylesheet" href="#{esc(url)}">)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# First visit - inline with cache attributes
|
|
42
|
+
%(<style sb-asset="#{esc(entry)}:#{esc(asset["hash"])}" sb-url="#{esc(url)}">#{asset["content"]}</style>)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Render JavaScript asset.
|
|
46
|
+
#
|
|
47
|
+
# On first visit: inlines JS with sb-* attributes for caching
|
|
48
|
+
# On repeat visit: outputs <script> tag (Service Worker serves from cache)
|
|
49
|
+
#
|
|
50
|
+
# @param entry [String] Source file path (e.g., 'src/js/app.js')
|
|
51
|
+
# @param is_module [Boolean] Whether to load as ES module (default: true)
|
|
52
|
+
# @return [String] HTML string
|
|
53
|
+
def script(entry, is_module: true)
|
|
54
|
+
asset = @map.dig("assets", entry)
|
|
55
|
+
return comment("Skybolt: asset not found: #{entry}") if asset.nil?
|
|
56
|
+
|
|
57
|
+
url = resolve_url(asset["url"])
|
|
58
|
+
type_attr = is_module ? ' type="module"' : ""
|
|
59
|
+
|
|
60
|
+
# Client has current version - external script (SW serves from cache)
|
|
61
|
+
if cached?(entry, asset["hash"])
|
|
62
|
+
return %(<script#{type_attr} src="#{esc(url)}"></script>)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# First visit - inline with cache attributes
|
|
66
|
+
%(<script#{type_attr} sb-asset="#{esc(entry)}:#{esc(asset["hash"])}" sb-url="#{esc(url)}">#{asset["content"]}</script>)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Render preload link for critical resources.
|
|
70
|
+
#
|
|
71
|
+
# Use this for fonts, images, or other resources that should load early.
|
|
72
|
+
# Preloaded resources are not cached by Skybolt's Service Worker.
|
|
73
|
+
#
|
|
74
|
+
# @param entry [String] Source file path or direct URL
|
|
75
|
+
# @param as_type [String] Resource type ('image', 'font', 'style', 'script', 'fetch')
|
|
76
|
+
# @param type [String, nil] MIME type (e.g., 'font/woff2', 'image/webp')
|
|
77
|
+
# @param crossorigin [String, nil] Crossorigin attribute ('anonymous', 'use-credentials')
|
|
78
|
+
# @param fetchpriority [String, nil] Fetch priority ('high', 'low', 'auto')
|
|
79
|
+
# @return [String] HTML string
|
|
80
|
+
def preload(entry, as_type:, type: nil, crossorigin: nil, fetchpriority: nil)
|
|
81
|
+
# Try to resolve from assets, fall back to using entry as URL
|
|
82
|
+
url = asset_url(entry) || entry
|
|
83
|
+
url = resolve_url(url)
|
|
84
|
+
|
|
85
|
+
attrs = {
|
|
86
|
+
rel: "preload",
|
|
87
|
+
href: url,
|
|
88
|
+
as: as_type
|
|
89
|
+
}
|
|
90
|
+
attrs[:type] = type if type
|
|
91
|
+
attrs[:crossorigin] = crossorigin if crossorigin
|
|
92
|
+
attrs[:fetchpriority] = fetchpriority if fetchpriority
|
|
93
|
+
|
|
94
|
+
build_tag("link", attrs)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Render the Skybolt client launcher.
|
|
98
|
+
#
|
|
99
|
+
# Call this once in <head> before other Skybolt assets.
|
|
100
|
+
# Outputs config meta tag and client script.
|
|
101
|
+
#
|
|
102
|
+
# @return [String] HTML string
|
|
103
|
+
def launch_script
|
|
104
|
+
sw_path = @map.dig("serviceWorker", "path") || "/skybolt-sw.js"
|
|
105
|
+
config = { swPath: sw_path }.to_json
|
|
106
|
+
|
|
107
|
+
%(<meta name="skybolt-config" content="#{esc(config)}">\n<script type="module">#{@map.dig("client", "script")}</script>)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Get URL for an asset (for manual use cases).
|
|
111
|
+
#
|
|
112
|
+
# @param entry [String] Source file path
|
|
113
|
+
# @return [String, nil] Asset URL or nil if not found
|
|
114
|
+
def asset_url(entry)
|
|
115
|
+
url = @map.dig("assets", entry, "url")
|
|
116
|
+
url ? resolve_url(url) : nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Get hash for an asset (for manual use cases).
|
|
120
|
+
#
|
|
121
|
+
# @param entry [String] Source file path
|
|
122
|
+
# @return [String, nil] Asset hash or nil if not found
|
|
123
|
+
def asset_hash(entry)
|
|
124
|
+
@map.dig("assets", entry, "hash")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def resolve_url(url)
|
|
130
|
+
return url if @cdn_url.nil?
|
|
131
|
+
return url if url.start_with?("http://", "https://", "//")
|
|
132
|
+
|
|
133
|
+
"#{@cdn_url}#{url}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def cached?(entry, hash)
|
|
137
|
+
@client_cache[entry] == hash
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def parse_cookie(cookie)
|
|
141
|
+
return {} if cookie.empty?
|
|
142
|
+
|
|
143
|
+
decoded = URI.decode_www_form_component(cookie)
|
|
144
|
+
cache = {}
|
|
145
|
+
|
|
146
|
+
decoded.split(",").each do |pair|
|
|
147
|
+
# Find last colon (hash doesn't contain colons, but paths might)
|
|
148
|
+
colon_pos = pair.rindex(":")
|
|
149
|
+
next if colon_pos.nil?
|
|
150
|
+
|
|
151
|
+
name = pair[0...colon_pos]
|
|
152
|
+
hash = pair[(colon_pos + 1)..]
|
|
153
|
+
cache[name] = hash
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
cache
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def build_tag(tag, attrs)
|
|
160
|
+
attr_str = attrs.map { |k, v| %(#{k}="#{esc(v)}") }.join(" ")
|
|
161
|
+
"<#{tag} #{attr_str}>"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def esc(value)
|
|
165
|
+
CGI.escapeHTML(value.to_s)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def comment(text)
|
|
169
|
+
"<!-- #{esc(text)} -->"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
data/lib/skybolt.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: skybolt
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 3.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jens Roland
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-11-27 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Reads the render-map.json generated by @skybolt/vite-plugin and outputs
|
|
14
|
+
optimized HTML tags with intelligent caching via Service Workers.
|
|
15
|
+
email:
|
|
16
|
+
- mail@jensroland.com
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- LICENSE
|
|
22
|
+
- README.md
|
|
23
|
+
- lib/skybolt.rb
|
|
24
|
+
- lib/skybolt/renderer.rb
|
|
25
|
+
- lib/skybolt/version.rb
|
|
26
|
+
homepage: https://github.com/JensRoland/skybolt-ruby
|
|
27
|
+
licenses:
|
|
28
|
+
- MIT
|
|
29
|
+
metadata:
|
|
30
|
+
homepage_uri: https://github.com/JensRoland/skybolt-ruby
|
|
31
|
+
source_code_uri: https://github.com/JensRoland/skybolt-ruby
|
|
32
|
+
changelog_uri: https://github.com/JensRoland/skybolt-ruby/blob/main/CHANGELOG.md
|
|
33
|
+
post_install_message:
|
|
34
|
+
rdoc_options: []
|
|
35
|
+
require_paths:
|
|
36
|
+
- lib
|
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: 3.0.0
|
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
requirements: []
|
|
48
|
+
rubygems_version: 3.4.19
|
|
49
|
+
signing_key:
|
|
50
|
+
specification_version: 4
|
|
51
|
+
summary: High-performance asset caching for multi-page applications
|
|
52
|
+
test_files: []
|