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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 80cbddb05a05365fd47474178fd2fb2b403f69c597169fe16bcb93e558dbabb7
4
- data.tar.gz: f342dd7687658b6c8df47cfa7566f2061517112eb248719c997b20ea2634d5bf
3
+ metadata.gz: dbf94679b0585877e9d92723888dc23756d797d3221b74ff06f5f54dd10dddcb
4
+ data.tar.gz: a07f1b4306486768fcc1060c1bcc7ebedce403c3d1e36b8193c2e0dab61c4093
5
5
  SHA512:
6
- metadata.gz: 5e3ed8b8c57007f2011c9b875cef7a84b4b4f262038bc2f9c92cc7e98ebfd9ed66b73c2b0fd7e9a1da9bee499b2a036f0c9ed48dd34dd9fdc99e680d82dae908
7
- data.tar.gz: ec2afebea06a97b95b7c22b71596c30b704e44bbf5c5a8b0579a497b5b44f89056efcdc5dbf2179564d74b921301740e4feef340d8257c3adbd7c72203c9b703
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
  [![CI](https://github.com/praxis-emergent/islandjs-rails/actions/workflows/github-actions-demo.yml/badge.svg)](https://github.com/praxis-emergent/islandjs-rails/actions/workflows/github-actions-demo.yml)
6
- [![Test Coverage](https://img.shields.io/badge/coverage-89.09%25-brightgreen.svg)](coverage/index.html)
7
- [![RSpec Tests](https://img.shields.io/badge/tests-162%20passing-brightgreen.svg)](spec/)
6
+ [![Test Coverage](https://img.shields.io/badge/coverage-86.63%25-brightgreen.svg)](coverage/index.html)
7
+ [![RSpec Tests](https://img.shields.io/badge/tests-367%20passing-brightgreen.svg)](spec/)
8
8
  [![Rails 8 Ready](https://img.shields.io/badge/Rails%208-Ready-brightgreen.svg)](#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,18.3.1]"
35
- rails "islandjs:install[react-dom,18.3.1]"
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,18.3.1]"
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 React 19+ removed UMD builds entirely. Future versions of IslandJS Rails will support local UMD generation for some packages (such as [React 19+](https://github.com/lofcz/umd-react)).
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,18.3.1]" # With specific version
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,18.3.1]" # To specific version
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,18.3.1]` |
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
@@ -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,18.3.1]\""
67
- puts " rails \"islandjs:install[react-dom,18.3.1]\" "
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}/#{package_name}@#{version}/#{path}"
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.chomp.downcase
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
- if response.code == '200'
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
- response.body.force_encoding('UTF-8')
403
- else
404
- raise IslandjsRails::Error, "Failed to download UMD from #{url}: #{response.code}"
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
- response = Net::HTTP.get_response(uri)
542
- response.code == '200'
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 18 createRoot if available, fallback to React 17 render
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 18 automatically clears container - no manual cleanup needed
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 18 unmount
378
+ // React 19 unmount
379
379
  if (container._reactRoot) {
380
380
  container._reactRoot.unmount();
381
381
  container._reactRoot = null;
@@ -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,18.3.1]\""
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,18.3.1]\""
30
+ puts "Usage: rails \"islandjs:update[react,19.2.0]\""
31
31
  exit 1
32
32
  end
33
33
 
@@ -1,3 +1,3 @@
1
1
  module IslandjsRails
2
- VERSION = "0.5.0"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -22,6 +22,8 @@ module IslandjsRails
22
22
  'umd/{name}.production.min.js',
23
23
  'umd/{name}.development.js',
24
24
  'umd/{name}.min.js',
25
+ 'umd/{name}.js',
26
+ 'dist/{name}.production.min.js',
25
27
  'umd/{name}.js',
26
28
  'dist/{name}.min.js',
27
29
  'dist/{name}.js',
@@ -3,8 +3,8 @@
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
5
  "scripts": {
6
- "build": "rm -f public/islands_* && NODE_ENV=production webpack",
7
- "build:dev": "rm -f public/islands_* && NODE_ENV=production webpack",
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: '/'
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.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Arnold