skybolt 3.2.0 → 3.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: 730d6c870bd053421958e83321177a1f55f6d394eab14243c9db126d1177eb8b
4
- data.tar.gz: '0883ef34760b719f36ec6e9221518f1034f3b9e3853f1bd54fb2432148adf446'
3
+ metadata.gz: 9380c0cdfa32e9c73ef10d8170f6c5f1d40803c6677c428eb3f62c28fe31b5ff
4
+ data.tar.gz: fe4d5f31d47a79d6f55bfe27c16f2977cd6c25013ada9e42ab29b3cb008e7be2
5
5
  SHA512:
6
- metadata.gz: ee08f62ab0bebbca2522022a22c52c1f3c00a8b30f27104c3e54487e963d4bf47ff03d23efac0ca34f5b825fba121090ebd2e1a8627be62305de5c400e586b49
7
- data.tar.gz: af5ab159945553fbbf348e1e81ab706f4ee6d6d5c2a2a4c73d54ae843afec1aa4365bea3b36d1eaae8e7590f5e318e0597d721494b6f55c1d086758adf307ae6
6
+ metadata.gz: c11d0a7a4602a1a958774c02948e76cbe8a229afe6bf95caa3196d25416cd291b6f6fdae9a189730ebc86012c9fe8a5edbfb7e82b06c04853ddb124c482fa310
7
+ data.tar.gz: ae9a6729144cd991aaf06acaa58a7c374956f07648f5e45f5086ccf661c18890cecf6508c327d6acec8e49ac33e50f542718f0936f1aeb61eccd9cc2efa83d40
data/README.md CHANGED
@@ -73,15 +73,24 @@ sb = Skybolt::Renderer.new(
73
73
  )
74
74
  ```
75
75
 
76
- ### `css(entry) -> String`
76
+ ### `css(entry, async: false) -> String`
77
77
 
78
78
  Render CSS asset.
79
79
 
80
80
  - First visit: Inlines CSS with caching attributes
81
81
  - Repeat visit: Outputs `<link>` tag (Service Worker serves from cache)
82
82
 
83
+ When `async:` is `true`, CSS loads non-blocking:
84
+
85
+ - First visit: Uses `media="print"` trick, swaps to `all` on load
86
+ - Repeat visit: Uses `<link rel="preload">` with `onload`
87
+
83
88
  ```ruby
84
- sb.css("src/css/main.css")
89
+ # Blocking (default) - for critical CSS
90
+ sb.css("src/css/critical.css")
91
+
92
+ # Non-blocking - for non-critical CSS
93
+ sb.css("src/css/main.css", async: true)
85
94
  ```
86
95
 
87
96
  ### `script(entry, is_module: true) -> String`
@@ -262,26 +271,6 @@ end
262
271
  - Ruby 3.0+
263
272
  - Vite with `@skybolt/vite-plugin`
264
273
 
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
274
  ## License
286
275
 
287
276
  MIT
@@ -26,8 +26,9 @@ module Skybolt
26
26
  # On repeat visit: outputs <link> tag (Service Worker serves from cache)
27
27
  #
28
28
  # @param entry [String] Source file path (e.g., 'src/css/main.css')
29
+ # @param async [Boolean] Load CSS asynchronously (non-render-blocking)
29
30
  # @return [String] HTML string
30
- def css(entry)
31
+ def css(entry, async: false)
31
32
  asset = @map.dig("assets", entry)
32
33
  return comment("Skybolt: asset not found: #{entry}") if asset.nil?
33
34
 
@@ -35,10 +36,20 @@ module Skybolt
35
36
 
36
37
  # Client has current version - external link (SW serves from cache)
37
38
  if cached?(entry, asset["hash"])
39
+ if async
40
+ # Preload + onload swap for non-blocking load
41
+ return %(<link rel="preload" href="#{esc(url)}" as="style" onload="this.rel='stylesheet'">) +
42
+ %(<noscript><link rel="stylesheet" href="#{esc(url)}"></noscript>)
43
+ end
38
44
  return %(<link rel="stylesheet" href="#{esc(url)}">)
39
45
  end
40
46
 
41
47
  # First visit - inline with cache attributes
48
+ if async
49
+ # media="print" trick: browser parses but doesn't apply until onload swaps to "all"
50
+ return %(<style media="print" onload="this.media='all'" sb-asset="#{esc(entry)}:#{esc(asset["hash"])}" sb-url="#{esc(url)}">#{asset["content"]}</style>)
51
+ end
52
+
42
53
  %(<style sb-asset="#{esc(entry)}:#{esc(asset["hash"])}" sb-url="#{esc(url)}">#{asset["content"]}</style>)
43
54
  end
44
55
 
@@ -99,12 +110,29 @@ module Skybolt
99
110
  # Call this once in <head> before other Skybolt assets.
100
111
  # Outputs config meta tag and client script.
101
112
  #
113
+ # On first visit (or cache miss), the launcher is inlined with sb-asset
114
+ # and sb-url attributes so the client can cache itself.
115
+ #
116
+ # On repeat visits (cache hit), returns an external script tag. The Service
117
+ # Worker will serve the launcher from cache (~5ms response time).
118
+ #
102
119
  # @return [String] HTML string
103
120
  def launch_script
104
121
  sw_path = @map.dig("serviceWorker", "path") || "/skybolt-sw.js"
105
122
  config = { swPath: sw_path }.to_json
106
123
 
107
- %(<meta name="skybolt-config" content="#{esc(config)}">\n<script type="module">#{@map.dig("client", "script")}</script>)
124
+ launcher = @map["launcher"]
125
+ url = resolve_url(launcher["url"])
126
+
127
+ meta = %(<meta name="skybolt-config" content="#{esc(config)}">\n)
128
+
129
+ if cached?("skybolt-launcher", launcher["hash"])
130
+ # Repeat visit - external script (SW serves from cache)
131
+ return %(#{meta}<script type="module" src="#{esc(url)}"></script>)
132
+ end
133
+
134
+ # First visit - inline with sb-asset and sb-url for self-caching
135
+ %(#{meta}<script type="module" sb-asset="skybolt-launcher:#{esc(launcher["hash"])}" sb-url="#{esc(url)}">#{launcher["content"]}</script>)
108
136
  end
109
137
 
110
138
  # Get URL for an asset (for manual use cases).
@@ -124,6 +152,41 @@ module Skybolt
124
152
  @map.dig("assets", entry, "hash")
125
153
  end
126
154
 
155
+ # Check if an asset URL is currently cached by the client.
156
+ #
157
+ # This is useful for Chain Lightning integration where we need to check
158
+ # cache status by URL rather than source path.
159
+ #
160
+ # @param url [String] The asset URL (e.g., '/assets/main-Abc123.css')
161
+ # @return [Boolean] True if the asset is cached
162
+ def cached_url?(url)
163
+ # Build URL to entry mapping if not already built
164
+ @url_to_entry ||= begin
165
+ mapping = {}
166
+ (@map["assets"] || {}).each do |entry, asset|
167
+ mapping[asset["url"]] = { "entry" => entry, "hash" => asset["hash"] }
168
+ end
169
+ mapping
170
+ end
171
+
172
+ info = @url_to_entry[url]
173
+ return false if info.nil?
174
+
175
+ cached?(info["entry"], info["hash"])
176
+ end
177
+
178
+ # Check if client has a specific entry:hash pair cached.
179
+ #
180
+ # Useful for external integrations (like Chain Lightning) that manage
181
+ # their own assets outside of Skybolt's render-map.
182
+ #
183
+ # @param entry [String] The entry name (e.g., 'chain-lightning' or 'cl-manifest')
184
+ # @param hash [String] The expected hash value
185
+ # @return [Boolean] True if the entry:hash pair is in the client's cache
186
+ def cached_entry?(entry, hash)
187
+ cached?(entry, hash)
188
+ end
189
+
127
190
  private
128
191
 
129
192
  def resolve_url(url)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Skybolt
4
- VERSION = "3.2.0"
4
+ VERSION = "3.3.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skybolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jens Roland
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-11-27 00:00:00.000000000 Z
11
+ date: 2025-11-30 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Reads the render-map.json generated by @skybolt/vite-plugin and outputs
14
14
  optimized HTML tags with intelligent caching via Service Workers.