islandjs-rails 0.5.0 → 0.7.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/CHANGELOG.md +12 -0
- data/README.md +9 -9
- data/lib/islandjs_rails/configuration.rb +14 -0
- data/lib/islandjs_rails/core.rb +7 -31
- data/lib/islandjs_rails/core_methods.rb +74 -10
- data/lib/islandjs_rails/rails_helpers.rb +3 -3
- data/lib/islandjs_rails/tasks.rb +2 -2
- data/lib/islandjs_rails/version.rb +1 -1
- data/lib/islandjs_rails.rb +2 -0
- data/lib/templates/package.json +2 -2
- data/lib/templates/webpack.config.js +36 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dbf94679b0585877e9d92723888dc23756d797d3221b74ff06f5f54dd10dddcb
|
|
4
|
+
data.tar.gz: a07f1b4306486768fcc1060c1bcc7ebedce403c3d1e36b8193c2e0dab61c4093
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e29967022d5e806b2313e67897e56f4d4a30c81e825b2fb33c1e80dce23fb213ca71a81af4a84033dc1c08db7ab4f91b7e98ed56cfca1dcd3388870f4aff8c64
|
|
7
|
+
data.tar.gz: fb5b8de3b03dc845ed4e1bb4bff6ee12c42ac1c30f0b0f2e1f4ea07340a454ea7b003e85813c6370e72b49c5698071da6262ea5e8182706f5cc62f19928b38c6
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ 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.7.0] - 2025-10-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **CleanIslandsPlugin**: Webpack plugin that automatically removes old island bundle files from the public directory after new builds, preventing stale file accumulation
|
|
12
|
+
- Simplified build scripts by removing manual `rm -f public/islands_*` commands
|
|
13
|
+
|
|
14
|
+
## [0.6.0] - 2025-10-24
|
|
15
|
+
|
|
16
|
+
- **React 19 Support Added**
|
|
17
|
+
- **Enhanced version parsing** in `cdn_package_name` with nil/empty string handling and exception safety
|
|
18
|
+
- **SSL certificate issues** with improved fallback mechanism for CDN downloads
|
|
19
|
+
|
|
8
20
|
## [0.5.0] - 2025-09-29
|
|
9
21
|
|
|
10
22
|
### Added
|
data/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
**Launch quickly:** *upgrade with vite only if necessary (MYAGNI).*
|
|
4
4
|
|
|
5
5
|
[](https://github.com/praxis-emergent/islandjs-rails/actions/workflows/github-actions-demo.yml)
|
|
6
|
-
[](coverage/index.html)
|
|
7
|
+
[](spec/)
|
|
8
8
|
[](#rails-8-ready)
|
|
9
9
|
|
|
10
10
|
IslandJS Rails supports the development of React islands in Rails apps by synchronizing `package.json` dependencies with UMD libraries served in `public/islands/vendor`.
|
|
@@ -31,8 +31,8 @@ rails islandjs:init
|
|
|
31
31
|
|
|
32
32
|
### Install React
|
|
33
33
|
```bash
|
|
34
|
-
rails "islandjs:install[react,
|
|
35
|
-
rails "islandjs:install[react-dom,
|
|
34
|
+
rails "islandjs:install[react,19.2.0]"
|
|
35
|
+
rails "islandjs:install[react-dom,19.2.0]"
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
### Run Yarn In Development
|
|
@@ -115,7 +115,7 @@ Modern Rails developers face a painful choice:
|
|
|
115
115
|
### The IslandJS Rails Solution
|
|
116
116
|
```bash
|
|
117
117
|
# Instead of complex vite/webpack configuration:
|
|
118
|
-
rails "islandjs:install[react,
|
|
118
|
+
rails "islandjs:install[react,19.2.0]"
|
|
119
119
|
rails "islandjs:install[react-beautiful-dnd]"
|
|
120
120
|
rails "islandjs:install[quill]"
|
|
121
121
|
rails "islandjs:install[recharts]"
|
|
@@ -132,7 +132,7 @@ const quill = new window.Quill("#editor", {
|
|
|
132
132
|
});
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
**Important Note:** IslandJS Rails works with packages that ship UMD builds. Many popular packages have UMD builds, but some modern packages do not
|
|
135
|
+
**Important Note:** IslandJS Rails works with packages that ship UMD builds. Many popular packages have UMD builds, but some modern packages do not. Starting with **React 19**, official UMD builds were removed, but IslandJS Rails now automatically fetches React 19+ UMD builds from the community-maintained [`umd-react`](https://github.com/lofcz/umd-react) package. No manual setup needed.
|
|
136
136
|
|
|
137
137
|
If you absolutely need a package that doesn't ship UMD builds, you have a few options:
|
|
138
138
|
|
|
@@ -170,12 +170,12 @@ rails islandjs:init
|
|
|
170
170
|
|
|
171
171
|
# Install packages (adds to package.json + saves to vendor directory)
|
|
172
172
|
rails "islandjs:install[react]"
|
|
173
|
-
rails "islandjs:install[react,
|
|
173
|
+
rails "islandjs:install[react,19.2.0]" # With specific version
|
|
174
174
|
rails "islandjs:install[lodash]"
|
|
175
175
|
|
|
176
176
|
# Update packages (updates package.json + refreshes vendor files)
|
|
177
177
|
rails "islandjs:update[react]"
|
|
178
|
-
rails "islandjs:update[react,
|
|
178
|
+
rails "islandjs:update[react,19.2.0]" # To specific version
|
|
179
179
|
|
|
180
180
|
# Remove packages (removes from package.json + deletes vendor files)
|
|
181
181
|
rails "islandjs:remove[react]"
|
|
@@ -354,7 +354,7 @@ rails "islandjs:install[@solana/web3]" # ❌ Wrong
|
|
|
354
354
|
| Command | What it does | Example |
|
|
355
355
|
|---------|--------------|---------|
|
|
356
356
|
| `install` | Adds package via yarn + downloads UMD + saves to vendor | `rails islandjs:install[react]` |
|
|
357
|
-
| `update` | Updates package version + refreshes UMD | `rails islandjs:update[react,
|
|
357
|
+
| `update` | Updates package version + refreshes UMD | `rails islandjs:update[react,19.2.0]` |
|
|
358
358
|
| `remove` | Removes package via yarn + deletes vendor files | `rails islandjs:remove[react]` |
|
|
359
359
|
| `clean` | Removes ALL vendor files (destructive!) | `rails islandjs:clean` |
|
|
360
360
|
|
|
@@ -27,6 +27,20 @@ module IslandjsRails
|
|
|
27
27
|
'@babel/preset-env' => 'babel-preset-env',
|
|
28
28
|
'@babel/preset-react' => 'babel-preset-react'
|
|
29
29
|
}.freeze
|
|
30
|
+
|
|
31
|
+
# Maps packages to alternate CDN names (e.g., React 19+ → umd-react).
|
|
32
|
+
def cdn_package_name(package_name, version)
|
|
33
|
+
# Handle nil or empty version strings
|
|
34
|
+
return package_name if version.nil? || version.empty?
|
|
35
|
+
|
|
36
|
+
# Check if React 19+ to use community-maintained umd-react package
|
|
37
|
+
if ['react', 'react-dom'].include?(package_name)
|
|
38
|
+
major_version = version.split('.').first.to_i rescue 0
|
|
39
|
+
return 'umd-react' if major_version >= 19
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
package_name
|
|
43
|
+
end
|
|
30
44
|
|
|
31
45
|
# Vendor file helper methods
|
|
32
46
|
def vendor_manifest_path
|
data/lib/islandjs_rails/core.rb
CHANGED
|
@@ -63,8 +63,8 @@ module IslandjsRails
|
|
|
63
63
|
|
|
64
64
|
puts "\n🎉 IslandjsRails initialized successfully!"
|
|
65
65
|
puts "\n📋 Next steps:"
|
|
66
|
-
puts "1. Install libraries: rails \"islandjs:install[react,
|
|
67
|
-
puts " rails \"islandjs:install[react-dom,
|
|
66
|
+
puts "1. Install libraries: rails \"islandjs:install[react,19.2.0]\""
|
|
67
|
+
puts " rails \"islandjs:install[react-dom,19.2.0]\" "
|
|
68
68
|
puts "2. Start dev: yarn watch"
|
|
69
69
|
puts "3. Use components: <%= react_component('HelloWorld') %>"
|
|
70
70
|
puts "4. Build for prod: yarn build"
|
|
@@ -281,6 +281,9 @@ module IslandjsRails
|
|
|
281
281
|
version ||= version_for(package_name)
|
|
282
282
|
return nil unless version
|
|
283
283
|
|
|
284
|
+
# Use cdn_package_name to handle React 19+ mapping to umd-react
|
|
285
|
+
cdn_package = configuration.cdn_package_name(package_name, version)
|
|
286
|
+
|
|
284
287
|
# Use original package name for URL, but get clean name for {name} substitution
|
|
285
288
|
clean_name = (Configuration::SCOPED_PACKAGE_MAPPINGS[package_name] || package_name).split('/').last
|
|
286
289
|
|
|
@@ -292,8 +295,7 @@ module IslandjsRails
|
|
|
292
295
|
else
|
|
293
296
|
pattern # Use pattern as-is for fixed filenames like IIFE
|
|
294
297
|
end
|
|
295
|
-
url = "#{cdn_base}/#{
|
|
296
|
-
|
|
298
|
+
url = "#{cdn_base}/#{cdn_package}@#{version}/#{path}"
|
|
297
299
|
|
|
298
300
|
if url_accessible?(url)
|
|
299
301
|
puts "✓ Found island: #{url}"
|
|
@@ -306,20 +308,6 @@ module IslandjsRails
|
|
|
306
308
|
nil
|
|
307
309
|
end
|
|
308
310
|
|
|
309
|
-
def download_umd_content(url)
|
|
310
|
-
require 'net/http'
|
|
311
|
-
require 'uri'
|
|
312
|
-
|
|
313
|
-
uri = URI(url)
|
|
314
|
-
response = Net::HTTP.get_response(uri)
|
|
315
|
-
|
|
316
|
-
unless response.code == '200'
|
|
317
|
-
raise IslandjsRails::Error, "Failed to download UMD from #{url}: #{response.code}"
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
response.body
|
|
321
|
-
end
|
|
322
|
-
|
|
323
311
|
def find_working_umd_url(package_name, version)
|
|
324
312
|
puts " 🔍 Searching for UMD build..."
|
|
325
313
|
|
|
@@ -334,7 +322,7 @@ module IslandjsRails
|
|
|
334
322
|
else
|
|
335
323
|
pattern # Use pattern as-is for fixed filenames like IIFE
|
|
336
324
|
end
|
|
337
|
-
url = "#{cdn_base}/#{package_name}@#{version}/#{path}"
|
|
325
|
+
url = "#{cdn_base}/#{configuration.cdn_package_name(package_name, version)}@#{version}/#{path}"
|
|
338
326
|
|
|
339
327
|
|
|
340
328
|
if url_accessible?(url)
|
|
@@ -353,18 +341,6 @@ module IslandjsRails
|
|
|
353
341
|
end
|
|
354
342
|
|
|
355
343
|
private
|
|
356
|
-
|
|
357
|
-
# Check if a URL is accessible (returns 200 status)
|
|
358
|
-
def url_accessible?(url)
|
|
359
|
-
require 'net/http'
|
|
360
|
-
require 'uri'
|
|
361
|
-
|
|
362
|
-
uri = URI(url)
|
|
363
|
-
response = Net::HTTP.get_response(uri)
|
|
364
|
-
response.code == '200'
|
|
365
|
-
rescue => e
|
|
366
|
-
false
|
|
367
|
-
end
|
|
368
344
|
|
|
369
345
|
# Check if a package has a partial file
|
|
370
346
|
def has_partial?(package_name)
|
|
@@ -37,8 +37,16 @@ module IslandjsRails
|
|
|
37
37
|
return
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Skip prompt in non-interactive mode
|
|
41
|
+
unless STDIN.tty?
|
|
42
|
+
puts "\n💡 To render your HelloWorld component:"
|
|
43
|
+
puts " In any view: <%= react_component('HelloWorld') %>"
|
|
44
|
+
puts " Don't forget to: yarn build && rails server"
|
|
45
|
+
return
|
|
46
|
+
end
|
|
47
|
+
|
|
40
48
|
print "\n❓ Would you like to create a demo route at /islandjs/react to showcase your HelloWorld component? (y/n): "
|
|
41
|
-
answer = STDIN.gets
|
|
49
|
+
answer = STDIN.gets&.chomp&.downcase
|
|
42
50
|
|
|
43
51
|
if answer == 'y' || answer == 'yes'
|
|
44
52
|
create_demo_route!
|
|
@@ -393,15 +401,36 @@ module IslandjsRails
|
|
|
393
401
|
create_partial_file(package_name, umd_content, global_name)
|
|
394
402
|
end
|
|
395
403
|
|
|
396
|
-
def download_umd_content(url)
|
|
404
|
+
def download_umd_content(url, use_ssl_verification = true)
|
|
405
|
+
require 'openssl'
|
|
397
406
|
uri = URI(url)
|
|
398
|
-
response = Net::HTTP.get_response(uri)
|
|
399
407
|
|
|
400
|
-
|
|
408
|
+
http_options = {
|
|
409
|
+
use_ssl: uri.scheme == 'https',
|
|
410
|
+
verify_mode: use_ssl_verification ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
begin
|
|
414
|
+
body = Net::HTTP.start(uri.host, uri.port, **http_options) do |http|
|
|
415
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
|
416
|
+
response = http.request(request)
|
|
417
|
+
|
|
418
|
+
if response.code == '200'
|
|
419
|
+
response.body
|
|
420
|
+
else
|
|
421
|
+
raise IslandjsRails::Error, "Failed to download UMD from #{url}: #{response.code}"
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
401
425
|
# Force UTF-8 encoding to avoid encoding errors when writing to file
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
426
|
+
body.force_encoding('UTF-8')
|
|
427
|
+
rescue OpenSSL::SSL::SSLError => ssl_error
|
|
428
|
+
if use_ssl_verification
|
|
429
|
+
$stderr.puts "⚠️ SSL verification failed, retrying without verification: #{ssl_error.message}"
|
|
430
|
+
return download_umd_content(url, false)
|
|
431
|
+
else
|
|
432
|
+
raise IslandjsRails::Error, "Failed to download UMD from #{url}: #{ssl_error.message}"
|
|
433
|
+
end
|
|
405
434
|
end
|
|
406
435
|
end
|
|
407
436
|
|
|
@@ -536,10 +565,45 @@ module IslandjsRails
|
|
|
536
565
|
copy_template_file('webpack.config.js', configuration.webpack_config_path)
|
|
537
566
|
end
|
|
538
567
|
|
|
539
|
-
def url_accessible?(url)
|
|
568
|
+
def url_accessible?(url, limit = 5, use_ssl_verification = true)
|
|
569
|
+
require 'openssl'
|
|
570
|
+
return false if limit == 0
|
|
571
|
+
|
|
540
572
|
uri = URI(url)
|
|
541
|
-
|
|
542
|
-
|
|
573
|
+
|
|
574
|
+
# Use proper SSL verification by default, but allow fallback for certificate issues
|
|
575
|
+
http_options = {
|
|
576
|
+
use_ssl: uri.scheme == 'https',
|
|
577
|
+
verify_mode: use_ssl_verification ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
begin
|
|
581
|
+
Net::HTTP.start(uri.host, uri.port, **http_options) do |http|
|
|
582
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
|
583
|
+
response = http.request(request)
|
|
584
|
+
|
|
585
|
+
case response
|
|
586
|
+
when Net::HTTPSuccess
|
|
587
|
+
return true
|
|
588
|
+
when Net::HTTPRedirection
|
|
589
|
+
# Follow redirects
|
|
590
|
+
location = response['location']
|
|
591
|
+
return url_accessible?(location, limit - 1, use_ssl_verification)
|
|
592
|
+
else
|
|
593
|
+
return false
|
|
594
|
+
end
|
|
595
|
+
end
|
|
596
|
+
rescue OpenSSL::SSL::SSLError => ssl_error
|
|
597
|
+
# If SSL verification fails and we're using verification, try once without it
|
|
598
|
+
# This handles local certificate store issues while still attempting security first
|
|
599
|
+
if use_ssl_verification
|
|
600
|
+
$stderr.puts "⚠️ SSL verification failed, retrying without verification: #{ssl_error.message}"
|
|
601
|
+
return url_accessible?(url, limit, false)
|
|
602
|
+
else
|
|
603
|
+
# Already tried without verification, give up
|
|
604
|
+
false
|
|
605
|
+
end
|
|
606
|
+
end
|
|
543
607
|
rescue => e
|
|
544
608
|
false
|
|
545
609
|
end
|
|
@@ -354,13 +354,13 @@ module IslandjsRails
|
|
|
354
354
|
const element = React.createElement(#{namespace_with_optional}.#{component_name}, props);
|
|
355
355
|
|
|
356
356
|
try {
|
|
357
|
-
// Use React
|
|
357
|
+
// Use React 19 createRoot if available, fallback to React 17 render
|
|
358
358
|
if (window.ReactDOM.createRoot) {
|
|
359
359
|
if (!container._reactRoot) {
|
|
360
360
|
container._reactRoot = window.ReactDOM.createRoot(container);
|
|
361
361
|
}
|
|
362
362
|
container._reactRoot.render(element);
|
|
363
|
-
// React
|
|
363
|
+
// React 19 automatically clears container - no manual cleanup needed
|
|
364
364
|
} else {
|
|
365
365
|
// React 17 - render is synchronous and clears container automatically
|
|
366
366
|
window.ReactDOM.render(element, container);
|
|
@@ -375,7 +375,7 @@ module IslandjsRails
|
|
|
375
375
|
const container = document.getElementById('#{component_id}');
|
|
376
376
|
if (!container) return;
|
|
377
377
|
|
|
378
|
-
// React
|
|
378
|
+
// React 19 unmount
|
|
379
379
|
if (container._reactRoot) {
|
|
380
380
|
container._reactRoot.unmount();
|
|
381
381
|
container._reactRoot = null;
|
data/lib/islandjs_rails/tasks.rb
CHANGED
|
@@ -13,7 +13,7 @@ namespace :islandjs do
|
|
|
13
13
|
|
|
14
14
|
if package_name.nil?
|
|
15
15
|
puts "❌ Package name is required"
|
|
16
|
-
puts "Usage: rails \"islandjs:install[react,
|
|
16
|
+
puts "Usage: rails \"islandjs:install[react,19.2.0]\""
|
|
17
17
|
exit 1
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -27,7 +27,7 @@ namespace :islandjs do
|
|
|
27
27
|
|
|
28
28
|
if package_name.nil?
|
|
29
29
|
puts "❌ Package name is required"
|
|
30
|
-
puts "Usage: rails \"islandjs:update[react,
|
|
30
|
+
puts "Usage: rails \"islandjs:update[react,19.2.0]\""
|
|
31
31
|
exit 1
|
|
32
32
|
end
|
|
33
33
|
|
data/lib/islandjs_rails.rb
CHANGED
data/lib/templates/package.json
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
|
-
"build": "
|
|
7
|
-
"build:dev": "
|
|
6
|
+
"build": "NODE_ENV=production webpack",
|
|
7
|
+
"build:dev": "NODE_ENV=production webpack",
|
|
8
8
|
"watch": "NODE_ENV=development webpack --watch"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {},
|
|
@@ -1,9 +1,44 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const TerserPlugin = require('terser-webpack-plugin');
|
|
3
3
|
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
|
|
4
|
+
const fs = require('fs');
|
|
4
5
|
|
|
5
6
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
6
7
|
|
|
8
|
+
// Custom plugin to clean old island bundle files
|
|
9
|
+
class CleanIslandsPlugin {
|
|
10
|
+
apply(compiler) {
|
|
11
|
+
compiler.hooks.afterEmit.tap('CleanIslandsPlugin', (compilation) => {
|
|
12
|
+
const publicDir = path.resolve(__dirname, 'public');
|
|
13
|
+
if (!fs.existsSync(publicDir)) return;
|
|
14
|
+
|
|
15
|
+
// Get the newly emitted files
|
|
16
|
+
const emittedFiles = Object.keys(compilation.assets).map(
|
|
17
|
+
filename => filename.split('/').pop() // Get just the filename
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const files = fs.readdirSync(publicDir);
|
|
21
|
+
files.forEach(file => {
|
|
22
|
+
// Clean old islands files, but keep the newly emitted ones
|
|
23
|
+
// Also include .map and .LICENSE.txt files
|
|
24
|
+
const isEmitted = emittedFiles.includes(file) ||
|
|
25
|
+
emittedFiles.some(ef => file.startsWith(ef) && (
|
|
26
|
+
file.endsWith('.map') || file.endsWith('.LICENSE.txt')
|
|
27
|
+
));
|
|
28
|
+
|
|
29
|
+
if (file.startsWith('islands_') && !isEmitted) {
|
|
30
|
+
const filePath = path.join(publicDir, file);
|
|
31
|
+
try {
|
|
32
|
+
fs.unlinkSync(filePath);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
// Ignore errors
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
7
42
|
module.exports = {
|
|
8
43
|
mode: isProduction ? 'production' : 'development',
|
|
9
44
|
entry: {
|
|
@@ -40,6 +75,7 @@ module.exports = {
|
|
|
40
75
|
minimizer: [new TerserPlugin()]
|
|
41
76
|
},
|
|
42
77
|
plugins: [
|
|
78
|
+
new CleanIslandsPlugin(),
|
|
43
79
|
new WebpackManifestPlugin({
|
|
44
80
|
fileName: 'islands_manifest.json',
|
|
45
81
|
publicPath: '/'
|