islandjs-rails 0.4.0 → 0.5.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: 3efc622c722a226faf6ab715f92302e3476aa46be3c186dabd7ec8e51cf49166
4
- data.tar.gz: 5c6fa3546df97f4c8430e6b9c06b320a6cd06432b6493025bc05a5c4242f88e9
3
+ metadata.gz: 80cbddb05a05365fd47474178fd2fb2b403f69c597169fe16bcb93e558dbabb7
4
+ data.tar.gz: f342dd7687658b6c8df47cfa7566f2061517112eb248719c997b20ea2634d5bf
5
5
  SHA512:
6
- metadata.gz: ec382c1d892648a353afed3dec2420b895bf14eae349f3b4a2877aade87bcf8dd2954f3bf1715aa93c6e9fd1f4b3ffed2089c86c356957b0b830aca71387fa03
7
- data.tar.gz: 6fd2a2440a750bf0b284452b6faf5835d6a5635e5fef7e4213eb439d77ae95d7667e934eec36e5ff077ea7f1e58d9fbe2d60c140d49ec3c795eae7fe429fe9ad
6
+ metadata.gz: 5e3ed8b8c57007f2011c9b875cef7a84b4b4f262038bc2f9c92cc7e98ebfd9ed66b73c2b0fd7e9a1da9bee499b2a036f0c9ed48dd34dd9fdc99e680d82dae908
7
+ data.tar.gz: ec2afebea06a97b95b7c22b71596c30b704e44bbf5c5a8b0579a497b5b44f89056efcdc5dbf2179564d74b921301740e4feef340d8257c3adbd7c72203c9b703
data/CHANGELOG.md CHANGED
@@ -5,10 +5,16 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.5.0] - 2025-09-29
9
+
10
+ ### Added
11
+ - **CSP support for script tags**: All IslandJS-generated `<script>` tags now automatically include a CSP nonce when one is present in the Rails request.
12
+ - **Flexible script attributes**: Helpers (`react_component`, `vue_component`, etc.) now support passing standard script attributes (`nonce`, `defer`, `async`, `crossorigin`, `integrity`).
13
+
8
14
  ## [0.4.0] - 2025-08-10
9
15
 
10
16
  ### Added
11
- - Add ENV flag to control the dev UMD bundle info footer. The floating footer is now disabled by default and only shows in development when `ISLANDJS_RAILS_SHOW_UMD_DEBUG` is truthy.
17
+ - Add ENV flag to control the dev UMD bundle info footer. The floating footer is disabled by default and only shows in development when `ISLANDJS_RAILS_SHOW_UMD_DEBUG` is truthy.
12
18
 
13
19
  ## [0.3.0] - 2025-08-09
14
20
 
data/README.md CHANGED
@@ -420,6 +420,7 @@ Renders a React component with Turbo-compatible lifecycle and optional placehold
420
420
  - `class`: CSS class for container
421
421
  - `placeholder_class`: CSS class for placeholder content
422
422
  - `placeholder_style`: Inline styles for placeholder content
423
+ - Script attributes: `nonce` (auto-detected for CSP), `defer`, `async`, `crossorigin`, `integrity`
423
424
 
424
425
  ## Placeholder Support
425
426
 
@@ -1,10 +1,13 @@
1
1
  module IslandjsRails
2
2
  module RailsHelpers
3
+ # Script attributes that can be passed through options
4
+ SCRIPT_ATTRIBUTES = %i[nonce defer async crossorigin integrity].freeze
5
+
3
6
  # Main helper method that combines all IslandJS functionality
4
- def islands
7
+ def islands(**attributes)
5
8
  output = []
6
9
  output << island_partials # Now uses vendor UMD partial
7
- output << island_bundle_script
10
+ output << island_bundle_script(**attributes)
8
11
  output << umd_versions_debug if umd_debug_enabled?
9
12
  output.compact.join("\n").html_safe
10
13
  end
@@ -22,29 +25,32 @@ module IslandjsRails
22
25
  end
23
26
 
24
27
  # Render the main IslandJS bundle script tag
25
- def island_bundle_script
28
+ def island_bundle_script(**attributes)
26
29
  manifest_path = Rails.root.join('public', 'islands_manifest.json')
27
30
  bundle_path = '/islands_bundle.js'
28
-
31
+
32
+ # Get formatted HTML attributes with defaults (including auto-nonce and defer)
33
+ html_attributes = script_html_attributes(defer: true, **attributes)
34
+
29
35
  unless File.exist?(manifest_path)
30
36
  # Fallback to direct bundle path when no manifest
31
- return html_safe_string("<script src=\"#{bundle_path}\" defer></script>")
37
+ return html_safe_string("<script src=\"#{bundle_path}\"#{html_attributes}></script>")
32
38
  end
33
-
39
+
34
40
  begin
35
41
  manifest = JSON.parse(File.read(manifest_path))
36
42
  # Look for islands_bundle.js in manifest
37
43
  bundle_file = manifest['islands_bundle.js']
38
-
44
+
39
45
  if bundle_file
40
- html_safe_string("<script src=\"#{bundle_file}\" defer></script>")
46
+ html_safe_string("<script src=\"#{bundle_file}\"#{html_attributes}></script>")
41
47
  else
42
48
  # Fallback to direct bundle path
43
- html_safe_string("<script src=\"#{bundle_path}\" defer></script>")
49
+ html_safe_string("<script src=\"#{bundle_path}\"#{html_attributes}></script>")
44
50
  end
45
51
  rescue JSON::ParserError
46
52
  # Fallback to direct bundle path on manifest parse error
47
- html_safe_string("<script src=\"#{bundle_path}\" defer></script>")
53
+ html_safe_string("<script src=\"#{bundle_path}\"#{html_attributes}></script>")
48
54
  end
49
55
  end
50
56
 
@@ -66,7 +72,11 @@ module IslandjsRails
66
72
  # Handle placeholder options
67
73
  placeholder_class = options[:placeholder_class]
68
74
  placeholder_style = options[:placeholder_style]
69
-
75
+
76
+ # Extract script attributes from options
77
+ script_attributes = options.slice(*SCRIPT_ATTRIBUTES)
78
+ script_attributes.compact!
79
+
70
80
  # For turbo-cache compatibility, store initial state as JSON in data attribute
71
81
  initial_state_json = props.to_json
72
82
 
@@ -91,7 +101,7 @@ module IslandjsRails
91
101
  end
92
102
 
93
103
  # Generate the mounting script - pass container_id as the only prop for turbo-cache pattern
94
- mount_script = generate_react_mount_script(component_name, component_id, namespace, namespace_with_optional)
104
+ mount_script = generate_react_mount_script(component_name, component_id, namespace, namespace_with_optional, **script_attributes)
95
105
 
96
106
  # Return the container div with data-initial-state and script
97
107
  data_part = data_attrs.empty? ? '' : " #{data_attrs}"
@@ -133,9 +143,13 @@ module IslandjsRails
133
143
  # Extract options
134
144
  tag_name = options[:tag] || 'div'
135
145
  css_class = options[:class] || ''
136
-
146
+
147
+ # Extract script attributes from options
148
+ script_attributes = options.slice(*SCRIPT_ATTRIBUTES)
149
+ script_attributes.compact!
150
+
137
151
  # Generate the mounting script
138
- mount_script = generate_vue_mount_script(component_name, component_id, props_json)
152
+ mount_script = generate_vue_mount_script(component_name, component_id, props_json, **script_attributes)
139
153
 
140
154
  # Return the container div and script
141
155
  container_html = "<#{tag_name} id=\"#{component_id}\" class=\"#{css_class}\"></#{tag_name}>"
@@ -237,6 +251,37 @@ module IslandjsRails
237
251
 
238
252
  private
239
253
 
254
+ # Format HTML attributes into a string
255
+ # Returns a string like ' nonce="abc123" defer' or empty string if no attributes
256
+ def format_html_attributes(**attributes)
257
+ return '' if attributes.empty?
258
+
259
+ attributes.filter_map do |key, value|
260
+ next if value.nil? || value == false
261
+ key_str = key.to_s.tr('_', '-')
262
+ value == true ? " #{key_str}" : " #{key_str}=\"#{value}\""
263
+ end.join
264
+ end
265
+
266
+ # Get default script attributes with auto-nonce detection
267
+ def default_script_attributes(**user_attributes)
268
+ attributes = {}
269
+
270
+ # Auto-add nonce if CSP is enabled and not explicitly provided
271
+ if !user_attributes.key?(:nonce) && respond_to?(:content_security_policy_nonce)
272
+ nonce = content_security_policy_nonce
273
+ attributes[:nonce] = nonce if nonce
274
+ end
275
+
276
+ attributes.merge(user_attributes)
277
+ end
278
+
279
+ # Get formatted HTML attributes string for script tags
280
+ def script_html_attributes(**attributes)
281
+ script_attributes = default_script_attributes(**attributes)
282
+ format_html_attributes(**script_attributes)
283
+ end
284
+
240
285
  # Whether the floating UMD versions debug footer should render
241
286
  def umd_debug_enabled?
242
287
  return false unless Rails.env.development?
@@ -274,9 +319,11 @@ module IslandjsRails
274
319
  end
275
320
 
276
321
  # Generate React component mounting script with Turbo compatibility
277
- def generate_react_mount_script(component_name, component_id, namespace, namespace_with_optional)
322
+ def generate_react_mount_script(component_name, component_id, namespace, namespace_with_optional, **attributes)
323
+ html_attributes = script_html_attributes(**attributes)
324
+
278
325
  <<~JAVASCRIPT
279
- <script>
326
+ <script#{html_attributes}>
280
327
  (function() {
281
328
  function mount#{component_name}() {
282
329
  const container = document.getElementById('#{component_id}');
@@ -360,9 +407,11 @@ module IslandjsRails
360
407
  end
361
408
 
362
409
  # Generate Vue component mounting script with Turbo compatibility
363
- def generate_vue_mount_script(component_name, component_id, props_json)
410
+ def generate_vue_mount_script(component_name, component_id, props_json, **attributes)
411
+ html_attributes = script_html_attributes(**attributes)
412
+
364
413
  <<~JAVASCRIPT
365
- <script>
414
+ <script#{html_attributes}>
366
415
  (function() {
367
416
  let vueApp = null;
368
417
 
@@ -1,3 +1,3 @@
1
1
  module IslandjsRails
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: islandjs-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Arnold