islandjs-rails 0.7.0 ā 1.0.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 +67 -2
- data/LICENSE.md +1 -1
- data/README.md +60 -52
- data/lib/islandjs_rails/cli.rb +2 -2
- data/lib/islandjs_rails/configuration.rb +2 -3
- data/lib/islandjs_rails/core.rb +15 -69
- data/lib/islandjs_rails/core_methods.rb +61 -65
- data/lib/islandjs_rails/rails_helpers.rb +38 -16
- data/lib/islandjs_rails/railtie.rb +6 -6
- data/lib/islandjs_rails/tasks.rb +2 -2
- data/lib/islandjs_rails/vendor_manager.rb +6 -3
- data/lib/islandjs_rails/version.rb +1 -1
- data/lib/islandjs_rails/vite_installer.rb +286 -0
- data/lib/islandjs_rails/vite_integration.rb +148 -0
- data/lib/islandjs_rails.rb +1 -0
- data/lib/templates/app/javascript/entrypoints/islands.js +20 -0
- data/lib/templates/app/views/islandjs_demo/index.html.erb +2 -2
- data/lib/templates/package.json +4 -11
- data/lib/templates/script/build-vite-atomic.js +89 -0
- data/lib/templates/vite.config.islands.ts +67 -0
- metadata +9 -8
- data/islandjs-rails.gemspec +0 -55
- data/lib/templates/app/javascript/islands/index.js +0 -10
- data/lib/templates/webpack.config.js +0 -85
- data/package.json +0 -12
- data/yarn.lock +0 -1890
|
@@ -3,29 +3,37 @@ module IslandjsRails
|
|
|
3
3
|
# Additional core methods (part 2)
|
|
4
4
|
|
|
5
5
|
def build_bundle!
|
|
6
|
-
puts "šØ Building IslandJS
|
|
6
|
+
puts "šØ Building IslandJS bundle with Vite..."
|
|
7
7
|
|
|
8
8
|
unless system('which yarn > /dev/null 2>&1')
|
|
9
9
|
puts "ā yarn not found, cannot build bundle"
|
|
10
10
|
return false
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
# Check if vite.config.islands.ts exists
|
|
14
|
+
unless File.exist?(Rails.root.join('vite.config.islands.ts'))
|
|
15
|
+
puts "ā ļø vite.config.islands.ts not found. Run: rails islandjs:init"
|
|
16
|
+
return false
|
|
16
17
|
end
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
# Determine which build script to use based on package.json
|
|
20
|
+
package_json_path = Rails.root.join('package.json')
|
|
21
|
+
build_cmd = 'yarn build'
|
|
22
|
+
|
|
23
|
+
if File.exist?(package_json_path)
|
|
24
|
+
package_json = JSON.parse(File.read(package_json_path))
|
|
25
|
+
scripts = package_json['scripts'] || {}
|
|
26
|
+
build_cmd = 'yarn build:islands' if scripts.key?('build:islands')
|
|
22
27
|
end
|
|
23
28
|
|
|
29
|
+
# Run Vite build
|
|
30
|
+
success = system(build_cmd)
|
|
31
|
+
|
|
24
32
|
if success
|
|
25
33
|
puts "ā
Bundle built successfully"
|
|
26
34
|
return true
|
|
27
35
|
else
|
|
28
|
-
puts "ā Build failed. Check your
|
|
36
|
+
puts "ā Build failed. Check your vite.config.islands.ts"
|
|
29
37
|
return false
|
|
30
38
|
end
|
|
31
39
|
end
|
|
@@ -269,27 +277,27 @@ module IslandjsRails
|
|
|
269
277
|
end
|
|
270
278
|
|
|
271
279
|
def install_essential_dependencies!
|
|
272
|
-
puts "š¦ Installing essential
|
|
273
|
-
puts " Installing: #{ESSENTIAL_DEPENDENCIES.join(', ')}"
|
|
274
|
-
|
|
275
|
-
missing_deps = ESSENTIAL_DEPENDENCIES.select do |dep|
|
|
276
|
-
package_name = dep.split('@').first
|
|
277
|
-
!package_installed?(package_name)
|
|
278
|
-
end
|
|
280
|
+
puts "š¦ Installing essential Vite dependencies..."
|
|
279
281
|
|
|
280
|
-
|
|
281
|
-
puts "ā All essential dependencies already installed"
|
|
282
|
-
return
|
|
283
|
-
end
|
|
282
|
+
deps_to_install = []
|
|
284
283
|
|
|
285
|
-
|
|
284
|
+
# Check for Vite and React plugin
|
|
285
|
+
deps_to_install << 'vite@^5.4.19' unless package_installed?('vite')
|
|
286
|
+
deps_to_install << '@vitejs/plugin-react@^5.0.0' unless package_installed?('@vitejs/plugin-react')
|
|
286
287
|
|
|
287
|
-
|
|
288
|
-
puts "
|
|
289
|
-
|
|
288
|
+
if deps_to_install.any?
|
|
289
|
+
puts " Installing: #{deps_to_install.join(', ')}"
|
|
290
|
+
success = system("yarn add --dev #{deps_to_install.join(' ')}")
|
|
291
|
+
|
|
292
|
+
unless success
|
|
293
|
+
puts "ā Failed to install dependencies"
|
|
294
|
+
exit 1
|
|
295
|
+
end
|
|
296
|
+
else
|
|
297
|
+
puts " ā All essential dependencies already installed"
|
|
290
298
|
end
|
|
291
299
|
|
|
292
|
-
puts "ā Installed essential
|
|
300
|
+
puts "ā Installed essential Vite dependencies"
|
|
293
301
|
end
|
|
294
302
|
|
|
295
303
|
def create_scaffolded_structure!
|
|
@@ -561,9 +569,7 @@ module IslandjsRails
|
|
|
561
569
|
puts " ā Removed from package.json: #{package_name}"
|
|
562
570
|
end
|
|
563
571
|
|
|
564
|
-
|
|
565
|
-
copy_template_file('webpack.config.js', configuration.webpack_config_path)
|
|
566
|
-
end
|
|
572
|
+
# No longer needed - vite.config.islands.ts created by ViteInstaller
|
|
567
573
|
|
|
568
574
|
def url_accessible?(url, limit = 5, use_ssl_verification = true)
|
|
569
575
|
require 'openssl'
|
|
@@ -616,59 +622,49 @@ module IslandjsRails
|
|
|
616
622
|
detect_global_name(package_name)
|
|
617
623
|
end
|
|
618
624
|
|
|
619
|
-
|
|
620
|
-
webpack_config_path = configuration.webpack_config_path
|
|
621
|
-
return unless File.exist?(webpack_config_path)
|
|
622
|
-
|
|
623
|
-
content = File.read(webpack_config_path)
|
|
624
|
-
|
|
625
|
-
externals_block = <<~JS
|
|
626
|
-
externals: {
|
|
627
|
-
// IslandjsRails managed externals - do not edit manually
|
|
628
|
-
},
|
|
629
|
-
JS
|
|
630
|
-
|
|
631
|
-
updated_content = content.gsub(
|
|
632
|
-
/externals:\s*\{[^}]*\}(?:,)?/m,
|
|
633
|
-
externals_block.chomp
|
|
634
|
-
)
|
|
635
|
-
|
|
636
|
-
File.write(webpack_config_path, updated_content)
|
|
637
|
-
puts " ā Reset webpack externals"
|
|
638
|
-
end
|
|
625
|
+
# No longer needed - Vite externals are in vite.config.islands.ts
|
|
639
626
|
|
|
640
|
-
def
|
|
641
|
-
|
|
642
|
-
return unless File.exist?(
|
|
627
|
+
def update_vite_externals(package_name = nil, global_name = nil)
|
|
628
|
+
vite_config_path = Rails.root.join('vite.config.islands.ts')
|
|
629
|
+
return unless File.exist?(vite_config_path)
|
|
643
630
|
|
|
644
|
-
content = File.read(
|
|
631
|
+
content = File.read(vite_config_path)
|
|
645
632
|
|
|
646
633
|
externals = {}
|
|
634
|
+
globals = {}
|
|
647
635
|
|
|
648
|
-
# Get installed packages from vendor manifest
|
|
636
|
+
# Get installed packages from vendor manifest
|
|
649
637
|
vendor_manager = IslandjsRails.vendor_manager
|
|
650
638
|
manifest = vendor_manager.send(:read_manifest)
|
|
651
639
|
|
|
652
640
|
manifest['libs'].each do |lib|
|
|
653
641
|
pkg = lib['name']
|
|
654
|
-
|
|
642
|
+
global = get_global_name_for_package(pkg)
|
|
643
|
+
externals[pkg] = true
|
|
644
|
+
globals[pkg] = global
|
|
655
645
|
end
|
|
656
646
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
#{
|
|
662
|
-
|
|
663
|
-
JS
|
|
647
|
+
# Build external array for Vite
|
|
648
|
+
external_array = externals.keys.map { |pkg| "'#{pkg}'" }.join(', ')
|
|
649
|
+
|
|
650
|
+
# Build globals object for Vite
|
|
651
|
+
globals_lines = globals.map { |pkg, global| " '#{pkg}': '#{global}'" }
|
|
652
|
+
globals_block = globals_lines.join(",\n")
|
|
664
653
|
|
|
654
|
+
# Update external array
|
|
665
655
|
updated_content = content.gsub(
|
|
666
|
-
/
|
|
667
|
-
|
|
656
|
+
/external:\s*\[[^\]]*\]/m,
|
|
657
|
+
"external: [#{external_array}]"
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
# Update globals object
|
|
661
|
+
updated_content = updated_content.gsub(
|
|
662
|
+
/globals:\s*\{[^}]*\}/m,
|
|
663
|
+
"globals: {\n#{globals_block}\n }"
|
|
668
664
|
)
|
|
669
665
|
|
|
670
|
-
File.write(
|
|
671
|
-
puts " ā Updated
|
|
666
|
+
File.write(vite_config_path, updated_content)
|
|
667
|
+
puts " ā Updated Vite externals in vite.config.islands.ts"
|
|
672
668
|
end
|
|
673
669
|
end
|
|
674
670
|
end
|
|
@@ -25,32 +25,54 @@ module IslandjsRails
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# Render the main IslandJS bundle script tag
|
|
28
|
+
# Reads from Vite-generated manifest at public/islands/.vite/manifest.json
|
|
29
|
+
#
|
|
30
|
+
# IMPORTANT: Manifest is read on EVERY request (no caching) to ensure
|
|
31
|
+
# content-hashed filenames are always fresh after rebuilds.
|
|
32
|
+
# This enables hot-reload behavior: rebuild Islands ā hard refresh ā new bundle loads
|
|
28
33
|
def island_bundle_script(**attributes)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
# Vite manifest path (generated by vite.config.islands.ts)
|
|
35
|
+
manifest_path = Rails.root.join('public/islands/.vite/manifest.json')
|
|
36
|
+
|
|
32
37
|
# Get formatted HTML attributes with defaults (including auto-nonce and defer)
|
|
33
38
|
html_attributes = script_html_attributes(defer: true, **attributes)
|
|
34
39
|
|
|
35
40
|
unless File.exist?(manifest_path)
|
|
36
|
-
#
|
|
37
|
-
|
|
41
|
+
# Development hint
|
|
42
|
+
if Rails.env.development?
|
|
43
|
+
return html_safe_string("<!-- Islands bundle not built. Run: yarn build:islands -->")
|
|
44
|
+
else
|
|
45
|
+
return html_safe_string("<!-- Islands bundle missing -->")
|
|
46
|
+
end
|
|
38
47
|
end
|
|
39
48
|
|
|
40
49
|
begin
|
|
50
|
+
# ALWAYS read manifest fresh (no caching) to pick up new content hashes
|
|
51
|
+
# This is critical for development workflow and production cache-busting
|
|
41
52
|
manifest = JSON.parse(File.read(manifest_path))
|
|
42
|
-
|
|
43
|
-
|
|
53
|
+
|
|
54
|
+
# Look for islands entrypoint in Vite manifest
|
|
55
|
+
# Vite format: { "app/javascript/entrypoints/islands.js": { "file": "islands_bundle.abc123.js" } }
|
|
56
|
+
entry = manifest['app/javascript/entrypoints/islands.js']
|
|
44
57
|
|
|
45
|
-
if
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# Fallback to direct bundle path
|
|
58
|
+
if entry && entry['file']
|
|
59
|
+
# Bundle path includes content hash for automatic cache-busting
|
|
60
|
+
bundle_path = "/islands/#{entry['file']}"
|
|
49
61
|
html_safe_string("<script src=\"#{bundle_path}\"#{html_attributes}></script>")
|
|
62
|
+
else
|
|
63
|
+
# Manifest exists but entry not found
|
|
64
|
+
if Rails.env.development?
|
|
65
|
+
html_safe_string("<!-- Islands entry not found in manifest. Available keys: #{manifest.keys.join(', ')} -->")
|
|
66
|
+
else
|
|
67
|
+
html_safe_string("<!-- Islands entry not found in manifest -->")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
rescue JSON::ParserError => e
|
|
71
|
+
if Rails.env.development?
|
|
72
|
+
html_safe_string("<!-- Islands manifest parse error: #{e.message} -->")
|
|
73
|
+
else
|
|
74
|
+
html_safe_string("<!-- Islands manifest parse error -->")
|
|
50
75
|
end
|
|
51
|
-
rescue JSON::ParserError
|
|
52
|
-
# Fallback to direct bundle path on manifest parse error
|
|
53
|
-
html_safe_string("<script src=\"#{bundle_path}\"#{html_attributes}></script>")
|
|
54
76
|
end
|
|
55
77
|
end
|
|
56
78
|
|
|
@@ -176,7 +198,7 @@ module IslandjsRails
|
|
|
176
198
|
debug_info = {
|
|
177
199
|
bundle_path: find_bundle_path,
|
|
178
200
|
partials_count: Dir.glob(File.join(IslandjsRails.configuration.partials_dir, '*.html.erb')).count,
|
|
179
|
-
|
|
201
|
+
vite_islands_config_exists: File.exist?(Rails.root.join('vite.config.islands.ts')),
|
|
180
202
|
package_json_exists: File.exist?(IslandjsRails.configuration.package_json_path)
|
|
181
203
|
}
|
|
182
204
|
|
|
@@ -185,7 +207,7 @@ module IslandjsRails
|
|
|
185
207
|
<strong>šļø IslandJS Debug Info:</strong><br>
|
|
186
208
|
Bundle Path: #{debug_info[:bundle_path] || 'Not found'}<br>
|
|
187
209
|
Partials: #{debug_info[:partials_count]} found<br>
|
|
188
|
-
|
|
210
|
+
Vite Islands Config: #{debug_info[:vite_islands_config_exists] ? 'ā' : 'ā'}<br>
|
|
189
211
|
Package.json: #{debug_info[:package_json_exists] ? 'ā' : 'ā'}
|
|
190
212
|
</div>
|
|
191
213
|
HTML
|
|
@@ -33,9 +33,9 @@ module IslandjsRails
|
|
|
33
33
|
return
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
# Check if
|
|
37
|
-
unless File.exist?(Rails.root.join('
|
|
38
|
-
Rails.logger.warn "IslandJS:
|
|
36
|
+
# Check if Vite Islands config exists
|
|
37
|
+
unless File.exist?(Rails.root.join('vite.config.islands.ts'))
|
|
38
|
+
Rails.logger.warn "IslandJS: vite.config.islands.ts not found. Run 'rails islandjs:init' to set up."
|
|
39
39
|
return
|
|
40
40
|
end
|
|
41
41
|
|
|
@@ -45,14 +45,14 @@ module IslandjsRails
|
|
|
45
45
|
return
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
# Check if essential
|
|
49
|
-
essential_deps = ['
|
|
48
|
+
# Check if essential Vite dependencies are installed
|
|
49
|
+
essential_deps = ['vite', '@vitejs/plugin-react']
|
|
50
50
|
missing_deps = essential_deps.select do |dep|
|
|
51
51
|
!system("yarn list #{dep} > /dev/null 2>&1")
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
unless missing_deps.empty?
|
|
55
|
-
Rails.logger.warn "IslandJS: Missing dependencies: #{missing_deps.join(', ')}. Run '
|
|
55
|
+
Rails.logger.warn "IslandJS: Missing dependencies: #{missing_deps.join(', ')}. Run 'yarn install'."
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
end
|
data/lib/islandjs_rails/tasks.rb
CHANGED
|
@@ -57,7 +57,7 @@ namespace :islandjs do
|
|
|
57
57
|
IslandjsRails.status!
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
desc "Clean all island partials and reset
|
|
60
|
+
desc "Clean all island partials and reset Vite externals"
|
|
61
61
|
task :clean => :environment do
|
|
62
62
|
IslandjsRails.clean!
|
|
63
63
|
end
|
|
@@ -69,7 +69,7 @@ namespace :islandjs do
|
|
|
69
69
|
puts "=" * 40
|
|
70
70
|
puts "Package.json path: #{config.package_json_path}"
|
|
71
71
|
puts "Partials directory: #{config.partials_dir}"
|
|
72
|
-
puts "
|
|
72
|
+
puts "Vite Islands config: vite.config.islands.ts"
|
|
73
73
|
puts "Supported CDNs: #{config.supported_cdns.join(', ')}"
|
|
74
74
|
puts "Built-in global name overrides: #{IslandjsRails::BUILT_IN_GLOBAL_NAME_OVERRIDES.size} available"
|
|
75
75
|
end
|
|
@@ -237,9 +237,12 @@ module IslandjsRails
|
|
|
237
237
|
<% # Load each library separately for better caching %>
|
|
238
238
|
ERB
|
|
239
239
|
|
|
240
|
-
|
|
240
|
+
# CRITICAL: Order libraries correctly (react before react-dom)
|
|
241
|
+
ordered_libs = order_libraries(manifest['libs'])
|
|
242
|
+
|
|
243
|
+
ordered_libs.each do |lib|
|
|
241
244
|
content += <<~ERB
|
|
242
|
-
<script src="/islands
|
|
245
|
+
<script src="/vendor/islands/#{lib['file']}" data-turbo-track="reload"></script>
|
|
243
246
|
ERB
|
|
244
247
|
end
|
|
245
248
|
|
|
@@ -256,7 +259,7 @@ module IslandjsRails
|
|
|
256
259
|
<%# IslandJS Rails Vendor UMD Scripts (Combined Mode) %>
|
|
257
260
|
<%# Generated automatically - do not edit manually %>
|
|
258
261
|
<%# Combined bundle: #{combined_info['size_kb']}KB %>
|
|
259
|
-
<script src="/islands
|
|
262
|
+
<script src="/vendor/islands/#{combined_info['file']}" data-turbo-track="reload"></script>
|
|
260
263
|
ERB
|
|
261
264
|
|
|
262
265
|
write_vendor_partial(content)
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative 'vite_integration'
|
|
6
|
+
|
|
7
|
+
module IslandjsRails
|
|
8
|
+
# Idempotent installer for Islands + Vite integration
|
|
9
|
+
class ViteInstaller
|
|
10
|
+
attr_reader :vite_integration, :root_path
|
|
11
|
+
|
|
12
|
+
def initialize(root_path = Rails.root)
|
|
13
|
+
@root_path = Pathname.new(root_path)
|
|
14
|
+
@vite_integration = ViteIntegration.new(root_path)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Main installation method - fully idempotent
|
|
18
|
+
def install!
|
|
19
|
+
puts "šļø Initializing IslandJS Rails with Vite..."
|
|
20
|
+
|
|
21
|
+
# Check current state
|
|
22
|
+
check_prerequisites!
|
|
23
|
+
|
|
24
|
+
# Install/configure based on current state
|
|
25
|
+
if vite_integration.vite_installed?
|
|
26
|
+
puts "ā Vite detected, configuring Islands alongside existing setup"
|
|
27
|
+
configure_islands_alongside_vite!
|
|
28
|
+
else
|
|
29
|
+
puts "ā Installing Vite for Islands"
|
|
30
|
+
install_vite_from_scratch!
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Always run these (idempotent)
|
|
34
|
+
create_islands_structure!
|
|
35
|
+
create_vendor_system!
|
|
36
|
+
inject_islands_helper!
|
|
37
|
+
|
|
38
|
+
puts "\nš IslandJS Rails initialized successfully!"
|
|
39
|
+
print_next_steps
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def check_prerequisites!
|
|
45
|
+
unless command_exists?('node')
|
|
46
|
+
raise Error, "Node.js not found. Please install Node.js 16+ first."
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
unless command_exists?('yarn')
|
|
50
|
+
puts "ā ļø Yarn not found, installing..."
|
|
51
|
+
system('npm install -g yarn')
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def configure_islands_alongside_vite!
|
|
56
|
+
# Vite already exists, just add Islands config
|
|
57
|
+
create_islands_vite_config! unless vite_integration.islands_vite_config_exists?
|
|
58
|
+
create_islands_entrypoint!
|
|
59
|
+
update_package_json_for_islands!
|
|
60
|
+
install_vite_dependencies!
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def install_vite_from_scratch!
|
|
64
|
+
# No Vite yet, install everything
|
|
65
|
+
create_base_vite_config!
|
|
66
|
+
create_islands_vite_config!
|
|
67
|
+
create_islands_entrypoint!
|
|
68
|
+
setup_package_json!
|
|
69
|
+
install_vite_dependencies!
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def create_base_vite_config!
|
|
73
|
+
return if vite_integration.vite_config_exists?
|
|
74
|
+
|
|
75
|
+
puts "ā Creating base vite.config.ts"
|
|
76
|
+
|
|
77
|
+
template = <<~TYPESCRIPT
|
|
78
|
+
import { defineConfig } from 'vite'
|
|
79
|
+
import react from '@vitejs/plugin-react'
|
|
80
|
+
import path from 'path'
|
|
81
|
+
|
|
82
|
+
export default defineConfig({
|
|
83
|
+
plugins: [
|
|
84
|
+
react(),
|
|
85
|
+
],
|
|
86
|
+
resolve: {
|
|
87
|
+
alias: {
|
|
88
|
+
'@': path.resolve(__dirname, 'app/javascript')
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
TYPESCRIPT
|
|
93
|
+
|
|
94
|
+
vite_integration.vite_config_path.write(template)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def create_islands_vite_config!
|
|
98
|
+
return if vite_integration.islands_vite_config_exists?
|
|
99
|
+
|
|
100
|
+
puts "ā Creating vite.config.islands.ts"
|
|
101
|
+
|
|
102
|
+
template_path = File.expand_path('../templates/vite.config.islands.ts', __dir__)
|
|
103
|
+
FileUtils.cp(template_path, vite_integration.islands_vite_config_path)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def create_islands_entrypoint!
|
|
107
|
+
entrypoint_path = root_path.join('app/javascript/entrypoints/islands.js')
|
|
108
|
+
return if entrypoint_path.exist?
|
|
109
|
+
|
|
110
|
+
puts "ā Creating Islands entrypoint"
|
|
111
|
+
|
|
112
|
+
FileUtils.mkdir_p(entrypoint_path.dirname)
|
|
113
|
+
template_path = File.expand_path('../templates/app/javascript/entrypoints/islands.js', __dir__)
|
|
114
|
+
FileUtils.cp(template_path, entrypoint_path)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def setup_package_json!
|
|
118
|
+
if vite_integration.package_json_exists?
|
|
119
|
+
update_package_json_for_islands!
|
|
120
|
+
else
|
|
121
|
+
create_package_json!
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def create_package_json!
|
|
126
|
+
puts "ā Creating package.json"
|
|
127
|
+
|
|
128
|
+
package_json = {
|
|
129
|
+
"private" => true,
|
|
130
|
+
"type" => "module",
|
|
131
|
+
"scripts" => {
|
|
132
|
+
"build:islands" => "vite build --config vite.config.islands.ts",
|
|
133
|
+
"watch:islands" => "vite build --config vite.config.islands.ts --watch"
|
|
134
|
+
},
|
|
135
|
+
"dependencies" => {},
|
|
136
|
+
"devDependencies" => {}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
vite_integration.write_package_json(package_json)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def update_package_json_for_islands!
|
|
143
|
+
puts "ā Updating package.json scripts"
|
|
144
|
+
vite_integration.update_package_json_scripts!
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def install_vite_dependencies!
|
|
148
|
+
puts "š¦ Installing Vite dependencies..."
|
|
149
|
+
|
|
150
|
+
# Check what's already installed
|
|
151
|
+
package_json = vite_integration.read_package_json
|
|
152
|
+
dev_deps = package_json['devDependencies'] || {}
|
|
153
|
+
|
|
154
|
+
deps_to_install = []
|
|
155
|
+
|
|
156
|
+
# Required Vite dependencies
|
|
157
|
+
deps_to_install << 'vite@^5.4.19' unless dev_deps.key?('vite')
|
|
158
|
+
deps_to_install << '@vitejs/plugin-react@^5.0.0' unless dev_deps.key?('@vitejs/plugin-react')
|
|
159
|
+
|
|
160
|
+
if deps_to_install.any?
|
|
161
|
+
puts " Installing: #{deps_to_install.join(', ')}"
|
|
162
|
+
system("yarn add --dev #{deps_to_install.join(' ')}")
|
|
163
|
+
else
|
|
164
|
+
puts " ā Vite dependencies already installed"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def create_islands_structure!
|
|
169
|
+
islands_dir = root_path.join('app/javascript/islands')
|
|
170
|
+
|
|
171
|
+
if islands_dir.exist?
|
|
172
|
+
puts "ā Islands structure already exists"
|
|
173
|
+
return
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
puts "ā Creating Islands directory structure"
|
|
177
|
+
|
|
178
|
+
# Create directories
|
|
179
|
+
FileUtils.mkdir_p(islands_dir.join('components'))
|
|
180
|
+
FileUtils.mkdir_p(islands_dir.join('utils'))
|
|
181
|
+
|
|
182
|
+
# Copy HelloWorld component template
|
|
183
|
+
hello_world_path = islands_dir.join('components/HelloWorld.jsx')
|
|
184
|
+
unless hello_world_path.exist?
|
|
185
|
+
template_path = File.expand_path('../templates/app/javascript/islands/components/HelloWorld.jsx', __dir__)
|
|
186
|
+
FileUtils.cp(template_path, hello_world_path) if File.exist?(template_path)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Copy Turbo utilities
|
|
190
|
+
turbo_utils_path = islands_dir.join('utils/turbo.js')
|
|
191
|
+
unless turbo_utils_path.exist?
|
|
192
|
+
template_path = File.expand_path('../templates/app/javascript/islands/utils/turbo.js', __dir__)
|
|
193
|
+
FileUtils.cp(template_path, turbo_utils_path) if File.exist?(template_path)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def create_vendor_system!
|
|
198
|
+
vendor_dir = root_path.join('public/vendor/islands')
|
|
199
|
+
|
|
200
|
+
if vendor_dir.exist?
|
|
201
|
+
puts "ā Vendor system already exists"
|
|
202
|
+
return
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
puts "ā Creating vendor directory"
|
|
206
|
+
FileUtils.mkdir_p(vendor_dir)
|
|
207
|
+
|
|
208
|
+
# Create vendor UMD partial
|
|
209
|
+
create_vendor_partial!
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def create_vendor_partial!
|
|
213
|
+
partials_dir = root_path.join('app/views/shared/islands')
|
|
214
|
+
FileUtils.mkdir_p(partials_dir)
|
|
215
|
+
|
|
216
|
+
partial_path = partials_dir.join('_vendor_umd.html.erb')
|
|
217
|
+
return if partial_path.exist?
|
|
218
|
+
|
|
219
|
+
puts "ā Creating vendor UMD partial"
|
|
220
|
+
|
|
221
|
+
# Create empty partial - will be populated when packages are installed
|
|
222
|
+
partial_content = <<~ERB
|
|
223
|
+
<%# IslandJS Rails - UMD Vendor Scripts %>
|
|
224
|
+
<%# This partial is auto-generated. Run: rails islandjs:install[react] %>
|
|
225
|
+
ERB
|
|
226
|
+
|
|
227
|
+
partial_path.write(partial_content)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def inject_islands_helper!
|
|
231
|
+
layout_path = vite_integration.islands_layout_path
|
|
232
|
+
|
|
233
|
+
unless layout_path.exist?
|
|
234
|
+
puts "ā ļø application.html.erb not found, skipping helper injection"
|
|
235
|
+
return
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
if vite_integration.layout_has_islands_helper?
|
|
239
|
+
puts "ā Islands helper already in layout"
|
|
240
|
+
return
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
puts "ā Adding <%= islands %> to application.html.erb"
|
|
244
|
+
|
|
245
|
+
content = layout_path.read
|
|
246
|
+
|
|
247
|
+
# Try to inject before </head>
|
|
248
|
+
if content.include?('</head>')
|
|
249
|
+
updated = content.sub('</head>', " <%= islands %>\n </head>")
|
|
250
|
+
layout_path.write(updated)
|
|
251
|
+
else
|
|
252
|
+
puts " ā ļø Could not find </head> tag, please add <%= islands %> manually"
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def print_next_steps
|
|
257
|
+
puts "\nš Next steps:"
|
|
258
|
+
puts "1. Install React UMD libraries:"
|
|
259
|
+
puts " rails \"islandjs:install[react,19.1.0]\""
|
|
260
|
+
puts " rails \"islandjs:install[react-dom,19.1.0]\""
|
|
261
|
+
puts ""
|
|
262
|
+
puts "2. Build Islands bundle:"
|
|
263
|
+
puts " yarn build:islands"
|
|
264
|
+
puts ""
|
|
265
|
+
puts "3. Use in ERB templates:"
|
|
266
|
+
puts " <%= react_component('HelloWorld', { message: 'Hello!' }) %>"
|
|
267
|
+
puts ""
|
|
268
|
+
|
|
269
|
+
if vite_integration.vite_installed?
|
|
270
|
+
puts "š” Existing Vite setup detected! You now have:"
|
|
271
|
+
puts " - Islands dev: yarn watch:islands"
|
|
272
|
+
puts " - Build both: yarn build"
|
|
273
|
+
else
|
|
274
|
+
puts "š” Start development:"
|
|
275
|
+
puts " yarn watch:islands"
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
puts ""
|
|
279
|
+
puts "š Ready to build Islands!"
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def command_exists?(command)
|
|
283
|
+
system("which #{command} > /dev/null 2>&1")
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|