islandjs-rails 0.1.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/CHANGELOG.md +16 -0
- data/LICENSE.md +22 -0
- data/README.md +754 -0
- data/exe/islandjs-rails +6 -0
- data/islandjs-rails.gemspec +55 -0
- data/lib/islandjs-rails.rb +3 -0
- data/lib/islandjs_rails/cli.rb +57 -0
- data/lib/islandjs_rails/configuration.rb +49 -0
- data/lib/islandjs_rails/core.rb +462 -0
- data/lib/islandjs_rails/core_methods.rb +609 -0
- data/lib/islandjs_rails/rails_helpers.rb +394 -0
- data/lib/islandjs_rails/railtie.rb +59 -0
- data/lib/islandjs_rails/tasks.rb +118 -0
- data/lib/islandjs_rails/vendor_manager.rb +271 -0
- data/lib/islandjs_rails/version.rb +3 -0
- data/lib/islandjs_rails.rb +142 -0
- data/lib/templates/app/controllers/islandjs_demo_controller.rb +9 -0
- data/lib/templates/app/javascript/islands/components/.gitkeep +0 -0
- data/lib/templates/app/javascript/islands/components/HelloWorld.jsx +117 -0
- data/lib/templates/app/javascript/islands/index.js +10 -0
- data/lib/templates/app/javascript/islands/utils/turbo.js +87 -0
- data/lib/templates/app/views/islandjs_demo/index.html.erb +98 -0
- data/lib/templates/app/views/islandjs_demo/react.html.erb +93 -0
- data/lib/templates/config/demo_routes.rb +3 -0
- data/lib/templates/package.json +21 -0
- data/lib/templates/webpack.config.js +49 -0
- data/package.json +12 -0
- data/yarn.lock +1890 -0
- metadata +181 -0
data/exe/islandjs-rails
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative "lib/islandjs_rails/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "islandjs-rails"
|
5
|
+
spec.version = IslandjsRails::VERSION
|
6
|
+
spec.authors = ["Eric Arnold"]
|
7
|
+
spec.email = ["ericarnold00+praxisemergent@gmail.com"]
|
8
|
+
|
9
|
+
spec.summary = "Simple, modern JavaScript islands for Rails"
|
10
|
+
spec.description = "IslandJS Rails enables React and other JavaScript islands in Rails apps with zero webpack configuration. Load UMD libraries from CDNs, integrate with ERB partials, and render components with Turbo-compatible lifecycle management."
|
11
|
+
spec.homepage = "https://github.com/praxis-emergent/islandjs-rails"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = ">= 3.0.0"
|
14
|
+
|
15
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
16
|
+
spec.metadata["source_code_uri"] = "https://github.com/praxis-emergent/islandjs-rails"
|
17
|
+
spec.metadata["changelog_uri"] = "https://github.com/praxis-emergent/islandjs-rails/blob/main/CHANGELOG.md"
|
18
|
+
spec.metadata["documentation_uri"] = "https://github.com/praxis-emergent/islandjs-rails/blob/main/README.md"
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
spec.files = Dir.chdir(__dir__) do
|
22
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
23
|
+
(File.expand_path(f) == __FILE__) ||
|
24
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github .claude appveyor Gemfile]) ||
|
25
|
+
f.match?(%r{\A(\.rspec|Rakefile)\z}) ||
|
26
|
+
f.end_with?(".gem")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
spec.bindir = "exe"
|
31
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ["lib"]
|
33
|
+
|
34
|
+
# Post-install message
|
35
|
+
spec.post_install_message = <<~MSG
|
36
|
+
|
37
|
+
šļø IslandJS Rails installed successfully!
|
38
|
+
|
39
|
+
š Next step: Initialize IslandJS in your Rails app
|
40
|
+
|
41
|
+
rails islandjs:init
|
42
|
+
|
43
|
+
MSG
|
44
|
+
|
45
|
+
# Rails integration
|
46
|
+
spec.add_dependency "rails", ">= 7.0", "< 9.0"
|
47
|
+
spec.add_dependency "thor", "~> 1.0"
|
48
|
+
|
49
|
+
# Development dependencies
|
50
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
51
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
52
|
+
spec.add_development_dependency "webmock", "~> 3.0"
|
53
|
+
spec.add_development_dependency "vcr", "~> 6.0"
|
54
|
+
spec.add_development_dependency "simplecov", "~> 0.22"
|
55
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module IslandjsRails
|
4
|
+
class CLI < Thor
|
5
|
+
desc "init", "Initialize IslandJS in this Rails project"
|
6
|
+
def init
|
7
|
+
IslandjsRails.init!
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "install PACKAGE_NAME [VERSION]", "Install a JavaScript island package"
|
11
|
+
def install(package_name, version = nil)
|
12
|
+
IslandjsRails.install!(package_name, version)
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "update PACKAGE_NAME [VERSION]", "Update a JavaScript island package"
|
16
|
+
def update(package_name, version = nil)
|
17
|
+
IslandjsRails.update!(package_name, version)
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "remove PACKAGE_NAME", "Remove a JavaScript island package"
|
21
|
+
def remove(package_name)
|
22
|
+
IslandjsRails.remove!(package_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "sync", "Sync all JavaScript island packages with current package.json"
|
26
|
+
def sync
|
27
|
+
IslandjsRails.sync!
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "status", "Show status of all JavaScript island packages"
|
31
|
+
def status
|
32
|
+
IslandjsRails.status!
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "clean", "Clean all island partials and reset webpack externals"
|
36
|
+
def clean
|
37
|
+
IslandjsRails.clean!
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "config", "Show IslandJS configuration"
|
41
|
+
def config
|
42
|
+
config = IslandjsRails.configuration
|
43
|
+
puts "š IslandjsRails Configuration"
|
44
|
+
puts "=" * 40
|
45
|
+
puts "Package.json path: #{config.package_json_path}"
|
46
|
+
puts "Partials directory: #{config.partials_dir}"
|
47
|
+
puts "Webpack config path: #{config.webpack_config_path}"
|
48
|
+
puts "Supported CDNs: #{config.supported_cdns.join(', ')}"
|
49
|
+
puts "Built-in global name overrides: #{IslandjsRails::BUILT_IN_GLOBAL_NAME_OVERRIDES.size} available"
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "version", "Show IslandJS Rails version"
|
53
|
+
def version
|
54
|
+
puts "IslandjsRails #{IslandjsRails::VERSION}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module IslandjsRails
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :package_json_path, :partials_dir, :webpack_config_path, :supported_cdns,
|
6
|
+
:vendor_script_mode, :vendor_order, :vendor_dir, :combined_basename
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@package_json_path = Rails.root.join('package.json')
|
10
|
+
@partials_dir = Rails.root.join('app', 'views', 'shared', 'islands')
|
11
|
+
@webpack_config_path = Rails.root.join('webpack.config.js')
|
12
|
+
@vendor_script_mode = :external_split # :external_split or :external_combined
|
13
|
+
@vendor_order = %w[react react-dom] # combine order for :external_combined
|
14
|
+
@vendor_dir = Rails.root.join('public', 'islands', 'vendor')
|
15
|
+
@combined_basename = 'islands-vendor'
|
16
|
+
@supported_cdns = [
|
17
|
+
'https://unpkg.com',
|
18
|
+
'https://cdn.jsdelivr.net/npm'
|
19
|
+
]
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# Scoped package name mappings
|
24
|
+
SCOPED_PACKAGE_MAPPINGS = {
|
25
|
+
'@solana/web3.js' => 'solana-web3.js',
|
26
|
+
'@babel/core' => 'babel-core',
|
27
|
+
'@babel/preset-env' => 'babel-preset-env',
|
28
|
+
'@babel/preset-react' => 'babel-preset-react'
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
# Vendor file helper methods
|
32
|
+
def vendor_manifest_path
|
33
|
+
@vendor_dir.join('manifest.json')
|
34
|
+
end
|
35
|
+
|
36
|
+
def vendor_partial_path
|
37
|
+
@partials_dir.join('_vendor_umd.html.erb')
|
38
|
+
end
|
39
|
+
|
40
|
+
def vendor_file_path(package_name, version)
|
41
|
+
safe_name = package_name.gsub(/[@\/]/, '_').gsub(/-/, '_')
|
42
|
+
@vendor_dir.join("#{safe_name}-#{version}.min.js")
|
43
|
+
end
|
44
|
+
|
45
|
+
def combined_vendor_path(hash)
|
46
|
+
@vendor_dir.join("#{@combined_basename}-#{hash}.js")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,462 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'open3'
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module IslandjsRails
|
8
|
+
class Core
|
9
|
+
attr_reader :configuration
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@configuration = IslandjsRails.configuration
|
13
|
+
end
|
14
|
+
|
15
|
+
# Essential dependencies for IslandJS webpack setup
|
16
|
+
ESSENTIAL_DEPENDENCIES = [
|
17
|
+
'webpack@^5.88.2',
|
18
|
+
'webpack-cli@^5.1.4',
|
19
|
+
'terser-webpack-plugin@^5.3.14',
|
20
|
+
'webpack-manifest-plugin@^5.0.1',
|
21
|
+
'babel-loader@^9.1.3',
|
22
|
+
'@babel/core@^7.23.0',
|
23
|
+
'@babel/preset-env@^7.23.0',
|
24
|
+
'@babel/preset-react@^7.23.0'
|
25
|
+
].freeze
|
26
|
+
|
27
|
+
# Initialize IslandJS in a Rails project
|
28
|
+
def init!
|
29
|
+
puts "š Initializing IslandjsRails..."
|
30
|
+
|
31
|
+
# Step 1: Check for required tools
|
32
|
+
check_node_tools!
|
33
|
+
|
34
|
+
# Step 2: Ensure package.json exists
|
35
|
+
ensure_package_json!
|
36
|
+
|
37
|
+
# Step 3: Install essential webpack dependencies
|
38
|
+
install_essential_dependencies!
|
39
|
+
|
40
|
+
# Step 4: Create scaffolded structure
|
41
|
+
create_scaffolded_structure!
|
42
|
+
|
43
|
+
# Step 5: Create directories
|
44
|
+
FileUtils.mkdir_p(configuration.partials_dir)
|
45
|
+
FileUtils.mkdir_p(configuration.vendor_dir)
|
46
|
+
puts "ā Created #{configuration.partials_dir}"
|
47
|
+
puts "ā Created #{configuration.vendor_dir}"
|
48
|
+
|
49
|
+
# Step 6: Generate webpack config if it doesn't exist
|
50
|
+
unless File.exist?(configuration.webpack_config_path)
|
51
|
+
generate_webpack_config!
|
52
|
+
puts "ā Generated webpack.config.js"
|
53
|
+
else
|
54
|
+
puts "ā webpack.config.js already exists"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Step 7: Set up vendor system and inject into layout
|
58
|
+
setup_vendor_system!
|
59
|
+
inject_islands_helper_into_layout!
|
60
|
+
|
61
|
+
# Step 8: Add node_modules to .gitignore
|
62
|
+
ensure_node_modules_gitignored!
|
63
|
+
|
64
|
+
puts "\nš IslandjsRails initialized successfully!"
|
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]\" "
|
68
|
+
puts "2. Start dev: yarn watch"
|
69
|
+
puts "3. Use components: <%= react_component('HelloWorld') %>"
|
70
|
+
puts "4. Build for prod: yarn build"
|
71
|
+
puts "5. Commit assets: git add public/islands_* && git add public/islands/*"
|
72
|
+
|
73
|
+
puts "\nš Rails 8 Ready: Commit your built assets for bulletproof deploys!"
|
74
|
+
puts "š” IslandjsRails is framework-agnostic - use React, Vue, or any UMD library!"
|
75
|
+
puts "š Ready to build!"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Install a new island package
|
79
|
+
def install!(package_name, version = nil)
|
80
|
+
puts "š¦ Installing UMD package: #{package_name}"
|
81
|
+
|
82
|
+
# Check if React ecosystem was incomplete before this install
|
83
|
+
was_react_ecosystem_incomplete = !react_ecosystem_complete?
|
84
|
+
|
85
|
+
# Add to package.json via yarn if not present
|
86
|
+
add_package_via_yarn(package_name, version) unless package_installed?(package_name)
|
87
|
+
|
88
|
+
# Install to vendor directory
|
89
|
+
vendor_manager = IslandjsRails.vendor_manager
|
90
|
+
success = vendor_manager.install_package!(package_name, version)
|
91
|
+
|
92
|
+
return false unless success
|
93
|
+
|
94
|
+
global_name = detect_global_name(package_name)
|
95
|
+
update_webpack_externals(package_name, global_name)
|
96
|
+
|
97
|
+
puts "ā
Successfully installed #{package_name}!"
|
98
|
+
|
99
|
+
# Auto-scaffold React if ecosystem just became complete
|
100
|
+
if was_react_ecosystem_incomplete && react_ecosystem_complete? &&
|
101
|
+
(package_name == 'react' || package_name == 'react-dom')
|
102
|
+
activate_react_scaffolding!
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Update an existing package
|
107
|
+
def update!(package_name, version = nil)
|
108
|
+
puts "š Updating UMD package: #{package_name}"
|
109
|
+
|
110
|
+
unless package_installed?(package_name)
|
111
|
+
raise IslandjsRails::PackageNotFoundError, "#{package_name} is not installed. Use 'install' instead."
|
112
|
+
end
|
113
|
+
|
114
|
+
# Update package.json via yarn
|
115
|
+
yarn_update!(package_name, version)
|
116
|
+
|
117
|
+
# Re-install to vendor directory
|
118
|
+
vendor_manager = IslandjsRails.vendor_manager
|
119
|
+
vendor_manager.install_package!(package_name, version)
|
120
|
+
|
121
|
+
# Update webpack externals
|
122
|
+
global_name = detect_global_name(package_name)
|
123
|
+
update_webpack_externals(package_name, global_name)
|
124
|
+
|
125
|
+
puts "ā
Successfully updated #{package_name}!"
|
126
|
+
end
|
127
|
+
|
128
|
+
# Remove a specific package
|
129
|
+
def remove!(package_name)
|
130
|
+
puts "šļø Removing island package: #{package_name}"
|
131
|
+
|
132
|
+
unless package_installed?(package_name)
|
133
|
+
raise IslandjsRails::PackageNotFoundError, "Package #{package_name} is not installed"
|
134
|
+
end
|
135
|
+
|
136
|
+
remove_package_via_yarn(package_name)
|
137
|
+
|
138
|
+
# Remove from vendor directory
|
139
|
+
vendor_manager = IslandjsRails.vendor_manager
|
140
|
+
vendor_manager.remove_package!(package_name)
|
141
|
+
|
142
|
+
update_webpack_externals
|
143
|
+
puts "ā
Successfully removed #{package_name}!"
|
144
|
+
end
|
145
|
+
|
146
|
+
# Sync all packages
|
147
|
+
def sync!
|
148
|
+
puts "š Syncing all UMD packages..."
|
149
|
+
|
150
|
+
packages = installed_packages
|
151
|
+
if packages.empty?
|
152
|
+
puts "š¦ No packages found in package.json"
|
153
|
+
return
|
154
|
+
end
|
155
|
+
|
156
|
+
vendor_manager = IslandjsRails.vendor_manager
|
157
|
+
|
158
|
+
packages.each do |package_name|
|
159
|
+
next unless supported_package?(package_name)
|
160
|
+
puts " š¦ Processing #{package_name}..."
|
161
|
+
|
162
|
+
# Get version from package.json
|
163
|
+
version = version_for(package_name)
|
164
|
+
|
165
|
+
# Install to vendor system
|
166
|
+
vendor_manager.install_package!(package_name, version)
|
167
|
+
|
168
|
+
# Update webpack externals
|
169
|
+
global_name = detect_global_name(package_name)
|
170
|
+
update_webpack_externals(package_name, global_name)
|
171
|
+
end
|
172
|
+
|
173
|
+
puts "ā
Sync completed!"
|
174
|
+
end
|
175
|
+
|
176
|
+
# Show status of all packages
|
177
|
+
def status!
|
178
|
+
puts "š IslandjsRails Status"
|
179
|
+
puts "=" * 40
|
180
|
+
|
181
|
+
packages = installed_packages
|
182
|
+
if packages.empty?
|
183
|
+
puts "š¦ No packages found in package.json"
|
184
|
+
return
|
185
|
+
end
|
186
|
+
|
187
|
+
# Check vendor system instead of partials
|
188
|
+
vendor_manager = IslandjsRails.vendor_manager
|
189
|
+
manifest = vendor_manager.send(:read_manifest)
|
190
|
+
vendor_packages = manifest['libs'].map { |lib| lib['name'] }
|
191
|
+
|
192
|
+
packages.each do |package_name|
|
193
|
+
version = version_for(package_name)
|
194
|
+
has_vendor = vendor_packages.include?(package_name)
|
195
|
+
status_icon = has_vendor ? "ā
" : "ā"
|
196
|
+
puts "#{status_icon} #{package_name}@#{version} #{has_vendor ? '(vendor ready)' : '(missing vendor)'}"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Clean vendor files and rebuild
|
201
|
+
def clean!
|
202
|
+
puts "š§¹ Cleaning vendor files..."
|
203
|
+
|
204
|
+
vendor_manager = IslandjsRails.vendor_manager
|
205
|
+
|
206
|
+
# Clean vendor files
|
207
|
+
if Dir.exist?(configuration.vendor_dir)
|
208
|
+
Dir.glob(File.join(configuration.vendor_dir, '*.js')).each do |file|
|
209
|
+
File.delete(file)
|
210
|
+
puts " ā Removed #{File.basename(file)}"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Reset vendor manifest by writing empty manifest
|
215
|
+
empty_manifest = { 'libs' => [] }
|
216
|
+
vendor_manager.send(:write_manifest, empty_manifest)
|
217
|
+
puts " ā Reset vendor manifest"
|
218
|
+
|
219
|
+
# Regenerate vendor partial
|
220
|
+
vendor_manager.send(:regenerate_vendor_partial!)
|
221
|
+
puts " ā Regenerated vendor partial"
|
222
|
+
|
223
|
+
# Reset webpack externals
|
224
|
+
reset_webpack_externals
|
225
|
+
puts " ā Reset webpack externals"
|
226
|
+
|
227
|
+
# Reinstall all packages from package.json
|
228
|
+
installed_packages.each do |package_name, version|
|
229
|
+
puts " š¦ Reinstalling #{package_name}@#{version}..."
|
230
|
+
vendor_manager.install_package!(package_name, version)
|
231
|
+
global_name = detect_global_name(package_name)
|
232
|
+
update_webpack_externals(package_name, global_name)
|
233
|
+
end
|
234
|
+
|
235
|
+
puts "ā
Clean completed!"
|
236
|
+
end
|
237
|
+
|
238
|
+
# Public methods for external access
|
239
|
+
def package_installed?(package_name)
|
240
|
+
return false unless File.exist?(configuration.package_json_path)
|
241
|
+
|
242
|
+
begin
|
243
|
+
package_data = JSON.parse(File.read(configuration.package_json_path))
|
244
|
+
dependencies = package_data.dig('dependencies') || {}
|
245
|
+
dev_dependencies = package_data.dig('devDependencies') || {}
|
246
|
+
|
247
|
+
dependencies.key?(package_name) || dev_dependencies.key?(package_name)
|
248
|
+
rescue JSON::ParserError, Errno::ENOENT
|
249
|
+
false
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def detect_global_name(package_name, url = nil)
|
254
|
+
# Check built-in overrides first
|
255
|
+
override = IslandjsRails::BUILT_IN_GLOBAL_NAME_OVERRIDES[package_name]
|
256
|
+
return override if override
|
257
|
+
|
258
|
+
# For scoped packages, use the package name part
|
259
|
+
clean_name = package_name.include?('/') ? package_name.split('/').last : package_name
|
260
|
+
|
261
|
+
# Convert kebab-case to camelCase
|
262
|
+
clean_name.split('-').map.with_index { |part, i| i == 0 ? part : part.capitalize }.join
|
263
|
+
end
|
264
|
+
|
265
|
+
def version_for(library_name)
|
266
|
+
package_data = package_json
|
267
|
+
return nil unless package_data
|
268
|
+
|
269
|
+
dependencies = package_data.dig('dependencies') || {}
|
270
|
+
dev_dependencies = package_data.dig('devDependencies') || {}
|
271
|
+
|
272
|
+
version = dependencies[library_name] || dev_dependencies[library_name]
|
273
|
+
return nil unless version
|
274
|
+
|
275
|
+
version.gsub(/[\^~>=<]/, '')
|
276
|
+
end
|
277
|
+
|
278
|
+
def find_working_island_url(package_name, version)
|
279
|
+
puts "š Searching for island build..."
|
280
|
+
|
281
|
+
version ||= version_for(package_name)
|
282
|
+
return nil unless version
|
283
|
+
|
284
|
+
# Use original package name for URL, but get clean name for {name} substitution
|
285
|
+
clean_name = (Configuration::SCOPED_PACKAGE_MAPPINGS[package_name] || package_name).split('/').last
|
286
|
+
|
287
|
+
configuration.supported_cdns.each do |cdn_base|
|
288
|
+
IslandjsRails::UMD_PATH_PATTERNS.each do |pattern|
|
289
|
+
# Handle both {name} substitution patterns and fixed filename patterns
|
290
|
+
path = if pattern.include?('{name}')
|
291
|
+
pattern.gsub('{name}', clean_name)
|
292
|
+
else
|
293
|
+
pattern # Use pattern as-is for fixed filenames like IIFE
|
294
|
+
end
|
295
|
+
url = "#{cdn_base}/#{package_name}@#{version}/#{path}"
|
296
|
+
|
297
|
+
|
298
|
+
if url_accessible?(url)
|
299
|
+
puts "ā Found island: #{url}"
|
300
|
+
return url
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
puts "ā No island build found for #{package_name}@#{version}"
|
306
|
+
nil
|
307
|
+
end
|
308
|
+
|
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
|
+
def find_working_umd_url(package_name, version)
|
324
|
+
puts " š Searching for UMD build..."
|
325
|
+
|
326
|
+
# Get package name without scope for path patterns
|
327
|
+
clean_name = package_name.split('/').last
|
328
|
+
|
329
|
+
IslandjsRails::CDN_BASES.each do |cdn_base|
|
330
|
+
IslandjsRails::UMD_PATH_PATTERNS.each do |pattern|
|
331
|
+
# Replace placeholders in pattern only if they exist
|
332
|
+
path = if pattern.include?('{name}')
|
333
|
+
pattern.gsub('{name}', clean_name)
|
334
|
+
else
|
335
|
+
pattern # Use pattern as-is for fixed filenames like IIFE
|
336
|
+
end
|
337
|
+
url = "#{cdn_base}/#{package_name}@#{version}/#{path}"
|
338
|
+
|
339
|
+
|
340
|
+
if url_accessible?(url)
|
341
|
+
puts " ā Found UMD: #{url}"
|
342
|
+
|
343
|
+
# Try to detect global name from the UMD content
|
344
|
+
global_name = detect_global_name(package_name, url)
|
345
|
+
|
346
|
+
return [url, global_name]
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
puts " ā No UMD build found for #{package_name}@#{version}"
|
352
|
+
[nil, nil]
|
353
|
+
end
|
354
|
+
|
355
|
+
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
|
+
|
369
|
+
# Check if a package has a partial file
|
370
|
+
def has_partial?(package_name)
|
371
|
+
File.exist?(partial_path_for(package_name))
|
372
|
+
end
|
373
|
+
|
374
|
+
# Get global name for a package (used by webpack externals)
|
375
|
+
def get_global_name_for_package(package_name)
|
376
|
+
detect_global_name(package_name)
|
377
|
+
end
|
378
|
+
|
379
|
+
def react_ecosystem_complete?
|
380
|
+
package_installed?('react') && package_installed?('react-dom')
|
381
|
+
end
|
382
|
+
|
383
|
+
def activate_react_scaffolding!
|
384
|
+
puts "\nš React ecosystem is now complete (React + React-DOM)!"
|
385
|
+
|
386
|
+
uncomment_react_imports!
|
387
|
+
create_hello_world_component!
|
388
|
+
build_bundle!
|
389
|
+
offer_demo_route!
|
390
|
+
end
|
391
|
+
|
392
|
+
def uncomment_react_imports!
|
393
|
+
index_js_path = File.join(Dir.pwd, 'app', 'javascript', 'islands', 'index.js')
|
394
|
+
return unless File.exist?(index_js_path)
|
395
|
+
|
396
|
+
content = File.read(index_js_path)
|
397
|
+
|
398
|
+
# Check if this looks like our commented template
|
399
|
+
if content.include?('// import HelloWorld from') && content.include?('// HelloWorld')
|
400
|
+
# Uncomment the import
|
401
|
+
updated_content = content.gsub('// import HelloWorld from', 'import HelloWorld from')
|
402
|
+
# Uncomment the export within the window.islandjsRails object
|
403
|
+
updated_content = updated_content.gsub(/(\s+)\/\/ HelloWorld/, '\1HelloWorld')
|
404
|
+
|
405
|
+
File.write(index_js_path, updated_content)
|
406
|
+
puts "ā Activated React imports in index.js"
|
407
|
+
else
|
408
|
+
puts "ā ļø index.js has been modified - please add HelloWorld manually"
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
def create_hello_world_component!
|
413
|
+
components_dir = File.join(Dir.pwd, 'app', 'javascript', 'islands', 'components')
|
414
|
+
FileUtils.mkdir_p(components_dir)
|
415
|
+
|
416
|
+
# Create turbo.js utility first
|
417
|
+
create_turbo_utility!
|
418
|
+
|
419
|
+
hello_world_path = File.join(components_dir, 'HelloWorld.jsx')
|
420
|
+
|
421
|
+
if File.exist?(hello_world_path)
|
422
|
+
puts "ā HelloWorld.jsx already exists"
|
423
|
+
return
|
424
|
+
end
|
425
|
+
|
426
|
+
# Copy from gem's template file instead of hardcoded string
|
427
|
+
gem_template_path = File.join(__dir__, '..', 'templates', 'app', 'javascript', 'islands', 'components', 'HelloWorld.jsx')
|
428
|
+
|
429
|
+
if File.exist?(gem_template_path)
|
430
|
+
FileUtils.cp(gem_template_path, hello_world_path)
|
431
|
+
puts "ā Created HelloWorld.jsx component"
|
432
|
+
else
|
433
|
+
puts "ā ļø Template file not found: #{gem_template_path}"
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
def create_turbo_utility!
|
438
|
+
utils_dir = File.join(Dir.pwd, 'app', 'javascript', 'islands', 'utils')
|
439
|
+
FileUtils.mkdir_p(utils_dir)
|
440
|
+
|
441
|
+
turbo_path = File.join(utils_dir, 'turbo.js')
|
442
|
+
|
443
|
+
if File.exist?(turbo_path)
|
444
|
+
puts "ā turbo.js utility already exists"
|
445
|
+
return
|
446
|
+
end
|
447
|
+
|
448
|
+
# Copy from gem's template file instead of hardcoded string
|
449
|
+
gem_template_path = File.join(__dir__, '..', 'templates', 'app', 'javascript', 'islands', 'utils', 'turbo.js')
|
450
|
+
|
451
|
+
if File.exist?(gem_template_path)
|
452
|
+
FileUtils.cp(gem_template_path, turbo_path)
|
453
|
+
puts "ā Created turbo.js utility"
|
454
|
+
else
|
455
|
+
puts "ā ļø Template file not found: #{gem_template_path}"
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
# Load additional core methods
|
462
|
+
require_relative 'core_methods'
|